From 9c0e659209cb838ab23ace26c3dd1586b3a89fba Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Fri, 25 Sep 2026 07:01:36 +0700 Subject: [PATCH] next-error stops on a diagnostic's notes, and CFn is drawn as a type --- TODO.org | 10 ++++++---- emacs/flan-mode.el | 7 ++++--- emacs/flan.el | 15 +++++++++++++-- emacs/test-flan-cider.el | 25 +++++++++++++++++++++++++ emacs/test-flan-mode.el | 2 ++ 5 files changed, 50 insertions(+), 9 deletions(-) diff --git a/TODO.org b/TODO.org index fcf9257b..1937ad1c 100644 --- a/TODO.org +++ b/TODO.org @@ -1862,10 +1862,12 @@ and a half-loaded file. =C-c C-k= is taken by the inspector. so =next-error= works, but a minor mode installs no font-lock. Open: whether the program's output should look different from the compiler's. -** TODO compilation-mode steps over the notes -Nothing sets the skip threshold, so =next-error= walks the errors and steps over -the notes, which are still parsed, coloured and clickable. Labelling them as -warnings would make them navigable and is refused: a note is not a warning. +** DONE compilation-mode steps over the notes +CLOSED: [2026-09-25] +The daemon buffer and the diagnostics buffer set =compilation-skip-threshold= to +0 locally, so =next-error= stops on a note as well as an error. The user's own +default is left alone. Rules out relabelling a note as a warning to make it +navigable. * Docs and the repository diff --git a/emacs/flan-mode.el b/emacs/flan-mode.el index 578c4c70..2319497c 100644 --- a/emacs/flan-mode.el +++ b/emacs/flan-mode.el @@ -235,8 +235,9 @@ reason and is the odd one — it is legal only as the last item of a `def' or a ("\\_<\\([a-zA-Z][a-zA-Z0-9!?*+=<>._-]*/\\)" 1 font-lock-type-face) ;; The types the compiler knows without being told: every primitive in ;; `Types.primitive_names', plus the four applied ones the checker - ;; resolves and the function type. `dyn' is lowercase on purpose — it is - ;; a primitive beside `i64' and `bool', not a container over something. + ;; resolves and the two function types, `Fn' and `CFn'. `dyn' is + ;; lowercase on purpose — it is a primitive beside `i64' and `bool', not + ;; a container over something. ;; `int' and `float' are builtin aliases for `i32' and `f32'. ;; ;; `Unit' is deliberately absent, though `Types.primitive_names' has it. @@ -245,7 +246,7 @@ reason and is the odd one — it is legal only as the last item of a `def' or a ;; word outright — unit is spelled `()'. Drawing it as a valid type would ;; advertise a spelling the parser rejects, which is the same reason ;; `find-restart' and `await' are left out of `flan--special'. - ("\\_<\\(?:[iu]\\(?:8\\|16\\|32\\|64\\)\\|f\\(?:32\\|64\\)\\|bool\\|string\\|dyn\\|int\\|float\\|Never\\|Allocator\\|Ptr\\|Option\\|Vec\\|Map\\|Fn\\)\\_>" + ("\\_<\\(?:[iu]\\(?:8\\|16\\|32\\|64\\)\\|f\\(?:32\\|64\\)\\|bool\\|string\\|dyn\\|int\\|float\\|Never\\|Allocator\\|Ptr\\|Option\\|Vec\\|Map\\|C?Fn\\)\\_>" . font-lock-type-face) ;; A type variable, `$t', which is what a generic `defn' names its ;; parameter types with and what `{:where (ordered? $t)}' constrains. diff --git a/emacs/flan.el b/emacs/flan.el index b6e6cd18..ffbfd0b9 100644 --- a/emacs/flan.el +++ b/emacs/flan.el @@ -78,6 +78,15 @@ ;; the commands here and requires nothing back. (require 'flan-mode) +(defun flan--navigable-notes () + "Make `next-error' stop on a note as well as on an error, in this buffer. +A diagnostic's notes are `file:line:col: note: ...' lines, and `compile' +reads the word `note' as an info message. Its default +`compilation-skip-threshold' of 1 steps over info messages, so the second +location a diagnostic names was clickable but never reached by `next-error'. +Zero skips nothing. A note is not relabelled as a warning to get there." + (setq-local compilation-skip-threshold 0)) + (defgroup flan nil "Talking to a running Flan program." :group 'flan @@ -821,7 +830,8 @@ It builds the program first, which for a cold project is most of this." ;; program runs, and `compilation-mode' would claim it as the output of ;; one finished command — killing the process on a `recompile', among ;; other things it has no business doing to a live session. - (compilation-minor-mode 1)) + (compilation-minor-mode 1) + (flan--navigable-notes)) (make-process :name "flan-daemon" :buffer buf :command args @@ -1449,7 +1459,8 @@ no longer wrong." ;; should go there. The minor mode rather than deriving from ;; `compilation-mode', because this buffer is not the output of a command ;; that ran once. - (compilation-minor-mode 1)) + (compilation-minor-mode 1) + (flan--navigable-notes)) (defvar-local flan--diagnostics-memory-start nil "Marker at the start of the memory section, or nil while there is none. diff --git a/emacs/test-flan-cider.el b/emacs/test-flan-cider.el index a0247452..730b5c27 100644 --- a/emacs/test-flan-cider.el +++ b/emacs/test-flan-cider.el @@ -1680,6 +1680,31 @@ stopped program, which is the case where it should fire." (eq (key-binding (kbd k)) #'flan-macroexpand--not-source)) '("C-c C-c" "C-c C-k" "C-x C-e")))) +;; A diagnostic's note is a second `file:line:col:' line, labelled `note:'. +;; `compile' reads it as an info message, and next-error has to stop on it as +;; well as on the error above it, in both buffers that carry diagnostics. +(message "\nnext-error reaches a note") +(let ((flan-diagnostics-buffer " *test-flan-diag*")) + (with-current-buffer (get-buffer-create flan-diagnostics-buffer) + (flan-diagnostics-mode) + (let ((inhibit-read-only t)) + (insert "a.flan:3:5: cannot add a bool to an i64\n" + " ^\n" + "a.flan:1:7: note: the bool was bound here\n" + " -\n" + "b.flan:9:2: the next error\n")) + (goto-char (point-min)) + (compilation-next-error 1) + (test-flan--check "from the error, n lands on its note" + (looking-at "a.flan:1:7: note:")) + (compilation-next-error 1) + (test-flan--check "and then on the next error" + (looking-at "b.flan:9:2:")) + (test-flan--check "the threshold is this buffer's, not the user's setting" + (and (local-variable-p 'compilation-skip-threshold) + (eql (default-value 'compilation-skip-threshold) 1)))) + (kill-buffer flan-diagnostics-buffer)) + ;; `flan-mode' itself — indentation, which is a function from text to text and ;; so belongs with the other fixture-driven checks rather than with anything ;; that needs a daemon. Loaded rather than run separately because diff --git a/emacs/test-flan-mode.el b/emacs/test-flan-mode.el index 4727dacd..e166a299 100644 --- a/emacs/test-flan-mode.el +++ b/emacs/test-flan-mode.el @@ -425,6 +425,8 @@ ("(def v (Vec u8))" "Vec" font-lock-type-face "Vec") ("(declare apply [(Fn [i64] i64)] i64)" "Fn" font-lock-type-face "Fn") + ("(declare call [(CFn [i64] i64)] i64)" "CFn" + font-lock-type-face "CFn") ("(defn seen? [k $t] bool 1)" "$t" font-lock-type-face "a type variable") ;; The package alias of a qualified name, `clojure-mode''s