|
;;; inf-haskell.el --- Interaction with an inferior Haskell process -*- lexical-binding: t -*-
|
|
|
|
;; Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc.
|
|
;; Copyright (C) 2017 Vasantha Ganesh Kanniappan <vasanthaganesh.k@tuta.io>
|
|
|
|
;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
|
|
;; Keywords: Haskell
|
|
|
|
;; 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, 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:
|
|
|
|
;; A major mode for the buffer that holds the inferior process
|
|
|
|
;; Todo:
|
|
|
|
;; - Check out Shim for ideas.
|
|
;; - i-h-load-buffer and i-h-send-region.
|
|
|
|
;;; Code:
|
|
|
|
(require 'comint)
|
|
(require 'shell) ; For directory tracking.
|
|
(require 'etags)
|
|
(require 'haskell-compat)
|
|
(require 'compile)
|
|
(require 'haskell-decl-scan)
|
|
(require 'haskell-cabal)
|
|
(require 'haskell-customize)
|
|
(require 'cl-lib)
|
|
(require 'haskell-string)
|
|
|
|
;;;###autoload
|
|
(defgroup inferior-haskell nil
|
|
"Settings for REPL interaction via `inferior-haskell-mode'"
|
|
:link '(custom-manual "(haskell-mode)inferior-haskell-mode")
|
|
:prefix "inferior-haskell-"
|
|
:prefix "haskell-"
|
|
:group 'haskell)
|
|
|
|
(defcustom inferior-haskell-hook nil
|
|
"The hook that is called after starting inf-haskell."
|
|
:type 'hook)
|
|
|
|
(defun haskell-program-name-with-args ()
|
|
"Return the command with the arguments to start the repl based on the
|
|
directory structure."
|
|
(cl-ecase (haskell-process-type)
|
|
('ghci (cond ((eq system-type 'cygwin) (nconc "ghcii.sh"
|
|
haskell-process-args-ghci))
|
|
(t (append
|
|
(if (listp haskell-process-path-ghci)
|
|
haskell-process-path-ghci
|
|
(list haskell-process-path-ghci))
|
|
haskell-process-args-ghci))))
|
|
('cabal-repl (nconc `(,haskell-process-path-cabal
|
|
"repl")
|
|
haskell-process-args-cabal-repl))
|
|
('stack-ghci (nconc `(,haskell-process-path-stack
|
|
"ghci")
|
|
haskell-process-args-stack-ghci))))
|
|
|
|
(defconst inferior-haskell-info-xref-re
|
|
"-- Defined at \\(.+\\):\\([0-9]+\\):\\([0-9]+\\)\\(?:-\\([0-9]+\\)\\)?$")
|
|
|
|
(defconst inferior-haskell-module-re
|
|
"-- Defined in \\(.+\\)$"
|
|
"Regular expression for matching module names in :info.")
|
|
|
|
(defvar inferior-haskell-multiline-prompt-re
|
|
"^\\*?[[:upper:]][\\._[:alnum:]]*\\(?: \\*?[[:upper:]][\\._[:alnum:]]*\\)*| "
|
|
"Regular expression for matching multiline prompt (the one inside :{ ... :} blocks).")
|
|
|
|
(defconst inferior-haskell-error-regexp-alist
|
|
`(;; Format of error messages used by GHCi.
|
|
("^\\(.+?\\):\\([0-9]+\\):\\(\\([0-9]+\\):\\)?\\( \\|\n *\\)\\([Ww]arning\\)?"
|
|
1 2 4 ,@(if (fboundp 'compilation-fake-loc)
|
|
'((6) nil (5 '(face nil font-lock-multiline t)))))
|
|
;; Runtime exceptions, from ghci.
|
|
("^\\*\\*\\* Exception: \\(.+?\\):(\\([0-9]+\\),\\([0-9]+\\))-(\\([0-9]+\\),\\([0-9]+\\)): .*"
|
|
1 ,@(if (fboundp 'compilation-fake-loc) '((2 . 4) (3 . 5)) '(2 3)))
|
|
;; GHCi uses two different forms for line/col ranges, depending on
|
|
;; whether it's all on the same line or not :-( In Emacs-23, I could use
|
|
;; explicitly numbered subgroups to merge the two patterns.
|
|
("^\\*\\*\\* Exception: \\(.+?\\):\\([0-9]+\\):\\([0-9]+\\)-\\([0-9]+\\): .*"
|
|
1 2 ,(if (fboundp 'compilation-fake-loc) '(3 . 4) 3))
|
|
;; Info messages. Not errors per se.
|
|
,@(when (fboundp 'compilation-fake-loc)
|
|
`(;; Other GHCi patterns used in type errors.
|
|
("^[ \t]+at \\(.+\\):\\([0-9]+\\):\\([0-9]+\\)-\\([0-9]+\\)$"
|
|
1 2 (3 . 4) 0)
|
|
;; Foo.hs:318:80:
|
|
;; Ambiguous occurrence `Bar'
|
|
;; It could refer to either `Bar', defined at Zork.hs:311:5
|
|
;; or `Bar', imported from Bars at Frob.hs:32:0-16
|
|
;; (defined at Location.hs:97:5)
|
|
("[ (]defined at \\(.+\\):\\([0-9]+\\):\\([0-9]+\\))?$" 1 2 3 0)
|
|
("imported from .* at \\(.+\\):\\([0-9]+\\):\\([0-9]+\\)-\\([0-9]+\\)$"
|
|
1 2 (3 . 4) 0)
|
|
;; Info xrefs.
|
|
(,inferior-haskell-info-xref-re 1 2 (3 . 4) 0))))
|
|
"Regexps for error messages generated by inferior Haskell processes.
|
|
The format should be the same as for `compilation-error-regexp-alist'.")
|
|
|
|
(defconst haskell-prompt-regexp
|
|
;; Why the backslash in [\\._[:alnum:]]?
|
|
"^\\*?[[:upper:]][\\._[:alnum:]]*\\(?: \\*?[[:upper:]][\\._[:alnum:]]*\\)*\\( λ\\)?> \\|^λ?> $")
|
|
|
|
;;; TODO
|
|
;;; -> Make font lock work for strings, directories, hyperlinks
|
|
;;; -> Make font lock work for key words???
|
|
|
|
(defvaralias 'inferior-haskell-mode-map 'inf-haskell-map)
|
|
|
|
(defvar inf-haskell-map
|
|
(let ((map (make-sparse-keymap)))
|
|
(define-key map "\C-c\C-d" 'comint-kill-subjob)
|
|
map))
|
|
|
|
(define-derived-mode inferior-haskell-mode comint-mode "Inf-Haskell"
|
|
"Major mode for interacting with an inferior Haskell process."
|
|
:group 'inferior-haskell
|
|
(setq-local comint-prompt-regexp haskell-prompt-regexp)
|
|
|
|
(setq-local paragraph-start haskell-prompt-regexp)
|
|
|
|
(setq-local comint-input-autoexpand nil)
|
|
(setq-local comint-prompt-read-only t)
|
|
|
|
;; Setup directory tracking.
|
|
(setq-local shell-cd-regexp ":cd")
|
|
(condition-case nil
|
|
(shell-dirtrack-mode 1)
|
|
(error ;The minor mode function may not exist or not accept an arg.
|
|
(setq-local shell-dirtrackp t)
|
|
(add-hook 'comint-input-filter-functions 'shell-directory-tracker
|
|
nil 'local)))
|
|
|
|
;; Setup `compile' support so you can just use C-x ` and friends.
|
|
(setq-local compilation-error-regexp-alist inferior-haskell-error-regexp-alist)
|
|
(setq-local compilation-first-column 0) ;GHCI counts from 0.
|
|
(if (and (not (boundp 'minor-mode-overriding-map-alist))
|
|
(fboundp 'compilation-shell-minor-mode))
|
|
;; If we can't remove compilation-minor-mode bindings, at least try to
|
|
;; use compilation-shell-minor-mode, so there are fewer
|
|
;; annoying bindings.
|
|
(compilation-shell-minor-mode 1)
|
|
;; Else just use compilation-minor-mode but without its bindings because
|
|
;; things like mouse-2 are simply too annoying.
|
|
(compilation-minor-mode 1)
|
|
(let ((map (make-sparse-keymap)))
|
|
(dolist (keys '([menu-bar] [follow-link]))
|
|
;; Preserve some of the bindings.
|
|
(define-key map keys (lookup-key compilation-minor-mode-map keys)))
|
|
(add-to-list 'minor-mode-overriding-map-alist
|
|
(cons 'compilation-minor-mode map))))
|
|
(add-hook 'inferior-haskell-hook 'inferior-haskell-init))
|
|
|
|
(defvar inferior-haskell-buffer nil
|
|
"The buffer in which the inferior process is running.")
|
|
|
|
(defun inferior-haskell-start-process ()
|
|
"Start an inferior haskell process.
|
|
With universal prefix \\[universal-argument], prompts for a COMMAND,
|
|
otherwise uses `haskell-program-name-with-args'.
|
|
It runs the hook `inferior-haskell-hook' after starting the process and
|
|
setting up the inferior-haskell buffer."
|
|
(let ((command (haskell-program-name-with-args)))
|
|
(when inferior-haskell-root-dir
|
|
(setq default-directory inferior-haskell-root-dir))
|
|
(setq inferior-haskell-buffer
|
|
(apply 'make-comint "haskell" (car command) nil (cdr command)))
|
|
(with-current-buffer inferior-haskell-buffer
|
|
(inferior-haskell-mode)
|
|
(run-hooks 'inferior-haskell-hook))))
|
|
|
|
(defun inferior-haskell-process ()
|
|
"Restart if not present."
|
|
(cond ((and (buffer-live-p inferior-haskell-buffer)
|
|
(comint-check-proc inferior-haskell-buffer))
|
|
(get-buffer-process inferior-haskell-buffer))
|
|
(t (inferior-haskell-start-process)
|
|
(inferior-haskell-process))))
|
|
|
|
;;;###autoload
|
|
(defalias 'run-haskell 'switch-to-haskell)
|
|
;;;###autoload
|
|
(defun switch-to-haskell ()
|
|
"Show the inferior-haskell buffer. Start the process if needed."
|
|
(interactive)
|
|
(let ((proc (inferior-haskell-process)))
|
|
(pop-to-buffer-same-window (process-buffer proc))))
|
|
|
|
(defvar inferior-haskell-result-history nil)
|
|
|
|
(defvar haskell-next-input ""
|
|
"This is a temporary variable to store the intermediate results while
|
|
`accecpt-process-output' with `haskell-extract-exp'")
|
|
|
|
(defun haskell-extract-exp (str)
|
|
(setq haskell-next-input (concat haskell-next-input str))
|
|
(if (with-temp-buffer
|
|
(insert haskell-next-input)
|
|
(re-search-backward haskell-prompt-regexp nil t 1))
|
|
(progn
|
|
(push (substring haskell-next-input
|
|
0
|
|
(1- (with-temp-buffer
|
|
(insert haskell-next-input)
|
|
(re-search-backward haskell-prompt-regexp nil t 1))))
|
|
inferior-haskell-result-history)
|
|
(setq haskell-next-input ""))
|
|
""))
|
|
|
|
(defun inferior-haskell-no-result-return (strg)
|
|
(let ((proc (inferior-haskell-process)))
|
|
(with-local-quit
|
|
(progn
|
|
(add-to-list 'comint-preoutput-filter-functions
|
|
(lambda (output)
|
|
(haskell-extract-exp output)))
|
|
(process-send-string proc strg)
|
|
(accept-process-output proc)
|
|
(sit-for 0.1)
|
|
(setq comint-preoutput-filter-functions nil)))))
|
|
|
|
(defun inferior-haskell-get-result (inf-expr)
|
|
"Submit the expression `inf-expr' to ghci and read the result."
|
|
(let* ((times 5))
|
|
(inferior-haskell-no-result-return (concat inf-expr "\n"))
|
|
(while (and (> times 0)
|
|
(not (stringp (car inferior-haskell-result-history))))
|
|
(setq times (1- times))
|
|
(inferior-haskell-no-result-return (concat inf-expr "\n")))
|
|
(haskell-string-chomp (car inferior-haskell-result-history))))
|
|
|
|
(defun inferior-haskell-init ()
|
|
"The first thing run while initalizing inferior-haskell-buffer"
|
|
(with-local-quit
|
|
(with-current-buffer inferior-haskell-buffer
|
|
(process-send-string (inferior-haskell-process) "\n")
|
|
(accept-process-output (inferior-haskell-process))
|
|
(sit-for 0.1))))
|
|
|
|
(defvar haskell-set+c-p nil
|
|
"t if `:set +c` else nil")
|
|
|
|
(defun haskell-set+c ()
|
|
"set `:set +c` is not already set"
|
|
(if (not haskell-set+c-p)
|
|
(inferior-haskell-get-result ":set +c")))
|
|
|
|
(provide 'inf-haskell)
|
|
|
|
;;; inf-haskell.el ends here
|