aboutsummaryrefslogtreecommitdiff
path: root/emacs-packages/alidist-mode.el
diff options
context:
space:
mode:
Diffstat (limited to 'emacs-packages/alidist-mode.el')
-rw-r--r--emacs-packages/alidist-mode.el91
1 files changed, 91 insertions, 0 deletions
diff --git a/emacs-packages/alidist-mode.el b/emacs-packages/alidist-mode.el
new file mode 100644
index 00000000..594fe2d1
--- /dev/null
+++ b/emacs-packages/alidist-mode.el
@@ -0,0 +1,91 @@
+;;; alidist-mode.el --- Major mode for alidist recipes
+;;; Commentary:
+;;; alidist recipes are shell scripts with a YAML header in front.
+;;; We want both these parts highlighted properly, and to lint the
+;;; whole thing with a custom script that glues together yamllint and
+;;; shellcheck with a few custom checks.
+;;; Code:
+
+(require 'flycheck)
+(require 'sh-script)
+(require 'mmm-auto)
+
+(flycheck-def-executable-var alidist "alidistlint")
+
+(flycheck-define-checker alidist
+ "A syntax checker and linter for alidist recipes."
+ ;; `flycheck-alidist-executable' automatically overrides the car of the
+ ;; :command list if set and non-nil.
+ :command ("alidistlint" "--format=gcc" source)
+ :error-patterns
+ ((error line-start (file-name) ":" line ":" column ": error: " (message)
+ " [" (id (minimal-match (one-or-more not-newline))) "]" line-end)
+ (warning line-start (file-name) ":" line ":" column ": warning: " (message)
+ " [" (id (minimal-match (one-or-more not-newline))) "]" line-end)
+ (info line-start (file-name) ":" line ":" column ": note: " (message)
+ " [" (id (minimal-match (one-or-more not-newline))) "]" line-end))
+ :modes (alidist-mode alidist-script-mode))
+
+(add-to-list 'flycheck-checkers 'alidist)
+
+(defvar-local alidist-mode-mmm-refresh-timer nil
+ "An idle timer for the current buffer, to make `mmm-mode' reparse it.")
+(put 'alidist-mode-mmm-refresh-timer 'risky-local-variable t)
+
+(defun alidist-mode-cancel-refresh-timer ()
+ "Cancel and delete the timer that reparses the buffer.
+It is stored in `alidist-mode-mmm-refresh-timer'."
+ (when alidist-mode-mmm-refresh-timer
+ (cancel-timer alidist-mode-mmm-refresh-timer)
+ (setq alidist-mode-mmm-refresh-timer nil)))
+
+(define-derived-mode alidist-mode yaml-mode "alidist"
+ "An outer mode for alidist recipes, handling the metadata."
+ (mmm-mode)
+ ;; `mmm-mode' doesn't refresh its submodes when the buffer changes (e.g. when
+ ;; a *_recipe key is added to the YAML header), so refresh manually when idle.
+ (alidist-mode-cancel-refresh-timer)
+ (add-hook 'kill-buffer-hook #'alidist-mode-cancel-refresh-timer 0 t)
+ (setq alidist-mode-mmm-refresh-timer
+ (run-with-idle-timer
+ 2 t (lambda (original-buffer)
+ (when (eq original-buffer (current-buffer))
+ ;; Silence `mmm-parse-buffer''s annoying message.
+ (let ((inhibit-message t))
+ (mmm-parse-buffer))))
+ ;; Idle timers are global, so make sure we only run the timer in the
+ ;; right buffer. Save the buffer now to enable this, and compare every
+ ;; time the timer ticks over.
+ (current-buffer))))
+
+(define-derived-mode alidist-script-mode sh-mode "Script"
+ "A mode for scripts in alidist recipes with some default settings."
+ (sh-set-shell "bash"))
+
+(mmm-add-group 'alidist-recipe
+ '((alidist-main-script
+ :submode alidist-script-mode
+ :face mmm-default-submode-face
+ :front "^---\n"
+ :back "\\'") ; end of buffer
+ (alidist-option-script
+ :submode alidist-script-mode
+ :face mmm-default-submode-face
+ ;; Any *_recipe key with a multiline string value is probably a script.
+ :front "^[[:space:]]*[a-z_]+_recipe: [|>]-?\n"
+ ;; End of YAML header, or another YAML key.
+ :back "^---\n\\|^[[:space:]]*[a-z_]+:\\($\\| \\)")))
+
+;; Add sh-mode's indentation variables to mmm-mode's list.
+(setq mmm-save-local-variables
+ `(,@(mapcar
+ (lambda (var) `(,var () (sh-mode)))
+ sh-var-list)
+ . ,mmm-save-local-variables))
+
+(mmm-add-mode-ext-class 'alidist-mode nil 'alidist-recipe)
+(add-to-list 'auto-mode-alist (cons "\\(\\`\\|/\\)alidist/.+\\.sh\\'"
+ #'alidist-mode))
+
+(provide 'alidist-mode)
+;;; alidist-mode.el ends here