Skip to content

Commit 281883d

Browse files
committed
Merge pull request #770 from deepfire/error-overlays
Error overlays in haskell-interactive-mode
2 parents 2144ef3 + bfab941 commit 281883d

File tree

3 files changed

+152
-18
lines changed

3 files changed

+152
-18
lines changed

doc/haskell-mode.texi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,8 @@ Separate sessions per Cabal project @file{haskell-session.el}.
536536
A new inferior Haskell process handling code @file{haskell-process.el}.
537537
@item
538538
New REPL implementation similiar to SLIME/IELM
539+
@item
540+
Navigatable error overlays
539541
@file{haskell-interactive-mode.el}.
540542
@end itemize
541543

haskell-load.el

Lines changed: 148 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ actual Emacs buffer of the module being loaded."
9292
(cursor (haskell-process-response-cursor process))
9393
(warning-count 0))
9494
(haskell-process-set-response-cursor process 0)
95+
(haskell-check-remove-overlays module-buffer)
9596
(while (haskell-process-errors-warnings module-buffer session process buffer)
9697
(setq warning-count (1+ warning-count)))
9798
(haskell-process-set-response-cursor process cursor)
@@ -265,10 +266,135 @@ actual Emacs buffer of the module being loaded."
265266
(modules (split-string modules-string ", ")))
266267
(cons modules modules-string)))
267268

