Skip to content
This repository

Integrate haskell-interactive-mode errors with Emacs' next-error support framework #62

Closed
ozataman opened this Issue · 8 comments

5 participants

Ozgun Ataman mstrlu Herbert Valerio Riedel John Wiegley Chris Done
Ozgun Ataman

It might be beneficial for some user setups to get Emacs compiler error system working with haskell-interactive-mode errors.

As an example, here is what I have in my global config file:

(global-set-key (kbd "C-,") 'previous-error)
(global-set-key (kbd "C-.") 'next-error)

These commands also work for grep and occur results and easy to reach keys such as these make it very easy to navigate the them.

Additionally, it would be good to have these commands work from within the Haskell files (and not necessarily exclusively inside the interactive session). With that, the user workflow would be:

  • Compile file
  • Hit next-error
  • Fix error
  • Hit next-error
  • Fix error
  • Recompile
  • ...

without a need to even touch the ghci session in between.

mstrlu
mstrlu commented

I re-implemented the next-error behaviour in my .emacs but didn't come around to integrate it with the haskell-mode source yet. I think it would be better to have `native' emacs next-error support, but my version works very well for me ... If someone wants to try it out, paste the code below after the haskell-mode configuration in your .emacs. EDIT: I forgot something of course: Next-error functionality should then be available with M-g M-n in a haskell source buffer and in the haskell-interactive buffer

(add-hook 'haskell-mode-hook 
          (lambda () 
            (define-key haskell-mode-map (kbd "M-g M-n") 'my-haskell-interactive-mode-next-error)))
(add-hook 'haskell-interactive-mode-hook 
          (lambda ()
            (my-haskell-interactive-error-state-get)
            (define-key haskell-interactive-mode-map (kbd "<return>") 'my-haskell-interactive-mode-return)
            (define-key haskell-mode-map (kbd "M-g M-n") 'my-haskell-interactive-mode-next-error)))

(defstruct my-haskell-interactive-error-state start overlay)
(defvar my-haskell-interactive-error-state nil)
(defun my-haskell-interactive-error-state-init ()
  "Initialize a new error state in the current buffer"
  (let ((state (make-my-haskell-interactive-error-state :start (buffer-end 1))))
    (set (make-local-variable 'my-haskell-interactive-error-state) state)))

(defun my-haskell-interactive-error-state-get ()
  (with-current-buffer (haskell-session-interactive-buffer (haskell-session))
    (unless my-haskell-interactive-error-state (my-haskell-interactive-error-state-init)) 
    my-haskell-interactive-error-state))

(defun my-haskell-interactive-error-state-reset-overlay (state)
  (let ((o (my-haskell-interactive-error-state-overlay state))) 
    (when o(delete-overlay o)))
  (setf (my-haskell-interactive-error-state-overlay state) nil))

(defface my-haskell-interactive-current-error
  '((t (:weight bold :inherit 'haskell-interactive-face-compile-error :underline t)))
  "Face for current haskell error"
  :group 'haskell)

(defun my-haskell-interactive-error-move-overlay (o-orig line)
  (let (start end) 
    (save-excursion 
      (goto-line line) 
      (setq start (line-beginning-position))
      (setq end (line-end-position)))
    (let ((o (if o-orig 
                 (move-overlay o-orig start end) 
               (make-overlay start end))))
      (overlay-put o 'face 'my-haskell-interactive-current-error)
      o)))

(defun my-haskell-interactive-error-state-update ()
  "Sets the overlay (i.e. error position) to the current line"
  (let ((state (my-haskell-interactive-error-state-get)))
    (setf (my-haskell-interactive-error-state-overlay state)
          (my-haskell-interactive-error-move-overlay (my-haskell-interactive-error-state-overlay state)
                                                     (line-number-at-pos)))))

(defun my-haskell-interactive-mode-expand-or-collapse (start collapse)
  (let* ((info-start (save-excursion 
                       (goto-char start)
                       (+ 1 (line-end-position))))
         (visible (not (get-text-property info-start 'invisible)))
         (should-do (or (and visible collapse)
                        (and (not visible) (not collapse)))))
    (when should-do
      (save-excursion
        (goto-char start)
        (haskell-interactive-mode-tab-expand)))))

(defun my-haskell-interactive-mode-expand (start)
  (my-haskell-interactive-mode-expand-or-collapse start nil))

(defun my-haskell-interactive-mode-collapse (start)
  (my-haskell-interactive-mode-expand-or-collapse start t))

(defun my-haskell-interactive-mode-next-error ()
  "Try to emulate the `next-error' handling of
  `compilation-minor-mode' for Chris Done's
  `haskell-interactive-mode'"
  (interactive)
  (with-current-buffer (haskell-session-interactive-buffer (haskell-session))
    (let* ((state (my-haskell-interactive-error-state-get))
           (o (my-haskell-interactive-error-state-overlay state))
           (look-from (if o
                          ;; we already have a current error... look from there (and collapse it)
                          (let ((start (overlay-start o)))
                            (my-haskell-interactive-mode-collapse start)
                            start) 
                        ;; we have no current error... so look form start 
                        (my-haskell-interactive-error-state-start state))))   
      (goto-char look-from)
      (if (haskell-interactive-mode-error-forward)
          (my-haskell-interactive-mode-visit-error)
        (error "No more errors!")))))

(defun my-haskell-interactive-mode-visit-error ()
  (my-haskell-interactive-error-state-update)
  (my-haskell-interactive-mode-expand (point))
  (haskell-interactive-mode-visit-error))

(defun my-haskell-interactive-mode-return ()
  "If point is at an error, go to it. Otherwise use the normal return function"
  (interactive)
  (let ((curr-line (line-number-at-pos))) 
    (if (and (save-excursion
               (previous-line)
               (and (haskell-interactive-mode-error-forward)
                    (= curr-line (line-number-at-pos))))
             (> (point) 
                (my-haskell-interactive-error-state-start 
                         (my-haskell-interactive-error-state-get))))
        (my-haskell-interactive-mode-visit-error)))
  (haskell-interactive-mode-return))

(defun my-haskell-process-load-file ()
  "Reset error state and call real haskell-interactive-mode-return"
  (interactive)
  (when (fboundp 'haskell-session-interactive-buffer) 
    (let ((state (my-haskell-interactive-error-state-get)))
      (with-current-buffer (haskell-session-interactive-buffer (haskell-session))
        (setf (my-haskell-interactive-error-state-start state)  
              ;; This is a hack to set the start before the next
              ;; possible error (as testing for an error at the current
              ;; position is not exposed my haskell-interactive mode
              ;; atm)
              (save-excursion (end-of-buffer) (previous-line) (point))) 
        (my-haskell-interactive-error-state-reset-overlay state))))  
  (call-interactively 'haskell-process-load-file))
Herbert Valerio Riedel
Owner
hvr commented

@mstrlu does your code still work as designed with current HEAD?

Herbert Valerio Riedel
Owner
hvr commented

From what I understood, integrating with the Emacs' "next-error" facility is just a matter of defining the next-error-function for the ghci buffer (and maybe also the next-error-hook variable), so that Emacs knows that the buffer provides error messages (and there may be multiple candidate buffers for a given haskell source buffer, e.g. if you happen to use hs-lint or hs-scan). The following example code shows what I mean:

(add-hook 'haskell-interactive-mode-hook
          '(lambda ()
             (setq next-error-function 'haskell-interactive-next-error-function)
             ))

(defun haskell-interactive-next-error-function (&optional n reset)
  "next-error-function..."
  (message "(haskell-interactive-next-error-function %s %s) called" n reset))
Herbert Valerio Riedel hvr referenced this issue from a commit
Herbert Valerio Riedel hvr Integrate interactive-mode with Emacs' `next-error` facility
This support `next-error-follow-minor-mode` (which can be toggled via
the new interactive-mode keymap binding `C-c C-f`)

Moreover, `RET` now uses `(next-error 0)` when over an compile-message
heading, and with `SPC` `(next-error-no-select 0)` is used.

Note: The default bindings for `{next,previous}-error` are `C-x
\``/`M-g n`/`M-g M-n` and `M-g p`/`M-g M-p` respectively, and work
from all buffers assoicated with the haskell session (i.e. REPL and
source file buffers).

This addresses #62.
797151e
Herbert Valerio Riedel hvr referenced this issue from a commit
Herbert Valerio Riedel hvr Integrate interactive-mode with Emacs' `next-error` facility
This support `next-error-follow-minor-mode` (which can be toggled via
the new interactive-mode keymap binding `C-c C-f`)

Moreover, `RET` now uses `(next-error 0)` when over a compile-message
heading, and with `SPC` `(next-error-no-select 0)` is used.

Note: The default bindings for `{next,previous}-error` are `C-x
\``/`M-g n`/`M-g M-n` and `M-g p`/`M-g M-p` respectively, and work
from all buffers assoicated with the Haskell session (i.e. REPL and
source file buffers).

This addresses #62.
e86fa16
Herbert Valerio Riedel hvr referenced this issue from a commit
Herbert Valerio Riedel hvr Add `-ferror-spans` as default for `haskell-process-args-ghci`
This finally resolves #151 as now all pieces (including #62) are in
place for proper `-ferror-spans` support.
3b634c0
Herbert Valerio Riedel hvr closed this
John Wiegley
Collaborator

Can we have an option not to do this? I like it when modes have their own error queues, independent from those used by compilation-mode and grep-mode. That way, I can step through both lists at the same time. It really bugged me when flycheck insisted on hooking into next-error.

Herbert Valerio Riedel
Owner
hvr commented

I can try to... but you might lose support for some features that come for free with next-error integration; for me having next-error support was one of the show-stoppers keeping me from switching from inf-haskell to haskell-interactive.

Btw, how do you handle the case when you have a Makefile-based compilation and want to step through grep-mode/compilation-mode at the same time?

Chris Done
Collaborator

Wait a minute… next-error just uses whatever next-error-buffer-p buffer you're currently using and switches based on which one is on screen.

Herbert Valerio Riedel
Owner
hvr commented

@chrisdone to be more exact (taken from a recent body of simple.el:next-error-find-buffer):

(defun next-error-find-buffer (&optional avoid-current extra-test-inclusive extra-test-exclusive)
   ...
   ;; 1. If one window on the selected frame displays such buffer, return it.
   ...
   ;; 2. If next-error-last-buffer is an acceptable buffer, use that.
   ...
   ;; 3. If the current buffer is acceptable, choose it.
   ...
   ;; 4. Look for any acceptable buffer.
   ...
   ;; 5. Use the current buffer as a last resort if it qualifies,
   ;; even despite AVOID-CURRENT.
   ...
   ;; 6. Give up.
   (error "No buffers contain error message locations")))

So to switch between two different next-error sources which are visible at the same time, one sure way is to trigger a locus-lookup in the error-buffer (which updates the next-error-last-buffer variable) you want to switch to, and then the next next-error call will continue from that error-buffer. But I'm still wondering which workflow @jwiegley is using that can't be accomodated with next-errors mode of operation.

Chris Done
Collaborator

Yep, that's where I looked for my information. Seems to support @jwiegley's desired use-case.

Herbert Valerio Riedel hvr referenced this issue from a commit
Herbert Valerio Riedel hvr Use `(next-error-internal)` instead of `(next-error 0)` error visiting
This is just a minor fix/optimization. `(next-error-internal)` better
matches the behavior of `compile-goto-error` in the presence of other
next-error capable because it avoids calling `next-error-find-buffer`.

This is related to #62
850880d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.