diff --git a/lisp/progmodes/rust-ts-mode.el b/lisp/progmodes/rust-ts-mode.el index c67ac43e4d0..c16e8a6c4c4 100644 --- a/lisp/progmodes/rust-ts-mode.el +++ b/lisp/progmodes/rust-ts-mode.el @@ -48,6 +48,20 @@ rust-ts-mode-indent-offset :safe 'integerp :group 'rust) +(defcustom rust-ts-flymake-command '("clippy-driver" "-") + "The external tool that will be used to perform the syntax check. +This is a non empty list of strings, the checker tool possibly followed +by required arguments. Once launched it will receive the Rust source +to be checked as its standard input." + :version "30.1" + :type '(choice (const :tag "Clippy standalone" ("clippy-driver" "-")) + ;; TODO: Maybe add diagnostics filtering by file name, + ;; to limit non-project list to the current buffer. + ;; Or annotate them with file names, at least. + (const :tag "Clippy cargo" ("cargo" "clippy")) + (repeat :tag "Custom command" string)) + :group 'rust) + (defvar rust-ts-mode-prettify-symbols-alist '(("&&" . ?∧) ("||" . ?∨) ("<=" . ?≤) (">=" . ?≥) ("!=" . ?≠) @@ -417,6 +431,67 @@ rust-ts-mode--prettify-symbols-compose-p "operator")) (_ t)))) +(defvar rust-ts--flymake-proc nil) + +(defun rust-ts-flymake--helper (process-name command parser-fn) + (when (process-live-p rust-ts--flymake-proc) + (kill-process rust-ts--flymake-proc)) + + (let ((source (current-buffer))) + (save-restriction + (widen) + (setq + rust-ts--flymake-proc + (make-process + :name process-name :noquery t :connection-type 'pipe + :buffer (generate-new-buffer (format " *%s*" process-name)) + :command command + :sentinel + (lambda (proc _event) + (when (and (eq 'exit (process-status proc)) (buffer-live-p source)) + (unwind-protect + (if (with-current-buffer source (eq proc rust-ts--flymake-proc)) + (with-current-buffer (process-buffer proc) + (funcall parser-fn proc source)) + (flymake-log :debug "Canceling obsolete check %s" + proc)) + (kill-buffer (process-buffer proc))))))) + (process-send-region rust-ts--flymake-proc (point-min) (point-max)) + (process-send-eof rust-ts--flymake-proc)))) + +(defun rust-ts-flymake (report-fn &rest _args) + "Rust backend for Flymake." + (unless (executable-find (car rust-ts-flymake-command)) + (error "Cannot find the rust flymake program: %s" (car rust-ts-flymake-command))) + + (rust-ts-flymake--helper + "rust-ts-flymake" + rust-ts-flymake-command + (lambda (_proc source) + (goto-char (point-min)) + (cl-loop + while (search-forward-regexp + (concat + "^\\(\\(?:warning\\|error\\|help\\).*\\)\n +--> [^:]+:" + "\\([0-9]+\\):\\([0-9]+\\)\\(\\(?:\n[^\n]+\\)*\\)\n\n") + nil t) + for msg1 = (match-string 1) + for msg2 = (match-string 4) + for (beg . end) = (flymake-diag-region + source + (string-to-number (match-string 2)) + (string-to-number (match-string 3))) + for type = (if (string-match "^warning" msg1) + :warning + :error) + collect (flymake-make-diagnostic source + beg + end + type + (concat msg1 msg2)) + into diags + finally (funcall report-fn diags))))) + ;;;###autoload (define-derived-mode rust-ts-mode prog-mode "Rust" "Major mode for editing Rust, powered by tree-sitter." @@ -464,6 +539,9 @@ rust-ts-mode (setq-local electric-indent-chars (append "{}():;,#" electric-indent-chars)) + ;; Flymake + (add-hook 'flymake-diagnostic-functions #'rust-ts-flymake nil 'local) + ;; Navigation. (setq-local treesit-defun-type-regexp (regexp-opt '("enum_item"