From cf412740aabae885d5bd735b4874a3456d6cbdeb Mon Sep 17 00:00:00 2001 From: Brian Cully Date: Wed, 24 Feb 2021 21:22:09 -0500 Subject: A bunch of fixes. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Get cargo checker working with non-main source files by making some assumptions about the crate path we’re given in cargo’s check output. * Attempt to fix TRAMP issues in Emacs 28.0.50. These may be due to bugs in TRAMP, in nativecomp, or maybe I just wrote some broken code that happens to work in 27.1. - Simplify output buffer names so they don’t ever have TRAMP paths * Use a generator for JSON parsing. This makes the code much easier to read, IMHO. * Skip past newlines when processing JSON. Now I no longer need to catch errors from the parser, since end-of-file shouldn’t be encountered anymore by the parser. --- flymake-rust.el | 123 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 69 insertions(+), 54 deletions(-) diff --git a/flymake-rust.el b/flymake-rust.el index bc160f6..bc2fec1 100644 --- a/flymake-rust.el +++ b/flymake-rust.el @@ -38,8 +38,7 @@ ;;; Code: -(eval-when-compile - (require 'subr-x)) +(require 'generator) (defgroup flymake-rust nil "Flymake integration for the Rust programming language." @@ -63,10 +62,11 @@ (defcustom flymake-rust-checker 'cargo "Which checker to use. - * cargo (the default) can only check on save, but is suitable + * cargo (default) can only check on save, but is suitable for all projects. - * rustc can check between saves, but isn’t project aware, and + * rustc can check between saves, but isn’t project aware, can’t +figure out your edition (or anything else from Cargo.toml) and will complain about things like missing main functions." :package-version '(flymake-rust . "0.1") :type '(choice (const cargo) @@ -118,15 +118,34 @@ this function attempts to account for." (gethash "message" hash))) ('rustc hash))) +(defun flymake-rust--crate-local-path (crate) + "Return the local path for ‘CRATE’." + (string-match (rx "(path+file://" (group (1+ (not ")"))) ")") + crate) + (match-string-no-properties 1 crate)) + +(flymake-rust--crate-local-path "std-async 0.1.0 (path+file:///home/bjc/src/std-async)") + + (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." +path to the workspace from ‘HASH’ to get the full path. +Unfortunately, cargo doesn’t include this information directly, +but instead appears to expect you to call it with the ‘metadata’ +option to extract it. Which would be fine, if everything were +local, but over TRAMP it may cause undue delay. + +So, to avoid calling cargo twice every check, we make +the (probably safe) assumption that the thing we’re working on is +local, and thus the local crate will have a ‘path+file’ URL in +‘package_id’ section, from which we can extract the project +root." (pcase flymake-rust-checker - ('cargo (thread-last hash - (gethash "target") - (gethash "src_path"))) + ('cargo (expand-file-name file-name + (flymake-rust--crate-local-path + (gethash "package_id" hash)))) ('rustc file-name))) ;; See https://doc.rust-lang.org/rustc/json.html for a description of @@ -153,44 +172,51 @@ checker for ‘SOURCE-BUFFER’" message)))) errlocs)))) -(defun flymake-rust--parse-buffer (proc-buffer source-buffer report-fn) - "Parse ‘PROC-BUFFER’ as JSON compiler output for ‘REPORT-FN’. +(iter-defun flymake-rust--json-generator () + "Return an iterator for JSON data from the current buffer. + +The entire buffer is treated as JSON data, with the exception of +newlines, since that’s the only non-JSON data that the Rust +compiler suite currently emits. -‘PROC-BUFFER’ is the process buffer associated with the checker +This function parses the entire buffer from beginning to end, and +tramples all over point, so save that if you need to." + (goto-char (point-min)) + (while (not (eobp)) + (iter-yield (json-parse-buffer)) + (search-forward "\n" nil t))) + +(defun flymake-rust--parse-buffer (diag-buffer source-buffer report-fn) + "Parse ‘DIAG-BUFFER’ as JSON compiler output for ‘REPORT-FN’. + +‘DIAG-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)) - (while continue - (if-let ((diag (condition-case err - (json-parse-buffer) - (json-end-of-file nil) - (t (error err))))) - (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)) - (funcall report-fn nil :explanation "no errors")))))) + (with-current-buffer diag-buffer + (js-mode) + (let ((diags)) + (iter-do (diag (flymake-rust--json-generator)) + (push (flymake-rust--make-diagnostics source-buffer diag) diags)) + + (if diags + (funcall report-fn (flatten-list diags)) + (funcall report-fn nil :explanation "no errors"))))) ;; TODO: verify that idle timers don’t actually break anything, and ;; are, in fact, an improvement over just calling stuff in the ;; sentinel directly. (defun flymake-rust--sentinel (source-buffer report-fn proc _event) - "Handle events for ‘PROC‘. + "Handle events for ‘PROC’. 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 +processing of compiler output for ‘SOURCE-BUFFER’ which is reported to ‘REPORT-FN’." (when (eq 'exit (process-status proc)) - (process-put proc 'timer - (run-with-idle-timer 0.1 nil + (let ((timer (run-with-idle-timer 0.1 nil 'flymake-rust--parse-buffer - (process-buffer flymake-rust--process) source-buffer report-fn)))) + (process-buffer proc) source-buffer report-fn))) + (process-put proc 'timer timer)))) (defun flymake-rust--make-sentinel (source-buffer report-fn) "Create a sentinel for ‘SOURCE-BUFFER’ reporting to ‘REPORT-FN’." @@ -220,15 +246,8 @@ reported to ‘REPORT-FN’." "Return a command line for rustc reading from standard input." (cons (executable-find flymake-rust-rustc-path t) flymake-rust--rustc-flags)) -(defun flymake-rust--ignore-stderr-p () - "Return t if the stderr output of ‘flymake-rust-checker’ should be ignored. - -Cargo puts non-JSON data on stderr as it runs, so it has to be -shunted elsewhere or it breaks parsing." - (eq flymake-rust-checker 'cargo)) - (defun flymake-rust--checker-command () - "Return a command line to check ‘PATH’. + "Return a command line used to check the current buffer’s file. This uses the value of ‘flymake-rust-checker’ to determine the specific command line." @@ -246,20 +265,16 @@ 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 - :command (flymake-rust--checker-command) - :connection-type 'pipe - :noquery nil - :sentinel (flymake-rust--make-sentinel source-buffer report-fn) - :stderr (and (flymake-rust--ignore-stderr-p) - (flymake-rust--buffer-name "stderr")) - :file-handler t))) + (setq flymake-rust--process + (make-process :name "flymake-rust" + :buffer (get-buffer-create (flymake-rust--buffer-name "stdout")) + :command (flymake-rust--checker-command) + :connection-type 'pipe + :noquery nil + :sentinel (flymake-rust--make-sentinel source-buffer report-fn) + :stderr (when (eq flymake-rust-checker 'cargo) + (get-buffer-create (flymake-rust--buffer-name "stderr"))) + :file-handler t)) (when (eq flymake-rust-checker 'rustc) (process-send-region flymake-rust--process -- cgit v1.2.3