;;; 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