|
;;; haskell-compile.el --- Haskell/GHC compilation sub-mode -*- lexical-binding: t -*-
|
|
|
|
;; Copyright (C) 2013 Herbert Valerio Riedel
|
|
|
|
;; Author: Herbert Valerio Riedel <hvr@gnu.org>
|
|
|
|
;; This file is not part of GNU Emacs.
|
|
|
|
;; This file is free software; you can redistribute it and/or modify
|
|
;; it under the terms of the GNU General Public License as published by
|
|
;; the Free Software Foundation; either version 3 of the License, or
|
|
;; (at your option) any later version.
|
|
|
|
;; This file is distributed in the hope that it will be useful,
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
;; GNU General Public License for more details.
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
;;; Commentary:
|
|
|
|
;; Simple GHC-centric compilation sub-mode; see info node
|
|
;; `(haskell-mode)compilation' for more information
|
|
|
|
;;; Code:
|
|
|
|
(require 'compile)
|
|
(require 'haskell-cabal)
|
|
(require 'ansi-color)
|
|
(eval-when-compile (require 'subr-x))
|
|
|
|
;;;###autoload
|
|
(defgroup haskell-compile nil
|
|
"Settings for Haskell compilation mode"
|
|
:link '(custom-manual "(haskell-mode)compilation")
|
|
:group 'haskell)
|
|
|
|
(defcustom haskell-compile-cabal-build-command
|
|
"cabal build --ghc-option=-ferror-spans"
|
|
"Default build command to use for `haskell-cabal-build' when a cabal file is detected.
|
|
For legacy compat, `%s' is replaced by the cabal package top folder."
|
|
:group 'haskell-compile
|
|
:type 'string)
|
|
|
|
(defcustom haskell-compile-cabal-build-alt-command
|
|
"cabal clean -s && cabal build --ghc-option=-ferror-spans"
|
|
"Alternative build command to use when `haskell-cabal-build' is called with a negative prefix argument.
|
|
For legacy compat, `%s' is replaced by the cabal package top folder."
|
|
:group 'haskell-compile
|
|
:type 'string)
|
|
|
|
(defcustom haskell-compile-stack-build-command
|
|
"stack build --fast"
|
|
"Default build command to use for `haskell-stack-build' when a stack file is detected.
|
|
For legacy compat, `%s' is replaced by the stack package top folder."
|
|
:group 'haskell-compile
|
|
:type 'string)
|
|
|
|
(defcustom haskell-compile-stack-build-alt-command
|
|
"stack clean && stack build --fast"
|
|
"Alternative build command to use when `haskell-stack-build' is called with a negative prefix argument.
|
|
For legacy compat, `%s' is replaced by the stack package top folder."
|
|
:group 'haskell-compile
|
|
:type 'string)
|
|
|
|
(defcustom haskell-compile-command
|
|
"ghc -Wall -ferror-spans -fforce-recomp -c %s"
|
|
"Default build command to use for `haskell-cabal-build' when no cabal file is detected.
|
|
The `%s' placeholder is replaced by the current buffer's filename."
|
|
:group 'haskell-compile
|
|
:type 'string)
|
|
|
|
(defcustom haskell-compile-ghc-filter-linker-messages
|
|
t
|
|
"Filter out unremarkable \"Loading package...\" linker messages during compilation."
|
|
:group 'haskell-compile
|
|
:type 'boolean)
|
|
|
|
(defcustom haskell-compile-ignore-cabal nil
|
|
"Ignore cabal build definitions files for this buffer when detecting the build tool."
|
|
:group 'haskell-compile
|
|
:type 'boolean)
|
|
(make-variable-buffer-local 'haskell-compile-ignore-cabal)
|
|
(put 'haskell-compile-ignore-cabal 'safe-local-variable #'booleanp)
|
|
|
|
(defconst haskell-compilation-error-regexp-alist
|
|
`((,(concat
|
|
"^ *\\(?1:[^\t\r\n]+?\\):"
|
|
"\\(?:"
|
|
"\\(?2:[0-9]+\\):\\(?4:[0-9]+\\)\\(?:-\\(?5:[0-9]+\\)\\)?" ;; "121:1" & "12:3-5"
|
|
"\\|"
|
|
"(\\(?2:[0-9]+\\),\\(?4:[0-9]+\\))-(\\(?3:[0-9]+\\),\\(?5:[0-9]+\\))" ;; "(289,5)-(291,36)"
|
|
"\\)"
|
|
":\\(?6:\n?[ \t]+[Ww]arning:\\)?")
|
|
1 (2 . 3) (4 . 5) (6 . nil)) ;; error/warning locus
|
|
|
|
;; multiple declarations
|
|
("^ \\(?:Declared at:\\| \\) \\(?1:[^ \t\r\n]+\\):\\(?2:[0-9]+\\):\\(?4:[0-9]+\\)$"
|
|
1 2 4 0) ;; info locus
|
|
|
|
;; failed tasty tests
|
|
(".*error, called at \\(.*\\.hs\\):\\([0-9]+\\):\\([0-9]+\\) in .*" 1 2 3 2 1)
|
|
(" +\\(.*\\.hs\\):\\([0-9]+\\):$" 1 2 nil 2 1)
|
|
|
|
;; this is the weakest pattern as it's subject to line wrapping et al.
|
|
(" at \\(?1:[^ \t\r\n]+\\):\\(?2:[0-9]+\\):\\(?4:[0-9]+\\)\\(?:-\\(?5:[0-9]+\\)\\)?[)]?$"
|
|
1 2 (4 . 5) 0)) ;; info locus
|
|
"Regexps used for matching GHC compile messages.
|
|
See `compilation-error-regexp-alist' for semantics.")
|
|
|
|
(defvar haskell-compilation-mode-map
|
|
(let ((map (make-sparse-keymap)))
|
|
(set-keymap-parent map compilation-mode-map))
|
|
"Keymap for `haskell-compilation-mode' buffers.
|
|
This is a child of `compilation-mode-map'.")
|
|
|
|
(defun haskell-compilation-filter-hook ()
|
|
"Local `compilation-filter-hook' for `haskell-compilation-mode'."
|
|
|
|
(when haskell-compile-ghc-filter-linker-messages
|
|
(delete-matching-lines "^ *Loading package [^ \t\r\n]+ [.]+ linking [.]+ done\\.$"
|
|
(save-excursion (goto-char compilation-filter-start)
|
|
(line-beginning-position))
|
|
(point)))
|
|
|
|
(let ((inhibit-read-only t))
|
|
(ansi-color-apply-on-region compilation-filter-start (point-max))))
|
|
|
|
(define-compilation-mode haskell-compilation-mode "HsCompilation"
|
|
"Haskell/GHC specific `compilation-mode' derivative.
|
|
This mode provides support for GHC 7.[46]'s compile
|
|
messages. Specifically, also the `-ferror-spans` source location
|
|
format is supported, as well as info-locations within compile
|
|
messages pointing to additional source locations."
|
|
(setq-local compilation-error-regexp-alist
|
|
haskell-compilation-error-regexp-alist)
|
|
|
|
(add-hook 'compilation-filter-hook
|
|
'haskell-compilation-filter-hook nil t)
|
|
)
|
|
|
|
;;;###autoload
|
|
(defun haskell-compile (&optional edit-command)
|
|
"Run a compile command for the current Haskell buffer.
|
|
|
|
Locates stack or cabal definitions and, if found, invokes the
|
|
default build command for that build tool. Cabal is preferred
|
|
but may be ignored with `haskell-compile-ignore-cabal'.
|
|
|
|
If prefix argument EDIT-COMMAND is non-nil (and not a negative
|
|
prefix `-'), prompt for a custom compile command.
|
|
|
|
If EDIT-COMMAND contains the negative prefix argument `-', call
|
|
the alternative command defined in
|
|
`haskell-compile-stack-build-alt-command' /
|
|
`haskell-compile-cabal-build-alt-command'.
|
|
|
|
If there is no prefix argument, the most recent custom compile
|
|
command is used, falling back to
|
|
`haskell-compile-stack-build-command' for stack builds
|
|
`haskell-compile-cabal-build-command' for cabal builds, and
|
|
`haskell-compile-command' otherwise.
|
|
|
|
'% characters in the `-command' templates are replaced by the
|
|
base directory for build tools, or the current buffer for
|
|
`haskell-compile-command'."
|
|
(interactive "P")
|
|
(save-some-buffers (not compilation-ask-about-save)
|
|
compilation-save-buffers-predicate)
|
|
(let ((cabaldir (and
|
|
(not haskell-compile-ignore-cabal)
|
|
(or (haskell-cabal-find-dir)
|
|
(locate-dominating-file default-directory "cabal.project")
|
|
(locate-dominating-file default-directory "cabal.project.local")))))
|
|
(if cabaldir
|
|
(haskell--compile cabaldir edit-command
|
|
'haskell--compile-cabal-last
|
|
haskell-compile-cabal-build-command
|
|
haskell-compile-cabal-build-alt-command)
|
|
(let ((stackdir (and haskell-compile-ignore-cabal
|
|
(locate-dominating-file default-directory "stack.yaml"))))
|
|
(if stackdir
|
|
(haskell--compile stackdir edit-command
|
|
'haskell--compile-stack-last
|
|
haskell-compile-stack-build-command
|
|
haskell-compile-stack-build-alt-command)
|
|
(let ((srcfile (buffer-file-name)))
|
|
(haskell--compile srcfile edit-command
|
|
'haskell--compile-ghc-last
|
|
haskell-compile-command
|
|
haskell-compile-command)))))))
|
|
|
|
(defvar haskell--compile-stack-last nil)
|
|
(defvar haskell--compile-cabal-last nil)
|
|
(defvar haskell--compile-ghc-last nil)
|
|
|
|
(defun haskell--directory-name-p (name)
|
|
"Version of `directory-name-p', which is unavailable in Emacs 24.4."
|
|
(string= (file-name-as-directory name) name))
|
|
|
|
(defun haskell--compile (dir-or-file edit last-sym fallback alt)
|
|
(let* ((default (or (symbol-value last-sym) fallback))
|
|
(template (cond
|
|
((null edit) default)
|
|
((< edit 0) alt)
|
|
(t (compilation-read-command default))))
|
|
(command (format template dir-or-file))
|
|
(dir (if (haskell--directory-name-p dir-or-file)
|
|
dir-or-file
|
|
default-directory))
|
|
(name (if (haskell--directory-name-p dir-or-file)
|
|
(file-name-base (directory-file-name dir-or-file))
|
|
(file-name-nondirectory dir-or-file))))
|
|
(unless (eq edit'-)
|
|
(set last-sym template))
|
|
(let ((default-directory dir))
|
|
(compilation-start
|
|
command
|
|
'haskell-compilation-mode
|
|
(lambda (mode) (format "*%s* <%s>" mode name))))))
|
|
|
|
(provide 'haskell-compile)
|
|
;;; haskell-compile.el ends here
|