Subject: adding next-error support for flymake diagnostics buffers
From: [email-masked]
To: [email-masked]
Date: Mon, 14 Apr 2025 17:06:21 -0400
List-Id: <debbugs-submit.debbugs.gnu.org>

Tags: patch

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.

In GNU Emacs 30.1.50 (build 1, x86_64-pc-linux-gnu, X toolkit, cairo
version 1.18.2, Xaw3d scroll bars)
Windowing system distributor 'The X.Org Foundation', version 11.0.12101016
System Description: NixOS 24.11 (Vicuna)

Configured using:
 'configure
 --prefix=/nix/store/yffwm14bwi3vask5wfx6pcrdgxck61ba-emacs-30.1
 --disable-build-details --with-modules --with-x-toolkit=lucid
 --with-cairo --with-xft --with-compress-install
 --with-toolkit-scroll-bars --with-native-compilation
 --without-imagemagick --with-mailutils --without-small-ja-dic
 --with-tree-sitter --with-xinput2 --without-xwidgets --with-dbus
 --with-selinux'

diff --git a/lisp/progmodes/flymake.el b/lisp/progmodes/flymake.el
index 6cc7e1f7a79..002e587b029 100644
--- a/lisp/progmodes/flymake.el
+++ b/lisp/progmodes/flymake.el
@@ -1390,6 +1390,10 @@ flymake-mode-map
                (format "<%s> <mouse-1>" flymake-fringe-indicator-position)
                #'flymake-show-buffer-diagnostics-at-event-line)
 
+(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.
@@ -1881,7 +1885,8 @@ flymake-pulse-momentary-highlight-region
 (defun flymake-show-diagnostic (pos &optional other-window)
   "Show location 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))
@@ -1890,6 +1895,7 @@ flymake-show-diagnostic
          (visit (lambda (b e)
                   (goto-char b)
                   (flymake-pulse-momentary-highlight-region b e))))
+    (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
@@ -1905,6 +1911,8 @@ flymake-show-diagnostic
                                                  (car beg)
                                                  (cdr beg))))
                  (funcall visit bbeg bend)))))
+      (setq next-error-buffer diagnostics-buffer
+            next-error-last-buffer diagnostics-buffer)
       (current-buffer))))
 
 (defun flymake-goto-diagnostic (pos)
@@ -2008,6 +2016,23 @@ flymake--diagnostics-base-tabulated-list-format
     ("Backend" 8 t)
     ("Message" 0 t)])
 
+(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."
@@ -2015,6 +2040,9 @@ flymake-diagnostics-buffer-mode
   (setq tabulated-list-format flymake--diagnostics-base-tabulated-list-format)
   (setq tabulated-list-entries
         'flymake--diagnostics-buffer-entries)
+
+  (setq-local next-error-function #'flymake--diagnostics-next-error)
+
   (tabulated-list-init-header))
 
 (defun flymake--diagnostics-buffer-name ()
@@ -2036,6 +2064,7 @@ flymake-show-buffer-diagnostics
                        (current-buffer)))))
     (with-current-buffer target
       (setq flymake--diagnostics-buffer-source source)
+      (setq next-error-last-buffer (current-buffer))
       (revert-buffer)
       (display-buffer (current-buffer)
                       `((display-buffer-reuse-window
@@ -2085,6 +2114,9 @@ flymake-project-diagnostics-mode
   (setq tabulated-list-format
         (vconcat [("File" 25 t)]
                  flymake--diagnostics-base-tabulated-list-format))
+
+  (setq-local next-error-function #'flymake--diagnostics-next-error)
+
   (setq tabulated-list-entries
         'flymake--project-diagnostics-entries)
   (tabulated-list-init-header))
@@ -2149,6 +2181,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