summaryrefslogtreecommitdiff
path: root/emacs-packages/alidist-mode.el
diff options
context:
space:
mode:
authorTimo Wilken2022-12-01 00:59:49 +0100
committerTimo Wilken2022-12-01 00:59:49 +0100
commitaa5abff40df6deac3e662898e5b31672d6591e63 (patch)
treee65fe54612eb06f8156ecde27becfc5505809302 /emacs-packages/alidist-mode.el
parent0794482e11434f7c1c12b93f9e1fa4686244bf74 (diff)
Convert alidist-mode to flymake and tweak flymake config
Diffstat (limited to 'emacs-packages/alidist-mode.el')
-rw-r--r--emacs-packages/alidist-mode.el197
1 files changed, 134 insertions, 63 deletions
diff --git a/emacs-packages/alidist-mode.el b/emacs-packages/alidist-mode.el
index 594fe2d1..0f264e3b 100644
--- a/emacs-packages/alidist-mode.el
+++ b/emacs-packages/alidist-mode.el
@@ -1,91 +1,162 @@
-;;; alidist-mode.el --- Major mode for alidist recipes
+;;; alidist-mode.el --- Major mode for alidist recipes -*- lexical-binding: t -*-
;;; 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
+;;; 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 'custom)
+(require 'flymake)
+(require 'mmm-mode)
+(require 'mmm-cmds)
+(require 'mmm-vars)
(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
+(require 'yaml-mode)
+
+(defgroup alidist-mode nil
+ "Alidist-related options."
+ :group 'languages
+ :prefix "alidist-mode-")
+
+(defcustom alidist-mode-alidistlint-executable "alidistlint"
+ "The alidistlint executable to use. This will be looked up in $PATH."
+ :type '(string)
+ :risky t
+ :group 'alidist-mode)
+
+(defvar alidist-mode--message-regexp
+ (rx bol "-:" ; filename
+ (group (+ digit)) ":" ; line
+ (group (+ digit)) ": " ; column
+ (group (or "note" "warning" "error")) ": " ; type
+ (group (+ not-newline)) eol) ; message
+ "Regular expression matching messages from alidistlint.
+`alidist-flymake' expects the following capturing groups in this
+regexp: (1) line number; (2) column number; (3) error type; (4)
+message.")
+
+(defvar-local alidist-mode--flymake-proc nil
+ "The latest invocation of alidistlint.")
+
+;; See info node: (flymake)An annotated example backend.
+(defun alidist-flymake (report-fn &rest _args)
+ "Run alidistlint and report diagnostics from it using REPORT-FN.
+Any running invocations are killed before running another one."
+ (unless (executable-find alidist-mode-alidistlint-executable)
+ (funcall report-fn :panic
+ :explanation "Cannot find `alidist-mode-alidistlint-executable' program")
+ (error "Cannot find alidistlint executable"))
+
+ ;; Kill previous check, if it's still running.
+ (when (process-live-p alidist-mode--flymake-proc)
+ (kill-process alidist-mode--flymake-proc))
+
+ ;; This needs `lexical-binding'.
+ (let ((source (current-buffer)))
+ (save-restriction
+ (widen)
+ (setq alidist-mode--flymake-proc
+ (make-process
+ :name "alidistlint-flymake" :noquery t :connection-type 'pipe
+ ;; Direct output to a temporary buffer.
+ :buffer (generate-new-buffer " *alidistlint-flymake*")
+ :command (list alidist-mode-alidistlint-executable "-f" "gcc" "-")
+ :sentinel
+ (lambda (proc _event)
+ "Parse diagnostic messages once the process PROC has exited."
+ ;; Check the process has actually exited, not just been suspended.
+ (when (memq (process-status proc) '(exit signal))
+ (unwind-protect
+ ;; Only proceed if we've got the "latest" process.
+ (if (with-current-buffer source (eq proc alidist-mode--flymake-proc))
+ (with-current-buffer (process-buffer proc)
+ (goto-char (point-min))
+ (cl-loop
+ while (search-forward-regexp alidist-mode--message-regexp nil t)
+ for (beg . end) = (flymake-diag-region
+ source
+ (string-to-number (match-string 1))
+ (string-to-number (match-string 2)))
+ for type = (pcase (match-string 3)
+ ("note" :note)
+ ("warning" :warning)
+ ("error" :error)
+ (type (error "Unknown alidistlint error type %s" type)))
+ collect (flymake-make-diagnostic source beg end type (match-string 4))
+ into diags
+ finally (funcall report-fn diags)))
+ (flymake-log :warning "Canceling obsolete check %s" proc))
+ ;; Clean up temporary buffer.
+ (kill-buffer (process-buffer proc)))))))
+ ;; Send the buffer to alidistlint on stdin.
+ (process-send-region alidist-mode--flymake-proc (point-min) (point-max))
+ (process-send-eof alidist-mode--flymake-proc))))
+
+(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)
+(put 'alidist-mode--mmm-refresh-timer 'risky-local-variable t)
-(defun alidist-mode-cancel-refresh-timer ()
+(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)))
+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
+ ;; `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))))
+ ;; 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)))
+ ;; Set up `flymake-mode'.
+ (add-hook 'flymake-diagnostic-functions #'alidist-flymake nil t))
(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-group
+ 'alidist-recipe
+ '((alidist-main-script
+ :submode alidist-script-mode
+ :face mmm-default-submode-face
+ :front (rx line-start "---\n")
+ :back (rx buffer-end))
+ (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 (rx line-start (* whitespace) (1+ (any alnum ?\_)) "_recipe: |\n")
+ ;; End of YAML header, or another YAML key.
+ :back (rx line-start
+ (or "---\n"
+ (seq (* whitespace) (+ (any alnum ?\_)) ":"
+ (or line-end whitespace)))))))
+
+;; Make `mmm-mode' remember `sh-mode' indentation variables.
+(cl-dolist (var sh-var-list)
+ (cl-pushnew `(,var nil (sh-mode))
+ mmm-save-local-variables :test 'equal))
(mmm-add-mode-ext-class 'alidist-mode nil 'alidist-recipe)
-(add-to-list 'auto-mode-alist (cons "\\(\\`\\|/\\)alidist/.+\\.sh\\'"
- #'alidist-mode))
+(add-to-list 'auto-mode-alist
+ (cons (rx (or bot "/") "alidist/" (1+ (not ?\/)) ".sh" eot)
+ #'alidist-mode))
(provide 'alidist-mode)
;;; alidist-mode.el ends here