Search code examples
emacselisp

Cycle through results using "next-error" & "previous-error"


I use both "next-error" & "previous-error" to cycle through results. But once reaching the last entry the cycle doesn't go back to the top and so on.

Is there a way to have the cycling continue to the top once reaching the bottom and vice versa?


Solution

  • I wrote some elisp to achieve this, here it

    (defvar my-modeline-flash-color "#af00d7")
    
    (defun my-indicate-error-nav-wrapped (direction)
      "Display a message in minibuffer indicating that we wrapped
    also flash the mode-line"
      (let ((mode-line-color (face-background 'mode-line)))
        (message "Wrapped %s error" (symbol-name direction))
        (set-face-background 'mode-line my-modeline-flash-color)
        (sit-for 0.3)
        (set-face-background 'mode-line mode-line-color)))
    
    (defun my-next-error-wrapped (&optional arg reset)
      "Jumps to previous error if at first error jump to last error instead.
    Prefix argument ARG says how many error messages to move forwards (or
    backwards, if negative). With just C-u as prefix moves to first error"
      (interactive "P")
      (condition-case nil
          (call-interactively 'next-error)
        ('user-error (progn (next-error 1 t)
                            (my-indicate-error-nav-wrapped 'next)))))
    
    (defun my-jump-to-last-error (buffer)
      "Jump to last error in the BUFFER, this assumes that
    the error is at last but third line"
      (save-selected-window
        (select-window (get-buffer-window buffer))
        (goto-char (point-max))
        (forward-line -3)
        (call-interactively 'compile-goto-error)))
    
    (defun my-previous-error-wrapped (&optional arg)
      "Jumps to previous error if at first error jump to last error instead.
    Prefix argument ARG says how many error messages to move backwards (or
    forwards, if negative)."
      (interactive "P")
      (condition-case nil
          (if (compilation-buffer-p (current-buffer))
              (compilation-previous-error 1)
            (call-interactively 'previous-error))
        ('user-error (progn
                       (let ((error-buffer (next-error-find-buffer)))
                         ;; If the buffer has an associated error buffer use it to
                         ;; to move to last error
                         (if (and (not (eq (current-buffer) error-buffer))
                                  (compilation-buffer-p error-buffer))
                             (my-jump-to-last-error error-buffer)
                           ;; Otherwise move to last point and invoke previous error
                           (goto-char (point-max))
                           (call-interactively 'previous-error))
                         (my-indicate-error-nav-wrapped 'previous))))))
    

    Bind the functions my-previous-error-wrapped and my-next-error-wrapped to some convenient keys and you are good to go. The modeline will be flashed momentarily using the my-modeline-flash-color, a minibuffer message will also be displayed when wrapping.

    How does this work?

    Just in case you want to how this works here is the explanation, next-error internally uses the value of next-error-function to find the function to call to get the next error in the buffer, now most of these functions (or at least the ones I checked) try to navigate to next error in the buffer, or signal an user-error if there are no more errors in the buffer. In the above function we are calling next-error and handling user-error. Now if user-error occurs we make a second call to next-error with the argument reset as t, which takes us to the first error in the buffer. You read more about arguments passed next-error using C-h f next-error RET. Read this to learn more about elisp errors. Hope that helps