aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrian Cully <bjc@kublai.com>2021-02-24 21:22:09 -0500
committerBrian Cully <bjc@kublai.com>2021-02-25 08:58:52 -0500
commitcf412740aabae885d5bd735b4874a3456d6cbdeb (patch)
tree0b241a51392476e2ea08ee9f71d70c1383ea6275
parentc5b12f62aa415a1183c399c7a842d90cf82841dd (diff)
downloadflymake-rust-cf412740aabae885d5bd735b4874a3456d6cbdeb.tar.gz
flymake-rust-cf412740aabae885d5bd735b4874a3456d6cbdeb.zip
A bunch of fixes.
* 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.
-rw-r--r--flymake-rust.el123
1 files 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