From c04d8c1eb2b5f69d18b39dfd24eb42b4a78cb666 Mon Sep 17 00:00:00 2001 From: Maya Tomasek Date: Fri, 15 Sep 2023 19:19:18 +0200 Subject: [PATCH] Add flymake integration Flymake is a pretty good backend to report compiler errors to. Better than `compilation-mode`. This commit allows, if user calls `flymake-mode`, to automatically call a zig command. It is `zig build test` but can be `zig ast-check` or similar. The errors are automatically reported to the buffer. This works project-wide with correct locations. However, a small limitation is, that only one process can be spawned. This is because otherwise there would be much more code to check in a alist. (key would be either `(project-current)` or if nil `(current-buffer)`) I don't think it is a huge limitation, as the commands runs project-wide and most people won't run more than one project check at a time. In the reference implementation the command is ran per buffer, which this implementation does not support. And that is why the limitation is here compared to the flymake manual (https://www.gnu.org/software/emacs/manual/html_node/flymake/An-annotated-example-backend.html). --- zig-mode.el | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/zig-mode.el b/zig-mode.el index 447d4aa..c4f0371 100644 --- a/zig-mode.el +++ b/zig-mode.el @@ -70,6 +70,17 @@ :safe #'stringp :group 'zig-mode) +(defcustom zig-flymake t + "Enable zig flymake integration." + :type 'boolean + :safe #'booleanp + :group 'zig-mode) + +(defcustom zig-flymake-command '("zig" "build" "test") + "Command to execute when calling flymake-start." + :type (repeat 'string) + :group 'zig-mode) + ;; zig CLI commands (defun zig--run-cmd (cmd &optional source &rest args) @@ -117,6 +128,66 @@ If given a SOURCE, execute the CMD on it." (interactive) (zig--run-cmd "run" (file-local-name (buffer-file-name)) "-O" zig-run-optimization-mode)) +;; zig flymake + +(defvar zig--flymake-proc nil + "Current zig flymake process.") +(defun zig-flymake (report-fn &rest _args) + "Zig flymake command, called by flymake-start." + (unless (executable-find "zig") + (error "Cannot find suitable zig executable")) + ;; Kill the last zig--flymake-proc, as flymake can spawn them indefinitely. + (when (process-live-p zig--flymake-proc) + (kill-process zig--flymake-proc)) + + (save-restriction + (widen) + (setq + zig--flymake-proc + (make-process + :name "zig-flymake" :noquery t :connection-type 'pipe + :buffer (generate-new-buffer "*zig-flymake*") + :command zig-flymake-command + :sentinel + (lambda (proc _event) + (when (memq (process-status proc) '(exit signal)) + (unwind-protect + (if (eq proc zig--flymake-proc) + (with-current-buffer (process-buffer proc) + (goto-char (point-min)) + (let ((diags '())) + (while (search-forward-regexp + "^\\(.*.zig\\):\\([0-9]+\\):\\([0-9]+\\): \\(.*\\)$" nil t) + (let* ((msg (match-string 4)) + (file (match-string 1)) + (beg-line (string-to-number (match-string 2))) + (beg-col (string-to-number (match-string 3))) + ;; Zig does not warn, only errors or notes. + (type (if (string-match "^error" msg) + :error + :note)) + (diag (flymake-make-diagnostic + file + (cons beg-line beg-col) + ;; Errors will have properly recalculated ends + ;; as when flymake-make-diagnostic gots file as + ;; locus, it automatically calls flymake-diag-region + ;; when trying to convert line and col into beg end. + nil + type + msg))) + (setq diags (cons diag diags)))) + (funcall report-fn diags))) + (flymake-log :warning "Canceling obsolete check %s" + proc)) + (kill-buffer (process-buffer proc))))))))) + +(defun zig--setup-flymake-backend () + (add-hook 'flymake-diagnostic-functions 'zig-flymake nil t)) + +(if zig-flymake + (add-hook 'zig-mode-hook 'zig--setup-flymake-backend)) + ;; zig fmt (reformatter-define zig-format