diff options
-rw-r--r-- | flymake-rust.el | 120 |
1 files changed, 70 insertions, 50 deletions
diff --git a/flymake-rust.el b/flymake-rust.el index 8092061..9bbb0e3 100644 --- a/flymake-rust.el +++ b/flymake-rust.el @@ -1,10 +1,10 @@ -;;; flymake-rust -- Flymake integration for the Rust programming language -*- lexical-binding: t; -*- +;;; flymake-rust.el -- Flymake integration for the Rust programming language -*- lexical-binding: t; -*- ;; Copyright © 2021 Brian Cully <bjc@kublai.com> ;; Author: Brian Cully <bjc@kublai.com> -;; URL: https://github.com/bjc/nspawn-tramp -;; Keywords: flymake, rust +;; URL: https://github.com/bjc/flymake-rust +;; Keywords: tools, flymake, rust ;; Maintainer: Brian Cully <bjc@kublai.com> ;; Version: 0.1 ;; Package-Requires: ((emacs "27.1")) @@ -38,6 +38,9 @@ ;;; Code: +(eval-when-compile + (require 'subr-x)) + (defgroup flymake-rust nil "Flymake integration for the Rust programming language." :prefix "flymake-rust-" @@ -47,11 +50,13 @@ (defcustom flymake-rust-cargo-path "cargo" "Path to cargo executable." + :package-version '(flymake-rust . "0.1") :type 'string :group 'flymake-rust) (defcustom flymake-rust-rustc-path "rustc" "Path to rustc executable." + :package-version '(flymake-rust . "0.1") :type 'string :group 'flymake-rust) @@ -63,6 +68,7 @@ for all projects. * rustc can check between saves, but isn’t project aware, and will complain about things like missing main functions." + :package-version '(flymake-rust . "0.1") :type '(choice (const cargo) (const rustc)) :options '(cargo rustc) @@ -74,27 +80,18 @@ will complain about things like missing main functions." (defvar flymake-rust--cargo-flags '("check" "--quiet" "--message-format=json") "Additional flags to pass to cargo.") -(defvar flymake-rust--rustc-flags '("--error-format=json" "-") +(defvar flymake-rust--rustc-flags '("--error-format=json" "-o" "/dev/null" "-") "Additional flags to pass to rustc.") (defun flymake-rust--buffer-name (suffix) "What to name the flymake buffer for ‘SUFFIX’." (format "*flymake-rust[%s] %s*" suffix buffer-file-name)) -(defmacro flymake-rust--with-proc-buf (&rest body) - "Run ‘BODY’ in the buffer for the current buffer’s Flymake process." - (declare (indent defun)) - (let ((buf (gensym))) - `(when-let ((,buf (and flymake-rust--process - (process-buffer flymake-rust--process)))) - (with-current-buffer ,buf - ,@body)))) - (defun flymake-rust--extract-error-location (hash) "Extract file location data from ‘HASH’. Returns a sequence of data in the form of (FILE-NAME (BYTE-START -. BYTE-END)) If any data are not available, nil will be used in +. BYTE-END)). If any datum is not available, nil will be used in its place." (let* ((spans (or (gethash "spans" hash) []))) (mapcar (lambda (span) @@ -106,43 +103,49 @@ its place." (defun flymake-rust--level-to-action (level) "Convert ‘LEVEL’ into a Flymake action." - (cond ((string= level "error") :error) - ((string= level "warning") :warning) - (t :note))) + (pcase level + ("error" :error) + ("warning" :warning) + (_ :note))) (defun flymake-rust--extract-msghash (hash) "Return the message hash from ‘HASH’. -Cargo and rustc have slightly different formats for this." - (cond ((eq flymake-rust-checker 'cargo) - (when (string= (gethash "reason" hash) "compiler-message") - (gethash "message" hash))) - ((eq flymake-rust-checker 'rustc) hash))) +Cargo and rustc have slightly different formats for this, which +this function attempts to account for." + (pcase flymake-rust-checker + ('cargo (when (string= (gethash "reason" hash) "compiler-message") + (gethash "message" hash))) + ('rustc hash))) (defun flymake-rust--normalize-path (hash file-name) "Return full path to ‘FILE-NAME’. Cargo only puts the relative path in there, so we need to add the path to the workspace from ‘HASH’ to get the full path." - (cond ((eq flymake-rust-checker 'cargo) - (gethash "src_path" (gethash "target" hash))) - ((eq flymake-rust-checker 'rustc) file-name))) + (pcase flymake-rust-checker + ('cargo (thread-last hash + (gethash "target") + (gethash "src_path"))) + ('rustc file-name))) ;; See https://doc.rust-lang.org/rustc/json.html for a description of ;; the JSON format. (defun flymake-rust--make-diagnostics (source-buffer hash) - "Pull interesting things out of ‘HASH’ for ‘SOURCE-BUFFER ’and forward them to ‘REPORT-FN’." + "Extract diagnostic messages for Flymake from ‘HASH’. + +‘HASH’ is the hash-table representation of the JSON output by the +checker for ‘SOURCE-BUFFER’" (when-let ((msghash (flymake-rust--extract-msghash hash))) (let ((message (gethash "message" msghash)) (level (gethash "level" msghash)) (errlocs (flymake-rust--extract-error-location msghash))) (mapcar (lambda (errloc) - (when-let ((start (caadr errloc)) - (end (cdadr errloc))) + (pcase-let ((`(,file-name (,start . ,end)) errloc)) ;; Filter out errors that don’t relate to ;; ‘source-buffer’. - (when (or (string= (car errloc) "<anon>") - (string= (flymake-rust--normalize-path hash (car errloc)) + (when (or (string= file-name "<anon>") + (string= (flymake-rust--normalize-path hash file-name) (file-local-name (buffer-file-name source-buffer)))) (flymake-make-diagnostic source-buffer start end @@ -150,9 +153,13 @@ path to the workspace from ‘HASH’ to get the full path." message)))) errlocs)))) -(defun flymake-rust--parse-buffer (source-buffer report-fn) - "Parse the diagnostics for ‘SOURCE-BUFFER’ and send to ‘REPORT-FN’." - (flymake-rust--with-proc-buf +(defun flymake-rust--parse-buffer (proc-buffer source-buffer report-fn) + "Parse ‘PROC-BUFFER’ as JSON compiler output for ‘REPORT-FN’. + +‘PROC-BUFFER’ is the process buffer associated with the checker +process for ‘SOURCE-BUFFER’, and is expected to contain JSON +output from the Rust compiler." + (with-current-buffer proc-buffer (let ((continue t) (diags nil)) (goto-char (point-min)) @@ -161,9 +168,9 @@ path to the workspace from ‘HASH’ to get the full path." (json-parse-buffer) (json-end-of-file nil) (t (error err))))) - (progn - (push (flymake-rust--make-diagnostics source-buffer diag) diags)) + (push (flymake-rust--make-diagnostics source-buffer diag) diags) (setq continue nil))) + (let ((diags (flatten-list diags))) (if diags (funcall report-fn (flatten-list diags)) @@ -173,15 +180,17 @@ path to the workspace from ‘HASH’ to get the full path." ;; are, in fact, an improvement over just calling stuff in the ;; sentinel directly. (defun flymake-rust--sentinel (source-buffer report-fn proc _event) - "Call ‘REPORT-FN’ for ‘SOURCE-BUFFER’ with diagnostics when ‘PROC’ finishes. + "Handle events for ‘PROC‘. -This function does not directly call ‘REPORT-FN’, but instead -sets up a short timer to do so. This is done because sentinel -processes inhibits Emacs handling of events like quit." +The only event currently being handled is the process finishing, +at which point a short-delay idle timer is set up to handle the +processing of compiler output for ‘SOURCE-BUFFER’, which will be +reported to ‘REPORT-FN’." (when (eq 'exit (process-status proc)) (process-put proc 'timer (run-with-idle-timer 0.1 nil - 'flymake-rust--parse-buffer source-buffer report-fn)))) + 'flymake-rust--parse-buffer + (process-buffer flymake-rust--process) source-buffer report-fn)))) (defun flymake-rust--make-sentinel (source-buffer report-fn) "Create a sentinel for ‘SOURCE-BUFFER’ reporting to ‘REPORT-FN’." @@ -189,20 +198,21 @@ processes inhibits Emacs handling of events like quit." (flymake-rust--sentinel source-buffer report-fn proc event))) (defun flymake-rust--cleanup () - "Clean up and leftover processes and buffers for the current buffer." + "Clean up processes and buffers associated with the current buffer." (when-let ((timer (and flymake-rust--process (process-get flymake-rust--process 'timer)))) (cancel-timer timer)) + (when (process-live-p flymake-rust--process) (flymake-log :warning "Killing out-of-date checker process.") (delete-process flymake-rust--process)) - (mapc (lambda (suffix) - (when-let ((buf (get-buffer (flymake-rust--buffer-name suffix)))) - (kill-buffer buf))) - '("stdout" "stderr"))) + + (dolist (suffix '("stdout" "stderr")) + (when-let ((buf (get-buffer (flymake-rust--buffer-name suffix)))) + (kill-buffer buf)))) (defun flymake-rust--cargo-command () - "Return an appropriate cargo check command line." + "Return a command line for cargo check." (cons (executable-find flymake-rust-cargo-path t) flymake-rust--cargo-flags)) @@ -220,17 +230,26 @@ shunted elsewhere or it breaks parsing." (defun flymake-rust--checker-command () "Return a command line to check ‘PATH’. -This will use the value configured in ‘flymake-rust-checker’ to what to run." - (cond ((eq flymake-rust-checker 'cargo) (flymake-rust--cargo-command)) - ((eq flymake-rust-checker 'rustc) (flymake-rust--rustc-command)))) +This uses the value of ‘flymake-rust-checker’ to determine the +specific command line." + (pcase flymake-rust-checker + ('cargo (flymake-rust--cargo-command)) + ('rustc (flymake-rust--rustc-command)))) (defun flymake-rust--call (source-buffer report-fn) - "Begin checking ‘SOURCE-BUFFER’ and report to ‘REPORT-FN’." + "Check ‘SOURCE-BUFFER’ for errors and report them to ‘REPORT-FN’. + +‘REPORT-FN’ is a function normally created by Flymake which +expects a list of diagnostics created by +‘flymake-make-diagnostic’. For further information, see the Info +node ‘(flymake)Backend functions’." (with-current-buffer source-buffer (flymake-rust--cleanup) + (let ((buf (get-buffer-create (flymake-rust--buffer-name "stdout")))) (with-current-buffer buf (js-mode)) + (setq flymake-rust--process (make-process :name "flymake-rust" :buffer buf @@ -256,6 +275,7 @@ For the value of ‘ARGS’, see the documentation for (when (or (not rc) (cadr rc)) (flymake-rust--call (current-buffer) report-fn)))) +;;;###autoload (defun flymake-rust-setup () "Provide Flymake support for the Rust programming language." (add-hook 'flymake-diagnostic-functions 'flymake-rust--backend nil t)) |