Subject: bug#77809: adding next-error support for flymake diagnostics buffers

From: Spencer Baugh <[email-masked]>
Date: Tue, 22 Apr 2025 15:37:49 -0400

Spencer Baugh <[email-masked]> writes:
> This looks good to me then, seems to work fine.  Let's wait a few more
> days for comments and then I think this can be installed.

I think this is ready to be installed.  I went ahead and updated your
commit message to include a changelog entry - please include that in
future changes, see "Generating ChangeLog entries" in CONTRIBUTE.

Eli, can you please install the attached change?

>From cdfac45643803685b60c09d0f45b7d6e2f8f4289 Mon Sep 17 00:00:00 2001
From: Matthew Tromp <[email-masked]>
Date: Tue, 22 Apr 2025 15:27:58 -0400
Subject: [PATCH] Add next-error support for flymake diagnostics buffers

This patch adds next-error support for flymake diagnostics buffers.
Buffers created with `flymake-show-buffer-diagnostics' and
`flymake-show-project-diagnostics' are now next-error enabled, and
`next-error' and `previous-error' will navigate through their listed
diagnostics.

* lisp/progmodes/flymake.el (flymake-current-diagnostic-line)
(flymake--diagnostics-next-error): Add.
(flymake-show-diagnostic, flymake-show-buffer-diagnostics)
(flymake-show-project-diagnostics): Set next-error-last-buffer.
(flymake--tabulated-setup): Set next-error-function. (bug#77809)
---
 lisp/progmodes/flymake.el | 30 +++++++++++++++++++++++++++++-
 1 file changed, 29 insertions(+), 1 deletion(-)

diff --git a/lisp/progmodes/flymake.el b/lisp/progmodes/flymake.el
index 8d63b23f2b8..3e2c24339a8 100644
--- a/lisp/progmodes/flymake.el
+++ b/lisp/progmodes/flymake.el
@@ -1457,6 +1457,10 @@ flymake-mode-map
     map)
   "Keymap for `flymake-mode'")
 
+(defvar-local flymake-current-diagnostic-line 0
+  "The line of the most recently focused diagnostic in the flymake
+diagnostics buffer.")
+
 ;;;###autoload
 (define-minor-mode flymake-mode
   "Toggle Flymake mode on or off.
@@ -1917,7 +1921,8 @@ flymake-diagnostics-buffer-mode-map
 (defun flymake-show-diagnostic (pos &optional other-window)
   "From Flymake diagnostics buffer, show source of diagnostic at POS."
   (interactive (list (point) t))
-  (let* ((id (or (tabulated-list-get-id pos)
+  (let* ((diagnostics-buffer (current-buffer))
+         (id (or (tabulated-list-get-id pos)
                  (user-error "Nothing at point")))
          (diag (plist-get id :diagnostic))
          (locus (flymake--diag-locus diag))
@@ -1927,6 +1932,7 @@ flymake-show-diagnostic
                   (goto-char b)
                   (pulse-momentary-highlight-region
                    b (or e (line-end-position))))))
+    (setq flymake-current-diagnostic-line (line-number-at-pos pos))
     (with-current-buffer (cond ((bufferp locus) locus)
                                (t (find-file-noselect locus)))
       (with-selected-window
@@ -1942,6 +1948,8 @@ flymake-show-diagnostic
                                                  (car beg)
                                                  (cdr beg))))
                  (funcall visit bbeg bend)))))
+      ;; Emacs < 27
+      (setq next-error-last-buffer diagnostics-buffer)
       (current-buffer))))
 
 (defun flymake-goto-diagnostic (pos)
@@ -2055,6 +2063,7 @@ flymake--tabulated-setup-1
 (defun flymake--tabulated-setup (use-project)
   "Helper for `flymake-diagnostics-buffer-mode'.
 And also `flymake-project-diagnostics-mode'."
+  (setq-local next-error-function #'flymake--diagnostics-next-error)
   (let ((saved-r-b-f revert-buffer-function)
         (refresh
          (lambda ()
@@ -2084,6 +2093,23 @@ flymake--tabulated-setup
             (funcall refresh)
             (apply saved-r-b-f args)))))
 
+(defun flymake--diagnostics-next-error (n &optional reset)
+  "`next-error-function' for flymake diagnostics buffers.
+N is an integer representing how many errors to move.
+If RESET is non-nil, returns to the beginning of the errors before
+moving."
+  (let ((line (if reset 1 flymake-current-diagnostic-line))
+        (total-lines (count-lines (point-min) (point-max))))
+    (goto-char (point-min))
+    (unless (zerop total-lines)
+      (let ((target-line (+ line n)))
+        (setq target-line (max 1 target-line))
+        (setq target-line (min target-line total-lines))
+        (forward-line (1- target-line))))
+    (when-let ((win (get-buffer-window nil t)))
+      (set-window-point win (point)))
+    (flymake-goto-diagnostic (point))))
+
 (define-derived-mode flymake-diagnostics-buffer-mode tabulated-list-mode
   "Flymake diagnostics"
   "A mode for listing Flymake diagnostics."
@@ -2140,6 +2166,7 @@ flymake-show-buffer-diagnostics
          window)
     (with-current-buffer target
       (setq flymake--diagnostics-buffer-source source)
+      (setq next-error-last-buffer (current-buffer))
       (revert-buffer)
       (setq window
             (display-buffer (current-buffer)
@@ -2246,6 +2273,7 @@ flymake-show-project-diagnostics
     (with-current-buffer buffer
       (flymake-project-diagnostics-mode)
       (setq-local flymake--project-diagnostic-list-project prj)
+      (setq next-error-last-buffer (current-buffer))
       (revert-buffer)
       (display-buffer (current-buffer)
                       `((display-buffer-reuse-window
-- 
2.39.3