269+
(defface haskell-error-face
270+
'((((supports :underline (:style wave)))
271+
:underline (:style wave :color "#dc322f"))
272+
(t
273+
:inherit error))
274+
"Face used for marking error lines."
275+
:group 'haskell-mode)
276+
277+
(defface haskell-warning-face
278+
'((((supports :underline (:style wave)))
279+
:underline (:style wave :color "#b58900"))
280+
(t
281+
:inherit warning))
282+
"Face used for marking warning lines."
283+
:group 'haskell-mode)
284+
285+
(defface haskell-hole-face
286+
'((((supports :underline (:style wave)))
287+
:underline (:style wave :color "#6c71c4"))
288+
(t
289+
:inherit warning))
290+
"Face used for marking hole lines."
291+
:group 'haskell-mode)
292+
293+
(defvar haskell-check-error-fringe (propertize "!" 'display '(left-fringe exclamation-mark)))
294+
(defvar haskell-check-warning-fringe (propertize "?" 'display '(left-fringe question-mark)))
295+
(defvar haskell-check-hole-fringe (propertize "_" 'display '(left-fringe horizontal-bar)))
296+
297+
(defun haskell-check-overlay-p (ovl)
298+
(overlay-get ovl 'haskell-check))
299+
300+
(defun haskell-check-filter-overlays (xs)
301+
(cl-remove-if-not 'haskell-check-overlay-p xs))
302+
303+
(defun haskell-check-remove-overlays (buffer)
304+
(with-current-buffer buffer
305+
(remove-overlays (point-min) (point-max) 'haskell-check t)))
306+
307+
(defmacro with-overlay-properties (proplist ovl &rest body)
308+
"Evaluate BODY with names in PROPLIST bound to the values of
309+
correspondingly-named overlay properties of OVL."
310+
(let ((ovlvar (cl-gensym "OVL-")))
311+
`(let* ((,ovlvar ,ovl)
312+
,@(mapcar (lambda (p) `(,p (overlay-get ,ovlvar ',p))) proplist))
313+
,@body)))
314+
315+
(defun overlay-start> (o1 o2)
316+
(> (overlay-start o1) (overlay-start o2)))
317+
(defun overlay-start< (o1 o2)
318+
(< (overlay-start o1) (overlay-start o2)))
319+
320+
(defun first-overlay-in-if (test beg end)
321+
(let ((ovls (cl-remove-if-not test (overlays-in beg end))))
322+
(cl-first (sort (cl-copy-list ovls) 'overlay-start<))))
323+
324+
(defun last-overlay-in-if (test beg end)
325+
(let ((ovls (cl-remove-if-not test (overlays-in beg end))))
326+
(cl-first (sort (cl-copy-list ovls) 'overlay-start>))))
327+
328+
(defun haskell-error-overlay-briefly (ovl)
329+
(with-overlay-properties (haskell-msg haskell-msg-type) ovl
330+
(cond ((not (eq haskell-msg-type 'warning))
331+
haskell-msg)
332+
((string-prefix-p "Warning:\n " haskell-msg)
333+
(cl-subseq haskell-msg 13))
334+
(t (error "Invariant failed: a warning message from GHC has unexpected form: %s." haskell-msg)))))
335+
336+
(defun haskell-goto-error-overlay (ovl)
337+
(cond (ovl
338+
(goto-char (overlay-start ovl))
339+
(haskell-mode-message-line (haskell-error-overlay-briefly ovl)))
340+
(t
341+
(message "No further notes from Haskell compiler."))))
342+
343+
(defun haskell-goto-prev-error ()
344+
(interactive)
345+
(haskell-goto-error-overlay
346+
(let ((ovl-at (cl-first (haskell-check-filter-overlays (overlays-at (point))))))
347+
(or (last-overlay-in-if 'haskell-check-overlay-p
348+
(point-min) (if ovl-at (overlay-start ovl-at) (point)))
349+
ovl-at))))
350+
351+
(defun haskell-goto-next-error ()
352+
(interactive)
353+
(haskell-goto-error-overlay
354+
(let ((ovl-at (cl-first (haskell-check-filter-overlays (overlays-at (point))))))
355+
(or (first-overlay-in-if 'haskell-check-overlay-p
356+
(if ovl-at (overlay-end ovl-at) (point)) (point-max))
357+
ovl-at))))
358+
359+
(defun haskell-check-paint-overlay (buffer error-from-this-file-p line msg file type hole coln)
360+
(with-current-buffer buffer
361+
(let (beg end)
362+
(goto-char (point-min))
363+
;; XXX: we can avoid excess buffer walking by relying on the maybe-fact that
364+
;; GHC sorts error messages by line number, maybe.
365+
(cond
366+
(error-from-this-file-p
367+
(forward-line (1- line))
368+
(forward-char (1- coln))
369+
(setq beg (point))
370+
(if (eq type 'hole)
371+
(forward-char (length hole))
372+
(skip-chars-forward "^[:space:]" (line-end-position)))
373+
(setq end (point)))
374+
(t
375+
(setq beg (point))
376+
(forward-line)
377+
(setq end (point))))
378+
(let ((ovl (make-overlay beg end)))
379+
(overlay-put ovl 'haskell-check t)
380+
(overlay-put ovl 'haskell-file file)
381+
(overlay-put ovl 'haskell-msg msg)
382+
(overlay-put ovl 'haskell-msg-type type)
383+
(overlay-put ovl 'help-echo msg)
384+
(overlay-put ovl 'haskell-hole hole)
385+
(cl-destructuring-bind (face fringe) (cl-case type
386+
(warning (list 'haskell-warning-face haskell-check-warning-fringe))
387+
(hole (list 'haskell-hole-face haskell-check-hole-fringe))
388+
(error (list 'haskell-error-face haskell-check-error-fringe)))
389+
(overlay-put ovl 'before-string fringe)
390+
(overlay-put ovl 'face face))))))
391+
268392
(defun haskell-process-errors-warnings (module-buffer session process buffer &optional return-only)
269-
"Trigger handling type errors or warnings. Either prints the
393+
"Trigger handling type errors or warnings. Either prints the
270394
messages in the interactive buffer or if CONT is specified,
271-
passes the error onto that."
395+
passes the error onto that.
396+
397+
When MODULE-BUFFER is non-NIL, paint error overlays."
272398
(cond
273399
((haskell-process-consume
274400
process
@@ -302,27 +428,31 @@ passes the error onto that."
302428
(- (haskell-process-response-cursor process) 1))
303429
(let* ((buffer (haskell-process-response process))
304430
(file (match-string 1 buffer))
305-
(location (match-string 2 buffer))
431+
(location-raw (match-string 2 buffer))
306432
(error-msg (match-string 3 buffer))
307-
(warning (string-match "^Warning:" error-msg))
308-
(splice (string-match "^Splicing " error-msg))
433+
(type (cond ((string-match "^Warning:" error-msg) 'warning)
434+
((string-match "^Splicing " error-msg) 'splice)
435+
(t 'error)))
436+
(critical (not (eq type 'warning)))
437+
;; XXX: extract hole information, pass down to `haskell-check-paint-overlay'
309438
(final-msg (format "%s:%s: %s"
310439
(haskell-session-strip-dir session file)
311-
location
312-
error-msg)))
440+
location-raw
441+
error-msg))
442+
(location (haskell-process-parse-error (concat file ":" location-raw ": x")))
443+
(line (plist-get location :line))
444+
(col1 (plist-get location :col)))
445+
(when module-buffer
446+
(haskell-check-paint-overlay module-buffer (string= (file-truename (buffer-file-name module-buffer)) (file-truename file))
447+
line error-msg file type nil col1))
313448
(if return-only
314-
(let* ((location (haskell-process-parse-error (concat file ":" location ": x")))
315-
(file (plist-get location :file))
316-
(line (plist-get location :line))
317-
(col1 (plist-get location :col)))
318-
(list :file file :line line :col col1 :msg error-msg :type (if warning 'warning 'error)))
319-
(progn (funcall (cond (warning
320-
'haskell-interactive-mode-compile-warning)
321-
(splice
322-
'haskell-interactive-mode-compile-splice)
323-
(t 'haskell-interactive-mode-compile-error))
449+
(list :file file :line line :col col1 :msg error-msg :type type)
450+
(progn (funcall (cl-case type
451+
(warning 'haskell-interactive-mode-compile-warning)
452+
(splice 'haskell-interactive-mode-compile-splice)
453+
(error 'haskell-interactive-mode-compile-error))
324454
session final-msg)
325-
(unless warning
455+
(when critical
326456
(haskell-mode-message-line final-msg))
327457
(haskell-process-trigger-suggestions
328458
session

haskell.el

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949
(define-key map (kbd "C-c C-x") 'haskell-process-cabal)
5050
(define-key map [?\C-c ?\C-b] 'haskell-interactive-switch)
5151
(define-key map [?\C-c ?\C-z] 'haskell-interactive-switch)
52+
(define-key map (kbd "M-n") 'haskell-goto-next-error)
53+
(define-key map (kbd "M-p") 'haskell-goto-prev-error)
5254
map)
5355
"Keymap for using haskell-interactive-mode.")
5456

0 commit comments

Comments
 (0)