Skip to content

Commit 3603ec2

Browse files
committed
Add new Haskell compilation sub-mode
This is a compilation sub-mode tailored to GHC's compile messages with some heuristics for supporting Cabal projects in a DWIM fashion. This is completely orthogonal to haskell-interactive and inf-haskell. See new "compilation" chapter in the info manual for more details.
1 parent b6f7c82 commit 3603ec2

File tree

5 files changed

+200
-1
lines changed

5 files changed

+200
-1
lines changed

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ ELFILES = \
1414
haskell-cabal.el \
1515
haskell-checkers.el \
1616
haskell-compat.el \
17+
haskell-compile.el \
1718
haskell-decl-scan.el \
1819
haskell-doc.el \
1920
haskell-font-lock.el \

NEWS

+3
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ See also [[https://github.com/haskell/haskell-mode/compare/v13.07...v13.08][deta
4949

5050
- Add support for "cabal repl" process type to `haskell-interactive-mode'
5151

52+
- Add new Haskell compilation sub-mode and associated `haskell-compile'
53+
command
54+
5255
* Changes in 13.7
5356

5457
See also [[https://github.com/haskell/haskell-mode/compare/v13.06...v13.07][detailed Git history]].

haskell-compile.el

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
;;; haskell-compile.el --- Haskell/GHC compilation sub-mode
2+
3+
;; Copyright (C) 2013 Herbert Valerio Riedel
4+
5+
;; Author: Herbert Valerio Riedel <[email protected]>
6+
7+
;; This file is not part of GNU Emacs.
8+
9+
;; This file is free software; you can redistribute it and/or modify
10+
;; it under the terms of the GNU General Public License as published by
11+
;; the Free Software Foundation; either version 3 of the License, or
12+
;; (at your option) any later version.
13+
14+
;; This file is distributed in the hope that it will be useful,
15+
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
;; GNU General Public License for more details.
18+
19+
;; You should have received a copy of the GNU General Public License
20+
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
22+
;;; Commentary:
23+
24+
;; Simple GHC-centric compilation sub-mode; see info node
25+
;; `(haskell-mode)compilation' for more information
26+
27+
;;; Code:
28+
29+
(require 'compile)
30+
(require 'haskell-cabal)
31+
32+
(defgroup haskell-compile nil
33+
"Settings for Haskell compilation mode"
34+
:link '(custom-manual "(haskell-mode)compilation")
35+
:group 'haskell)
36+
37+
(defcustom haskell-compile-cabal-build-command
38+
"cd %s; cabal build --ghc-option=-ferror-spans"
39+
"Default build command to use for `haskell-cabal-build' when a cabal file is detected.
40+
The `%s' placeholder is replaced by the cabal package top folder."
41+
:group 'haskell-compile
42+
:type 'string)
43+
44+
(defcustom haskell-compile-cabal-build-alt-command
45+
"cd %s; cabal clean -s; cabal build --ghc-option=-ferror-spans"
46+
"Alternative build command to use when `haskell-cabal-build' is called with a negative prefix argument.
47+
The `%s' placeholder is replaced by the cabal package top folder."
48+
:group 'haskell-compile
49+
:type 'string)
50+
51+
(defcustom haskell-compile-command
52+
"ghc -Wall -ferror-spans -fforce-recomp -c %s"
53+
"Default build command to use for `haskell-cabal-build' when no cabal file is detected.
54+
The `%s' placeholder is replaced by the current buffer's filename."
55+
:group 'haskell-compile
56+
:type 'string)
57+
58+
(defcustom haskell-compile-ghc-filter-linker-messages
59+
t
60+
"Filter out unremarkable \"Loading package...\" linker messages during compilation."
61+
:group 'haskell-compile
62+
:type 'boolean)
63+
64+
(defconst haskell-compilation-error-regexp-alist
65+
`((,(concat
66+
"^\\(?1:[^ \t\r\n]+?\\):"
67+
"\\(?:"
68+
"\\(?2:[0-9]+\\):\\(?4:[0-9]+\\)\\(?:-\\(?5:[0-9]+\\)\\)?" ;; "121:1" & "12:3-5"
69+
"\\|"
70+
"(\\(?2:[0-9]+\\),\\(?4:[0-9]+\\))-(\\(?3:[0-9]+\\),\\(?5:[0-9]+\\))" ;; "(289,5)-(291,36)"
71+
"\\)"
72+
":\\(?6: Warning:\\)?")
73+
1 (2 . 3) (4 . 5) (6 . nil)) ;; error/warning locus
74+
75+
;; multiple declarations
76+
("^ \\(?:Declared at:\\| \\) \\(?1:[^ \t\r\n]+\\):\\(?2:[0-9]+\\):\\(?4:[0-9]+\\)$"
77+
1 2 4 0) ;; info locus
78+
79+
;; this is the weakest pattern as it's subject to line wrapping et al.
80+
(" at \\(?1:[^ \t\r\n]+\\):\\(?2:[0-9]+\\):\\(?4:[0-9]+\\)\\(?:-\\(?5:[0-9]+\\)\\)?[)]?$"
81+
1 2 (4 . 5) 0)) ;; info locus
82+
"Regexps used for matching GHC compile messages.
83+
See `compilation-error-regexp-alist' for semantics.")
84+
85+
(defvar haskell-compilation-mode-map
86+
(let ((map (make-sparse-keymap)))
87+
(set-keymap-parent map compilation-mode-map))
88+
"Keymap for `haskell-compilation-mode' buffers.
89+
This is a child of `compilation-mode-map'.")
90+
91+
(defun haskell-compilation-filter-hook ()
92+
"Local `compilation-filter-hook' for `haskell-compilation-mode'."
93+
94+
(when haskell-compile-ghc-filter-linker-messages
95+
(delete-matching-lines "^Loading package [^ \t\r\n]+ [.]+ linking [.]+ done\\.$"
96+
(save-excursion (goto-char compilation-filter-start)
97+
(line-beginning-position))
98+
(point))))
99+
100+
(define-compilation-mode haskell-compilation-mode "HsCompilation"
101+
"Haskell/GHC specific `compilation-mode' derivative.
102+
This mode provides support for GHC 7.[46]'s compile
103+
messages. Specifically, also the `-ferror-spans` source location
104+
format is supported, as well as info-locations within compile
105+
messages pointing to additional source locations.
106+
107+
See Info node `(haskell-mode)compilation' for more details."
108+
(set (make-local-variable 'compilation-error-regexp-alist)
109+
haskell-compilation-error-regexp-alist)
110+
111+
(add-hook 'compilation-filter-hook
112+
'haskell-compilation-filter-hook nil t)
113+
)
114+
115+
;;;###autoload
116+
(defun haskell-compile (&optional edit-command)
117+
"Compile the Haskell program including the current buffer.
118+
Tries to locate the next cabal description in current or parent
119+
folders via `haskell-cabal-find-dir' and if found, invoke
120+
`haskell-compile-cabal-build-command' from the cabal package root
121+
folder. If no cabal package could be detected,
122+
`haskell-compile-command' is used instead.
123+
124+
If prefix argument EDIT-COMMAND is non-nil (and not a negative
125+
prefix `-'), `haskell-compile' prompts for custom compile
126+
command.
127+
128+
If EDIT-COMMAND contains the negative prefix argument `-',
129+
`haskell-compile' calls the alternative command defined in
130+
`haskell-compile-cabal-build-alt-command' if a cabal package was
131+
detected.
132+
133+
`haskell-compile' uses `haskell-compilation-mode' which is
134+
derived from `compilation-mode'. See Info
135+
node `(haskell-mode)compilation' for more details."
136+
(interactive "P")
137+
(save-some-buffers (not compilation-ask-about-save)
138+
compilation-save-buffers-predicate)
139+
(let* ((cabdir (haskell-cabal-find-dir))
140+
(command1 (if (eq edit-command '-)
141+
haskell-compile-cabal-build-alt-command
142+
haskell-compile-cabal-build-command))
143+
(srcname (buffer-file-name))
144+
(command (if cabdir
145+
(format command1 cabdir)
146+
(if (and srcname (derived-mode-p 'haskell-mode))
147+
(format haskell-compile-command srcname)
148+
command1))))
149+
(when (and edit-command (not (eq edit-command '-)))
150+
(setq command (compilation-read-command command)))
151+
152+
(compilation-start command 'haskell-compilation-mode)))
153+
154+
(provide 'haskell-compile)
155+
;;; haskell-compile.el ends here

haskell-mode.el

+1-1
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ sure all haskell customize definitions have been loaded."
198198
(interactive)
199199
;; make sure all modules with (defcustom ...)s are loaded
200200
(mapc 'require
201-
'(haskell-checkers haskell-doc haskell-font-lock haskell-indentation haskell-indent haskell-interactive-mode haskell-menu haskell-process haskell-yas inf-haskell))
201+
'(haskell-checkers haskell-compile haskell-doc haskell-font-lock haskell-indentation haskell-indent haskell-interactive-mode haskell-menu haskell-process haskell-yas inf-haskell))
202202
(customize-browse 'haskell))
203203

204204
;; Are we looking at a literate script?

haskell-mode.texi

+40
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ Haskell Mode is an Haskell development Environment for GNU Emacs version 23 or l
5757
* Editing Haskell Code::
5858
* Unicode::
5959
* Indentation::
60+
* Compilation::
6061
* haskell-decl-scan-mode::
6162
* inferior-haskell-mode::
6263
* haskell-interactive-mode::
@@ -280,6 +281,45 @@ In order to enable @code{which-function-mode} for Haskell buffers you need to ad
280281
'(add-to-list 'which-func-modes 'haskell-mode))
281282
@end lisp
282283

284+
@node Compilation
285+
@chapter Compilation
286+
287+
@findex haskell-compile
288+
289+
Haskell mode comes equipped with a specialized @dfn{Compilation mode} tailored to GHC's compiler messages with optional support for Cabal projects. @xref{Compilation Mode,,,emacs}, for more information about the basic commands provided by the Compilation mode which are available in the Haskell compilation sub-mode as well. The additional features provided compared to Emacs' basic Compilation mode are:
290+
291+
@itemize
292+
@item
293+
DWIM-style auto-detection of compile command (including support for CABAL projects)
294+
@item
295+
Support for GHC's compile messages and recognizing error, warning and info source locations (including @option{-ferror-spans} syntax)
296+
@item
297+
Support for filtering out GHC's uninteresting @samp{Loading package...} linker messages
298+
@end itemize
299+
300+
In order to use it, invoke the @code{haskell-compile} command instead of @code{compile} as you would for the ordinary Compilation mode. It's recommended to bind @code{haskell-compile} to a convenient key binding. For instance, you can add the following to your Emacs initialization to bind @code{haskell-compile} to @kbd{C-c C-c}.
301+
302+
@lisp
303+
(eval-after-load "haskell-mode"
304+
'(define-key haskell-mode-map (kbd "C-c C-c") 'haskell-compile))
305+
306+
(eval-after-load "haskell-cabal"
307+
'(define-key haskell-cabal-mode-map (kbd "C-c C-c") 'haskell-compile))
308+
@end lisp
309+
310+
@noindent
311+
The following description assumes that @code{haskell-compile} has been bound to @kbd{C-c C-c}.
312+
313+
@vindex haskell-compile-cabal-build-command
314+
@vindex haskell-compile-cabal-build-command-alt
315+
@vindex haskell-compile-command
316+
317+
When invoked, @code{haskell-compile} tries to guess how to compile the Haskell program your currently visited buffer belongs to, by searching for a @file{.cabal} file in the current of enclosing parent folders. If a @file{.cabal} file was found, the command defined in the @code{haskell-compile-cabal-build-command} option is used. Moreover, when requesting to compile a @file{.cabal}-file is detected and a negative prefix argument (e.g. @kbd{C-- C-c C-c}) was given, the alternative @code{haskell-compile-cabal-build-command-alt} is invoked. By default, @code{haskell-compile-cabal-build-command-alt} contains a @samp{cabal clean -s} command in order to force a full rebuild.
318+
319+
Otherwise if no @file{.cabal} could be found, a single-module compilation is assumed and @code{haskell-compile-command} is used (@emph{if} the currently visited buffer contains Haskell source code).
320+
321+
You can also inspect and modify the compile command to be invoked temporarily by invoking @code{haskell-compile} with a prefix argument (e.g. @kbd{C-u C-c C-c}). If later-on you want to recompile using the same customized compile command, invoke @code{recompile} (bound to @kbd{g}) inside the @samp{*haskell-compilation*} buffer.
322+
283323
@node inferior-haskell-mode
284324
@chapter @code{inferior-haskell-mode}
285325

0 commit comments

Comments
 (0)