|
|
- ;;; 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
|