|
|
- ;;; haskell-interactive-mode.el --- The interactive Haskell mode -*- lexical-binding: t -*-
-
- ;; Copyright © 2011-2012 Chris Done
- ;; 2016 Arthur Fayzrakhmanov
-
- ;; Author: Chris Done <chrisdone@gmail.com>
-
- ;; 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, 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 GNU Emacs; see the file COPYING. If not, write to
- ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- ;; Boston, MA 02110-1301, USA.
-
- ;;; Commentary:
-
- ;;; Todo:
-
- ;;; Code:
-
- (require 'haskell-mode)
- (require 'haskell-compile)
- (require 'haskell-process)
- (require 'haskell-session)
- (require 'haskell-font-lock)
- (require 'haskell-presentation-mode)
- (require 'haskell-utils)
- (require 'haskell-string)
- (require 'ansi-color)
- (require 'cl-lib)
- (require 'etags)
-
- (defvar-local haskell-interactive-mode-history-index 0)
-
- (defvar-local haskell-interactive-mode-history (list))
-
- (defvar-local haskell-interactive-mode-old-prompt-start nil
- "Mark used for the old beginning of the prompt.")
-
- (defun haskell-interactive-prompt-regex ()
- "Generate a regex for searching for any occurence of the prompt\
- at the beginning of the line. This should prevent any
- interference with prompts that look like haskell expressions."
- (concat "^" (regexp-quote haskell-interactive-prompt)))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;; Globals used internally
-
- (declare-function haskell-interactive-kill "haskell")
-
- (defvar haskell-interactive-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map (kbd "RET") 'haskell-interactive-mode-return)
- (define-key map (kbd "SPC") 'haskell-interactive-mode-space)
- (define-key map (kbd "C-j") 'haskell-interactive-mode-newline-indent)
- (define-key map [remap move-beginning-of-line] 'haskell-interactive-mode-bol)
- (define-key map (kbd "<home>") 'haskell-interactive-mode-beginning)
- (define-key map (kbd "C-c C-k") 'haskell-interactive-mode-clear)
- (define-key map (kbd "C-c C-c") 'haskell-process-interrupt)
- (define-key map (kbd "C-c C-f") 'next-error-follow-minor-mode)
- (define-key map (kbd "C-c C-z") 'haskell-interactive-switch-back)
- (define-key map (kbd "M-p") 'haskell-interactive-mode-history-previous)
- (define-key map (kbd "M-n") 'haskell-interactive-mode-history-next)
- (define-key map (kbd "C-c C-p") 'haskell-interactive-mode-prompt-previous)
- (define-key map (kbd "C-c C-n") 'haskell-interactive-mode-prompt-next)
- (define-key map (kbd "C-<up>") 'haskell-interactive-mode-history-previous)
- (define-key map (kbd "C-<down>") 'haskell-interactive-mode-history-next)
- (define-key map (kbd "TAB") 'haskell-interactive-mode-tab)
- (define-key map (kbd "<C-S-backspace>") 'haskell-interactive-mode-kill-whole-line)
- map)
- "Keymap used in `haskell-interactive-mode'.")
-
- (define-derived-mode haskell-interactive-mode fundamental-mode "Interactive-Haskell"
- "Interactive mode for Haskell.
-
- Key bindings:
- \\{haskell-interactive-mode-map}"
- :group 'haskell-interactive
- :syntax-table haskell-mode-syntax-table
-
- (setq haskell-interactive-mode-history (list))
- (setq haskell-interactive-mode-history-index 0)
-
- (setq next-error-function #'haskell-interactive-next-error-function)
- (add-hook 'completion-at-point-functions
- #'haskell-interactive-mode-completion-at-point-function nil t)
- (add-hook 'kill-buffer-hook #'haskell-interactive-kill nil t)
- (haskell-interactive-mode-prompt))
-
- (defvar haskell-interactive-mode-prompt-start
- nil
- "Mark used for the beginning of the prompt.")
-
- (defvar haskell-interactive-mode-result-end
- nil
- "Mark used to figure out where the end of the current result output is.
- Used to distinguish betwen user input.")
-
- (defvar-local haskell-interactive-previous-buffer nil
- "Records the buffer to which `haskell-interactive-switch-back' should jump.
- This is set by `haskell-interactive-switch', and should otherwise
- be nil.")
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;; Hooks
-
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;; Mode
-
-
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;; Faces
-
- ;;;###autoload
- (defface haskell-interactive-face-prompt
- '((t :inherit font-lock-function-name-face))
- "Face for the prompt."
- :group 'haskell-interactive)
-
- ;;;###autoload
- (defface haskell-interactive-face-prompt2
- '((t :inherit font-lock-keyword-face))
- "Face for the prompt2 in multi-line mode."
- :group 'haskell-interactive)
-
- ;;;###autoload
- (defface haskell-interactive-face-compile-error
- '((t :inherit compilation-error))
- "Face for compile errors."
- :group 'haskell-interactive)
-
- ;;;###autoload
- (defface haskell-interactive-face-compile-warning
- '((t :inherit compilation-warning))
- "Face for compiler warnings."
- :group 'haskell-interactive)
-
- ;;;###autoload
- (defface haskell-interactive-face-result
- '((t :inherit font-lock-string-face))
- "Face for the result."
- :group 'haskell-interactive)
-
- ;;;###autoload
- (defface haskell-interactive-face-garbage
- '((t :inherit font-lock-string-face))
- "Face for trailing garbage after a command has completed."
- :group 'haskell-interactive)
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;; Actions
-
- (defun haskell-interactive-mode-newline-indent ()
- "Make newline and indent."
- (interactive)
- (newline)
- (indent-to (length haskell-interactive-prompt))
- (indent-relative))
-
- (defun haskell-interactive-mode-kill-whole-line ()
- "Kill the whole REPL line."
- (interactive)
- (kill-region haskell-interactive-mode-prompt-start
- (line-end-position)))
-
- (defun haskell-interactive-switch-back ()
- "Switch back to the buffer from which this interactive buffer was reached."
- (interactive)
- (if haskell-interactive-previous-buffer
- (switch-to-buffer-other-window haskell-interactive-previous-buffer)
- (message "No previous buffer.")))
-
- (defun haskell-interactive-copy-to-prompt ()
- "Copy the current line to the prompt, overwriting the current prompt."
- (interactive)
- (let ((l (buffer-substring-no-properties (line-beginning-position)
- (line-end-position))))
- ;; If it looks like the prompt is at the start of the line, chop
- ;; it off.
- (when (and (>= (length l) (length haskell-interactive-prompt))
- (string= (substring l 0 (length haskell-interactive-prompt))
- haskell-interactive-prompt))
- (setq l (substring l (length haskell-interactive-prompt))))
-
- (haskell-interactive-mode-set-prompt l)))
-
- (defun haskell-interactive-mode-space (n)
- "Handle the space key."
- (interactive "p")
- (if (and (bound-and-true-p god-local-mode)
- (fboundp 'god-mode-self-insert))
- (call-interactively 'god-mode-self-insert)
- (if (haskell-interactive-at-compile-message)
- (next-error-no-select 0)
- (self-insert-command n))))
-
- (defun haskell-interactive-at-prompt (&optional end-line)
- "If at prompt, return start position of user-input, otherwise return nil.
- If END-LINE is non-nil, then return non-nil when the end of line
- is at the prompt."
- (if (>= (if end-line (line-end-position) (point))
- haskell-interactive-mode-prompt-start)
- haskell-interactive-mode-prompt-start
- nil))
-
- (defun haskell-interactive-mode-bol ()
- "Go to beginning of current line, but after current prompt if any."
- (interactive)
- (let ((beg (line-beginning-position))
- (end (line-end-position)))
- (goto-char (if (>= end haskell-interactive-mode-prompt-start beg)
- haskell-interactive-mode-prompt-start
- beg))))
-
- (define-derived-mode haskell-error-mode
- special-mode "Error"
- "Major mode for viewing Haskell compile errors.")
-
- ;; (define-key haskell-error-mode-map (kbd "q") 'quit-window)
-
- (defun haskell-interactive-mode-handle-h ()
- "Handle ^H in output."
- (let ((bound (point-min))
- (inhibit-read-only t))
- (save-excursion
- (while (search-backward "\b" bound t 1)
- (save-excursion
- (forward-char)
- (let ((end (point)))
- (if (search-backward-regexp "[^\b]" bound t 1)
- (forward-char)
- (goto-char (point-min)))
- (let ((start (point)))
- (delete-region (max (- (point) (- end start))
- (point-min))
- end))))))))
-
- (defun haskell-interactive-mode-multi-line (expr)
- "If a multi-line expression EXPR has been entered, then reformat it to be:
-
- :{
- do the
- multi-liner
- expr
- :}"
- (if (not (string-match-p "\n" expr))
- expr
- (let ((pre (format "^%s" (regexp-quote haskell-interactive-prompt)))
- (lines (split-string expr "\n")))
- (cl-loop for elt on (cdr lines) do
- (setcar elt (replace-regexp-in-string pre "" (car elt))))
- ;; Temporarily set prompt2 to be empty to avoid unwanted output
- (concat ":set prompt2 \"\"\n"
- ":{\n"
- (mapconcat #'identity lines "\n")
- "\n:}\n"
- (format ":set prompt2 \"%s\"" haskell-interactive-prompt2)))))
-
- (defun haskell-interactive-mode-line-is-query (line)
- "Is LINE actually a :t/:k/:i?"
- (and (string-match "^:[itk] " line)
- t))
-
- (defun haskell-interactive-mode-beginning ()
- "Go to the start of the line."
- (interactive)
- (if (haskell-interactive-at-prompt)
- (goto-char haskell-interactive-mode-prompt-start)
- (move-beginning-of-line nil)))
-
- (defun haskell-interactive-mode-input-partial ()
- "Get the interactive mode input up to point."
- (let ((input-start (haskell-interactive-at-prompt)))
- (unless input-start
- (error "not at prompt"))
- (buffer-substring-no-properties input-start (point))))
-
- (defun haskell-interactive-mode-input ()
- "Get the interactive mode input."
- (buffer-substring-no-properties
- haskell-interactive-mode-prompt-start
- (point-max)))
-
- (defun haskell-interactive-mode-prompt (&optional session)
- "Show a prompt at the end of the REPL buffer.
- If SESSION is non-nil, use the REPL buffer associated with
- SESSION, otherwise operate on the current buffer."
- (with-current-buffer (if session
- (haskell-session-interactive-buffer session)
- (current-buffer))
- (save-excursion
- (goto-char (point-max))
- (let ((prompt (propertize haskell-interactive-prompt
- 'font-lock-face 'haskell-interactive-face-prompt
- 'prompt t
- 'read-only haskell-interactive-prompt-read-only
- 'rear-nonsticky t)))
- ;; At the time of writing, front-stickying the first char gives an error
- ;; Has unfortunate side-effect of being able to insert before the prompt
- (insert (substring prompt 0 1)
- (propertize (substring prompt 1)
- 'front-sticky t)))
- (let ((marker (setq-local haskell-interactive-mode-prompt-start (make-marker))))
- (set-marker marker (point))))
- (when (haskell-interactive-at-prompt t)
- (haskell-interactive-mode-scroll-to-bottom))))
-
- (defun haskell-interactive-mode-eval-result (session text)
- "Insert the result of an eval as plain text."
- (with-current-buffer (haskell-session-interactive-buffer session)
- (let ((at-end (eobp))
- (prop-text (propertize text
- 'font-lock-face 'haskell-interactive-face-result
- 'front-sticky t
- 'prompt t
- 'read-only haskell-interactive-mode-read-only
- 'rear-nonsticky t
- 'result t)))
- (save-excursion
- (goto-char (point-max))
- (when (string= text haskell-interactive-prompt2)
- (setq prop-text
- (propertize prop-text
- 'font-lock-face 'haskell-interactive-face-prompt2
- 'read-only haskell-interactive-prompt-read-only)))
- (insert (ansi-color-apply prop-text))
- (haskell-interactive-mode-handle-h)
- (let ((marker (setq-local haskell-interactive-mode-result-end (make-marker))))
- (set-marker marker (point))))
- (when at-end
- (haskell-interactive-mode-scroll-to-bottom)))))
-
- (defun haskell-interactive-mode-scroll-to-bottom ()
- "Scroll to bottom."
- (let ((w (get-buffer-window (current-buffer))))
- (when w
- (goto-char (point-max))
- (set-window-point w (point)))))
-
- (defun haskell-interactive-mode-compile-error (session message)
- "Echo an error."
- (haskell-interactive-mode-compile-message
- session message 'haskell-interactive-face-compile-error))
-
- (defun haskell-interactive-mode-compile-warning (session message)
- "Warning message."
- (haskell-interactive-mode-compile-message
- session message 'haskell-interactive-face-compile-warning))
-
- (defun haskell-interactive-mode-compile-message (session message type)
- "Echo a compiler warning."
- (with-current-buffer (haskell-session-interactive-buffer session)
- (setq next-error-last-buffer (current-buffer))
- (save-excursion
- (haskell-interactive-mode-goto-end-point)
- (let ((lines (string-match "^\\(.*\\)\n\\([[:unibyte:][:nonascii:]]+\\)" message)))
- (if lines
- (progn
- (insert (propertize (concat (match-string 1 message) " …\n")
- 'expandable t
- 'font-lock-face type
- 'front-sticky t
- 'read-only haskell-interactive-mode-read-only
- 'rear-nonsticky t))
- (insert (propertize (concat (match-string 2 message) "\n")
- 'collapsible t
- 'font-lock-face type
- 'front-sticky t
- 'invisible haskell-interactive-mode-hide-multi-line-errors
- 'message-length (length (match-string 2 message))
- 'read-only haskell-interactive-mode-read-only
- 'rear-nonsticky t)))
- (insert (propertize (concat message "\n")
- 'font-lock-face type
- 'front-sticky t
- 'read-only haskell-interactive-mode-read-only
- 'rear-nonsticky t)))))))
-
- (defun haskell-interactive-mode-insert (session message)
- "Echo a read only piece of text before the prompt."
- (with-current-buffer (haskell-session-interactive-buffer session)
- (save-excursion
- (haskell-interactive-mode-goto-end-point)
- (insert (propertize message
- 'front-sticky t
- 'read-only t
- 'rear-nonsticky t)))))
-
- (defun haskell-interactive-mode-goto-end-point ()
- "Go to the 'end' of the buffer (before the prompt)."
- (goto-char haskell-interactive-mode-prompt-start)
- (goto-char (line-beginning-position)))
-
- (defun haskell-interactive-mode-history-add (input)
- "Add INPUT to the history."
- (setq haskell-interactive-mode-history
- (cons ""
- (cons input
- (cl-remove-if (lambda (i) (or (string= i input) (string= i "")))
- haskell-interactive-mode-history))))
- (setq haskell-interactive-mode-history-index
- 0))
-
- (defun haskell-interactive-mode-tab ()
- "Do completion if at prompt or else try collapse/expand."
- (interactive)
- (cond
- ((haskell-interactive-at-prompt)
- (completion-at-point))
- ((get-text-property (point) 'collapsible)
- (let ((column (current-column)))
- (search-backward-regexp "^[^ ]")
- (haskell-interactive-mode-tab-expand)
- (goto-char (+ column (line-beginning-position)))))
- (t (haskell-interactive-mode-tab-expand))))
-
- (defun haskell-interactive-mode-tab-expand ()
- "Expand the rest of the message."
- (cond ((get-text-property (point) 'expandable)
- (let* ((pos (1+ (line-end-position)))
- (visibility (get-text-property pos 'invisible))
- (length (1+ (get-text-property pos 'message-length))))
- (let ((inhibit-read-only t))
- (put-text-property pos
- (+ pos length)
- 'invisible
- (not visibility)))))))
-
- (defconst haskell-interactive-mode-error-regexp
- "^\\(\\(?:[A-Z]:\\)?[^ \r\n:][^\r\n:]*\\):\\([0-9()-:]+\\):?")
-
- (defun haskell-interactive-at-compile-message ()
- "Am I on a compile message?"
- (and (not (haskell-interactive-at-prompt))
- (save-excursion
- (goto-char (line-beginning-position))
- (looking-at haskell-interactive-mode-error-regexp))))
-
- (defun haskell-interactive-mode-error-backward (&optional count)
- "Go backward to the previous error."
- (interactive)
- (search-backward-regexp haskell-interactive-mode-error-regexp nil t count))
-
- (defun haskell-interactive-mode-error-forward (&optional count)
- "Go forward to the next error, or return to the REPL."
- (interactive)
- (goto-char (line-end-position))
- (if (search-forward-regexp haskell-interactive-mode-error-regexp nil t count)
- (progn (goto-char (line-beginning-position))
- t)
- (progn (goto-char (point-max))
- nil)))
-
- (defun haskell-interactive-mode-delete-compile-messages (session &optional file-name)
- "Delete compile messages in REPL buffer.
- If FILE-NAME is non-nil, restrict to removing messages concerning
- FILE-NAME only."
- (with-current-buffer (haskell-session-interactive-buffer session)
- (save-excursion
- (goto-char (point-min))
- (when (search-forward-regexp "^Compilation failed.$" nil t 1)
- (let ((inhibit-read-only t))
- (delete-region (line-beginning-position)
- (1+ (line-end-position))))
- (goto-char (point-min)))
- (while (when (re-search-forward haskell-interactive-mode-error-regexp nil t)
- (let ((msg-file-name (match-string-no-properties 1))
- (msg-startpos (line-beginning-position)))
- ;; skip over hanging continuation message lines
- (while (progn (forward-line) (looking-at "^[ ]+")))
-
- (when (or (not file-name) (string= file-name msg-file-name))
- (let ((inhibit-read-only t))
- (set-text-properties msg-startpos (point) nil))
- (delete-region msg-startpos (point))
- ))
- t)))))
-
- ;;;###autoload
- (defun haskell-interactive-mode-reset-error (session)
- "Reset the error cursor position."
- (interactive)
- (with-current-buffer (haskell-session-interactive-buffer session)
- (haskell-interactive-mode-goto-end-point)
- (let ((mrk (point-marker)))
- (haskell-session-set session 'next-error-locus nil)
- (haskell-session-set session 'next-error-region (cons mrk (copy-marker mrk t))))
- (goto-char (point-max))))
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;; Misc
-
- (declare-function haskell-interactive-switch "haskell")
- (declare-function haskell-session "haskell")
-
- (defun haskell-session-interactive-buffer (s)
- "Get the session interactive buffer."
- (let ((buffer (haskell-session-get s 'interactive-buffer)))
- (if (and buffer (buffer-live-p buffer))
- buffer
- (let ((buffer-name (format "*%s*" (haskell-session-name s)))
- (index 0))
- (while (get-buffer buffer-name)
- (setq buffer-name (format "*%s <%d>*" (haskell-session-name s) index))
- (setq index (1+ index)))
- (let ((buffer (get-buffer-create buffer-name)))
- (haskell-session-set-interactive-buffer s buffer)
- (with-current-buffer buffer
- (haskell-interactive-mode)
- (haskell-session-assign s))
- (haskell-interactive-switch)
- buffer)))))
-
- (defun haskell-interactive-buffer ()
- "Get the interactive buffer of the session."
- (haskell-session-interactive-buffer (haskell-session)))
-
- (defun haskell-process-cabal-live (state buffer)
- "Do live updates for Cabal processes."
- (haskell-interactive-mode-insert
- (haskell-process-session (cadr state))
- (replace-regexp-in-string
- haskell-process-prompt-regex
- ""
- (substring buffer (cl-cadddr state))))
- (setf (cl-cdddr state) (list (length buffer)))
- nil)
-
- (defun haskell-process-parse-error (string)
- "Parse the line number from the error string STRING."
- (let ((span nil))
- (cl-loop for regex
- in haskell-compilation-error-regexp-alist
- do (when (string-match (car regex) string)
- (setq span
- (list :file (match-string 1 string)
- :line (string-to-number (match-string 2 string))
- :col (string-to-number (match-string 4 string))
- :line2 (when (match-string 3 string)
- (string-to-number (match-string 3 string)))
- :col2 (when (match-string 5 string)
- (string-to-number (match-string 5 string)))))))
- span))
-
- (defun haskell-process-suggest-add-package (session msg)
- "Add the (matched) module to your cabal file.
- Cabal file is selected using SESSION's name, module matching is done in MSG."
- (let* ((suggested-package (match-string 1 msg))
- (package-name (replace-regexp-in-string "-[^-]+$" "" suggested-package))
- (version (progn (string-match "\\([^-]+\\)$" suggested-package)
- (match-string 1 suggested-package)))
- (cabal-file (concat (haskell-session-name session)
- ".cabal")))
- (haskell-mode-toggle-interactive-prompt-state)
- (unwind-protect
- (when (y-or-n-p
- (format "Add `%s' to %s?"
- package-name
- cabal-file))
- (haskell-cabal-add-dependency package-name version nil t)
- (when (y-or-n-p (format "Enable -package %s in the GHCi session?" package-name))
- (haskell-process-queue-without-filters
- (haskell-session-process session)
- (format ":set -package %s" package-name))))
- (haskell-mode-toggle-interactive-prompt-state t))))
-
- (defun haskell-process-suggest-remove-import (session file import line)
- "Suggest removing or commenting out import statement.
- Asks user to handle redundant import statement using interactive
- SESSION in specified FILE to remove IMPORT on given LINE."
- (let ((first t))
- (haskell-mode-toggle-interactive-prompt-state)
- (unwind-protect
- (cl-case (read-event
- (propertize (format "%sThe import line `%s' is redundant. Remove? (y, n, c: comment out) "
- (if (not first)
- "Please answer n, y or c: "
- "")
- import)
- 'face
- 'minibuffer-prompt))
- (?y
- (haskell-process-find-file session file)
- (save-excursion
- (goto-char (point-min))
- (forward-line (1- line))
- (goto-char (line-beginning-position))
- (delete-region (line-beginning-position)
- (line-end-position))))
- (?n
- (message "Ignoring redundant import %s" import))
- (?c
- (haskell-process-find-file session file)
- (save-excursion
- (goto-char (point-min))
- (forward-line (1- line))
- (goto-char (line-beginning-position))
- (insert "-- "))))
- ;; unwind
- (haskell-mode-toggle-interactive-prompt-state t))))
-
- (defun haskell-process-find-file (session file)
- "Find the given file in the project."
- (find-file (cond ((file-exists-p (concat (haskell-session-current-dir session) "/" file))
- (concat (haskell-session-current-dir session) "/" file))
- ((file-exists-p (concat (haskell-session-cabal-dir session) "/" file))
- (concat (haskell-session-cabal-dir session) "/" file))
- (t file))))
-
- (defun haskell-process-suggest-pragma (session pragma extension file)
- "Suggest to add something to the top of the file.
- SESSION is used to search given file. Adds PRAGMA and EXTENSION
- wrapped in compiler directive at the top of FILE."
- (let ((string (format "{-# %s %s #-}" pragma extension)))
- (haskell-mode-toggle-interactive-prompt-state)
- (unwind-protect
- (when (y-or-n-p (format "Add %s to the top of the file? " string))
- (haskell-process-find-file session file)
- (save-excursion
- (goto-char (point-min))
- (insert (concat string "\n"))))
- (haskell-mode-toggle-interactive-prompt-state t))))
-
- (defun haskell-interactive-mode-insert-error (response)
- "Insert an error message."
- (insert "\n"
- (haskell-fontify-as-mode
- response
- 'haskell-mode))
- (haskell-interactive-mode-prompt))
-
- (defun haskell-interactive-popup-error (response)
- "Popup an error."
- (if haskell-interactive-popup-errors
- (let ((buf (get-buffer-create "*HS-Error*")))
- (pop-to-buffer buf nil t)
- (with-current-buffer buf
-
- (haskell-error-mode)
- (let ((inhibit-read-only t))
- (erase-buffer)
- (insert (propertize response
- 'font-lock-face
- 'haskell-interactive-face-compile-error))
- (goto-char (point-min))
- (delete-blank-lines)
- (insert (propertize "-- Hit `q' to close this window.\n\n"
- 'font-lock-face 'font-lock-comment-face))
- (save-excursion
- (goto-char (point-max))
- (insert (propertize "\n-- To disable popups, customize `haskell-interactive-popup-errors'.\n\n"
- 'font-lock-face 'font-lock-comment-face))))))
- (haskell-interactive-mode-insert-error response)))
-
- (defun haskell-interactive-next-error-function (&optional n reset)
- "See `next-error-function' for more information."
-
- (let* ((session (haskell-interactive-session))
- (next-error-region (haskell-session-get session 'next-error-region))
- (next-error-locus (haskell-session-get session 'next-error-locus))
- (reset-locus nil))
-
- (when (and next-error-region (or reset (and (/= n 0) (not next-error-locus))))
- (goto-char (car next-error-region))
- (unless (looking-at haskell-interactive-mode-error-regexp)
- (haskell-interactive-mode-error-forward))
-
- (setq reset-locus t)
- (unless (looking-at haskell-interactive-mode-error-regexp)
- (error "no errors found")))
-
- ;; move point if needed
- (cond
- (reset-locus nil)
- ((> n 0) (unless (haskell-interactive-mode-error-forward n)
- (error "no more errors")))
-
- ((< n 0) (unless (haskell-interactive-mode-error-backward (- n))
- (error "no more errors"))))
-
- (let ((orig-line (buffer-substring-no-properties (line-beginning-position) (line-end-position))))
-
- (when (string-match haskell-interactive-mode-error-regexp orig-line)
- (let* ((msgmrk (set-marker (make-marker) (line-beginning-position)))
- (location (haskell-process-parse-error orig-line))
- (file (plist-get location :file))
- (line (plist-get location :line))
- (col1 (plist-get location :col))
- (col2 (plist-get location :col2))
-
- (cabal-relative-file (expand-file-name file (haskell-session-cabal-dir session)))
- (src-relative-file (expand-file-name file (haskell-session-current-dir session)))
-
- (real-file (cond ((file-exists-p cabal-relative-file) cabal-relative-file)
- ((file-exists-p src-relative-file) src-relative-file))))
-
- (haskell-session-set session 'next-error-locus msgmrk)
-
- (if real-file
- (let ((m1 (make-marker))
- (m2 (make-marker)))
- (with-current-buffer (find-file-noselect real-file)
- (save-excursion
- (goto-char (point-min))
- (forward-line (1- line))
- (set-marker m1 (+ col1 (point) -1))
-
- (when col2
- (set-marker m2 (- (point) col2)))))
- ;; ...finally select&hilight error locus
- (compilation-goto-locus msgmrk m1 (and (marker-position m2) m2)))
- (error "don't know where to find %S" file)))))))
-
- (defun haskell-interactive-session ()
- "Get the `haskell-session', throw an error if it's not available."
- (or (haskell-session-maybe)
- (haskell-session-assign
- (or (haskell-session-from-buffer)
- (haskell-session-choose)
- (error "No session associated with this buffer. Try M-x haskell-session-change or report this as a bug.")))))
-
- (defun haskell-interactive-process ()
- "Get the Haskell session."
- (or (haskell-session-process (haskell-interactive-session))
- (error "No Haskell session/process associated with this
- buffer. Maybe run M-x haskell-process-restart?")))
-
- (defun haskell-interactive-mode-do-presentation (expr)
- "Present the given expression EXPR.
- Requires the `present' package to be installed.
- Will automatically import it qualified as Present."
- (let ((p (haskell-interactive-process)))
- ;; If Present.code isn't available, we probably need to run the
- ;; setup.
- (unless (string-match "^Present" (haskell-process-queue-sync-request p ":t Present.encode"))
- (haskell-interactive-mode-setup-presentation p))
- ;; Happily, let statements don't affect the `it' binding in any
- ;; way, so we can fake it, no pun intended.
- (let ((error (haskell-process-queue-sync-request
- p (concat "let it = Present.asData (" expr ")"))))
- (if (not (string= "" error))
- (haskell-interactive-mode-eval-result (haskell-interactive-session) (concat error "\n"))
- (let ((hash (haskell-interactive-mode-presentation-hash)))
- (haskell-process-queue-sync-request
- p (format "let %s = Present.asData (%s)" hash expr))
- (let* ((presentation (haskell-interactive-mode-present-id
- hash
- (list 0))))
- (insert "\n")
- (haskell-interactive-mode-insert-presentation hash presentation)
- (haskell-interactive-mode-eval-result (haskell-interactive-session) "\n"))))
- (haskell-interactive-mode-prompt (haskell-interactive-session)))))
-
- (defun haskell-interactive-mode-present-id (hash id)
- "Generate a presentation for the current expression at ID."
- ;; See below for commentary of this statement.
- (let ((p (haskell-interactive-process)))
- (haskell-process-queue-without-filters
- p "let _it = it")
- (let* ((text (haskell-process-queue-sync-request
- p
- (format "Present.putStr (Present.encode (Present.fromJust (Present.present (Present.fromJust (Present.fromList [%s])) %s)))"
- (mapconcat 'identity (mapcar 'number-to-string id) ",")
- hash)))
- (reply
- (if (string-match "^*** " text)
- '((rep nil))
- (read text))))
- ;; Not necessary, but nice to restore it to the expression that
- ;; the user actually typed in.
- (haskell-process-queue-without-filters
- p "let it = _it")
- reply)))
-
- (defun haskell-presentation-present-slot (btn)
- "The callback to evaluate the slot and present it in place of the button BTN."
- (let ((id (button-get btn 'presentation-id))
- (hash (button-get btn 'hash))
- (parent-rep (button-get btn 'parent-rep))
- (continuation (button-get btn 'continuation)))
- (let ((point (point)))
- (button-put btn 'invisible t)
- (delete-region (button-start btn) (button-end btn))
- (haskell-interactive-mode-insert-presentation
- hash
- (haskell-interactive-mode-present-id hash id)
- parent-rep
- continuation)
- (when (> (point) point)
- (goto-char (1+ point))))))
-
- (defun haskell-interactive-mode-presentation-slot (hash slot parent-rep &optional continuation)
- "Make a slot at point, pointing to ID."
- (let ((type (car slot))
- (id (cadr slot)))
- (if (member (intern type) '(Integer Char Int Float Double))
- (haskell-interactive-mode-insert-presentation
- hash
- (haskell-interactive-mode-present-id hash id)
- parent-rep
- continuation)
- (haskell-interactive-mode-presentation-slot-button slot parent-rep continuation hash))))
-
- (defun haskell-interactive-mode-presentation-slot-button (slot parent-rep continuation hash)
- (let ((start (point))
- (type (car slot))
- (id (cadr slot)))
- (insert (propertize type 'font-lock-face '(:height 0.8 :underline t :inherit font-lock-comment-face)))
- (let ((button (make-text-button start (point)
- :type 'haskell-presentation-slot-button)))
- (button-put button 'hide-on-click t)
- (button-put button 'presentation-id id)
- (button-put button 'parent-rep parent-rep)
- (button-put button 'continuation continuation)
- (button-put button 'hash hash))))
-
- (defun haskell-interactive-mode-insert-presentation (hash presentation &optional parent-rep continuation)
- "Insert the presentation, hooking up buttons for each slot."
- (let* ((rep (cadr (assoc 'rep presentation)))
- (text (cadr (assoc 'text presentation)))
- (slots (cadr (assoc 'slots presentation)))
- (nullary (null slots)))
- (cond
- ((string= "integer" rep)
- (insert (propertize text 'font-lock-face 'font-lock-constant)))
- ((string= "floating" rep)
- (insert (propertize text 'font-lock-face 'font-lock-constant)))
- ((string= "char" rep)
- (insert (propertize
- (if (string= "string" parent-rep)
- (replace-regexp-in-string "^'\\(.+\\)'$" "\\1" text)
- text)
- 'font-lock-face 'font-lock-string-face)))
- ((string= "tuple" rep)
- (insert "(")
- (let ((first t))
- (cl-loop for slot in slots
- do (unless first (insert ","))
- do (haskell-interactive-mode-presentation-slot hash slot rep)
- do (setq first nil)))
- (insert ")"))
- ((string= "list" rep)
- (if (null slots)
- (if continuation
- (progn (delete-char -1)
- (delete-indentation))
- (insert "[]"))
- (let ((i 0))
- (unless continuation
- (insert "["))
- (let ((start-column (current-column)))
- (cl-loop for slot in slots
- do (haskell-interactive-mode-presentation-slot
- hash
- slot
- rep
- (= i (1- (length slots))))
- do (when (not (= i (1- (length slots))))
- (insert "\n")
- (indent-to (1- start-column))
- (insert ","))
- do (setq i (1+ i))))
- (unless continuation
- (insert "]")))))
- ((string= "string" rep)
- (unless (string= "string" parent-rep)
- (insert (propertize "\"" 'font-lock-face 'font-lock-string-face)))
- (cl-loop for slot in slots
- do (haskell-interactive-mode-presentation-slot hash slot rep))
- (unless (string= "string" parent-rep)
- (insert (propertize "\"" 'font-lock-face 'font-lock-string-face))))
- ((string= "alg" rep)
- (when (and parent-rep
- (not nullary)
- (not (string= "list" parent-rep)))
- (insert "("))
- (let ((start-column (current-column)))
- (insert (propertize text 'font-lock-face 'font-lock-type-face))
- (cl-loop for slot in slots
- do (insert "\n")
- do (indent-to (+ 2 start-column))
- do (haskell-interactive-mode-presentation-slot hash slot rep)))
- (when (and parent-rep
- (not nullary)
- (not (string= "list" parent-rep)))
- (insert ")")))
- ((string= "record" rep)
- (let ((start-column (current-column)))
- (insert (propertize text 'font-lock-face 'font-lock-type-face)
- " { ")
- (cl-loop for field in slots
- do (insert "\n")
- do (indent-to (+ 2 start-column))
- do (let ((name (nth 0 field))
- (slot (nth 1 field)))
- (insert name " = ")
- (haskell-interactive-mode-presentation-slot hash slot rep)))
- (insert "\n")
- (indent-to start-column)
- (insert "}")))
- ((eq rep nil)
- (insert (propertize "?" 'font-lock-face 'font-lock-warning)))
- (t
- (let ((err "Unable to present! This very likely means Emacs
- is out of sync with the `present' package. You should make sure
- they're both up to date, or report a bug."))
- (insert err)
- (error err))))))
-
- (defun haskell-interactive-mode-setup-presentation (p)
- "Setup the GHCi REPL for using presentations.
-
- Using asynchronous queued commands as opposed to sync at this
- stage, as sync would freeze up the UI a bit, and we actually
- don't care when the thing completes as long as it's soonish."
- ;; Import dependencies under Present.* namespace
- (haskell-process-queue-without-filters p "import qualified Data.Maybe as Present")
- (haskell-process-queue-without-filters p "import qualified Data.ByteString.Lazy as Present")
- (haskell-process-queue-without-filters p "import qualified Data.AttoLisp as Present")
- (haskell-process-queue-without-filters p "import qualified Present.ID as Present")
- (haskell-process-queue-without-filters p "import qualified Present as Present")
- ;; Make a dummy expression to avoid "Loading package" nonsense
- (haskell-process-queue-without-filters
- p "Present.present (Present.fromJust (Present.fromList [0])) ()"))
-
- (defvar haskell-interactive-mode-presentation-hash 0
- "Counter for the hash.")
-
- (defun haskell-interactive-mode-presentation-hash ()
- "Generate a presentation hash."
- (format "_present_%s"
- (setq haskell-interactive-mode-presentation-hash
- (1+ haskell-interactive-mode-presentation-hash))))
-
- (define-button-type 'haskell-presentation-slot-button
- 'action 'haskell-presentation-present-slot
- 'follow-link t
- 'help-echo "Click to expand…")
-
- (defun haskell-interactive-mode-history-toggle (n)
- "Toggle the history N items up or down."
- (unless (null haskell-interactive-mode-history)
- (setq haskell-interactive-mode-history-index
- (mod (+ haskell-interactive-mode-history-index n)
- (length haskell-interactive-mode-history)))
- (unless (zerop haskell-interactive-mode-history-index)
- (message "History item: %d" haskell-interactive-mode-history-index))
- (haskell-interactive-mode-set-prompt
- (nth haskell-interactive-mode-history-index
- haskell-interactive-mode-history))))
-
- (defun haskell-interactive-mode-set-prompt (p)
- "Set (and overwrite) the current prompt."
- (with-current-buffer (haskell-session-interactive-buffer (haskell-interactive-session))
- (goto-char haskell-interactive-mode-prompt-start)
- (delete-region (point) (point-max))
- (insert p)))
-
- (defun haskell-interactive-mode-history-previous (arg)
- "Cycle backwards through input history."
- (interactive "*p")
- (when (haskell-interactive-at-prompt)
- (if (not (zerop arg))
- (haskell-interactive-mode-history-toggle arg)
- (setq haskell-interactive-mode-history-index 0)
- (haskell-interactive-mode-history-toggle 1))))
-
- (defun haskell-interactive-mode-history-next (arg)
- "Cycle forward through input history."
- (interactive "*p")
- (when (haskell-interactive-at-prompt)
- (if (not (zerop arg))
- (haskell-interactive-mode-history-toggle (- arg))
- (setq haskell-interactive-mode-history-index 0)
- (haskell-interactive-mode-history-toggle -1))))
-
- (defun haskell-interactive-mode-prompt-previous ()
- "Jump to the previous prompt."
- (interactive)
- (let ((prev-prompt-pos
- (save-excursion
- (beginning-of-line) ;; otherwise prompt at current line matches
- (and (search-backward-regexp (haskell-interactive-prompt-regex) nil t)
- (match-end 0)))))
- (when prev-prompt-pos (goto-char prev-prompt-pos))))
-
- (defun haskell-interactive-mode-prompt-next ()
- "Jump to the next prompt."
- (interactive)
- (search-forward-regexp (haskell-interactive-prompt-regex) nil t))
-
- (defun haskell-interactive-mode-clear ()
- "Clear the screen and put any current input into the history."
- (interactive)
- (let ((session (haskell-interactive-session)))
- (with-current-buffer (haskell-session-interactive-buffer session)
- (let ((inhibit-read-only t))
- (set-text-properties (point-min) (point-max) nil))
- (delete-region (point-min) (point-max))
- (remove-overlays)
- (haskell-interactive-mode-prompt session)
- (haskell-session-set session 'next-error-region nil)
- (haskell-session-set session 'next-error-locus nil))
- (with-current-buffer (get-buffer-create "*haskell-process-log*")
- (let ((inhibit-read-only t))
- (delete-region (point-min) (point-max)))
- (remove-overlays))))
-
- (defun haskell-interactive-mode-completion-at-point-function ()
- "Offer completions for partial expression between prompt and point.
- This completion function is used in interactive REPL buffer itself."
- (when (haskell-interactive-at-prompt)
- (let* ((process (haskell-interactive-process))
- (inp (haskell-interactive-mode-input-partial))
- (resp2 (haskell-process-get-repl-completions process inp))
- (rlen (- (length inp) (length (car resp2))))
- (coll (append (if (string-prefix-p inp "import") '("import"))
- (if (string-prefix-p inp "let") '("let"))
- (cdr resp2))))
- (list (- (point) rlen) (point) coll))))
-
- (defun haskell-interactive-mode-trigger-compile-error (state response)
- "Look for an <interactive> compile error.
- If there is one, pop that up in a buffer, similar to `debug-on-error'."
- (when (and haskell-interactive-types-for-show-ambiguous
- (string-match "^\n<interactive>:[-0-9]+:[-0-9]+:" response)
- (not (string-match "^\n<interactive>:[-0-9]+:[-0-9]+:[\n ]+[Ww]arning:" response)))
- (let ((inhibit-read-only t))
- (delete-region haskell-interactive-mode-prompt-start (point))
- (set-marker haskell-interactive-mode-prompt-start
- haskell-interactive-mode-old-prompt-start)
- (goto-char (point-max)))
- (cond
- ((and (not (haskell-interactive-mode-line-is-query (elt state 2)))
- (or (string-match "No instance for (?Show[ \n]" response)
- (string-match "Ambiguous type variable " response)))
- (haskell-process-reset (haskell-interactive-process))
- (let ((resp (haskell-process-queue-sync-request
- (haskell-interactive-process)
- (concat ":t "
- (buffer-substring-no-properties
- haskell-interactive-mode-prompt-start
- (point-max))))))
- (cond
- ((not (string-match "<interactive>:" resp))
- (haskell-interactive-mode-insert-error resp))
- (t (haskell-interactive-popup-error response)))))
- (t (haskell-interactive-popup-error response)
- t))
- t))
-
- ;;;###autoload
- (defun haskell-interactive-mode-echo (session message &optional mode)
- "Echo a read only piece of text before the prompt."
- (with-current-buffer (haskell-session-interactive-buffer session)
- (save-excursion
- (haskell-interactive-mode-goto-end-point)
- (insert (if mode
- (haskell-fontify-as-mode
- (concat message "\n")
- mode)
- (propertize (concat message "\n")
- 'front-sticky t
- 'read-only t
- 'rear-nonsticky t))))))
-
- (defun haskell-interactive-mode-splices-buffer (session)
- "Get the splices buffer for the current SESSION."
- (get-buffer-create (haskell-interactive-mode-splices-buffer-name session)))
-
- (defun haskell-interactive-mode-splices-buffer-name (session)
- (format "*%s:splices*" (haskell-session-name session)))
-
- (defun haskell-interactive-mode-compile-splice (session message)
- "Echo a compiler splice."
- (with-current-buffer (haskell-interactive-mode-splices-buffer session)
- (unless (eq major-mode 'haskell-mode)
- (haskell-mode))
- (let* ((parts (split-string message "\n ======>\n"))
- (file-and-decl-lines (split-string (nth 0 parts) "\n"))
- (file (nth 0 file-and-decl-lines))
- (decl (mapconcat #'identity (cdr file-and-decl-lines) "\n"))
- (output (nth 1 parts)))
- (insert "-- " file "\n")
- (let ((start (point)))
- (insert decl "\n")
- (indent-rigidly start (point) -4))
- (insert "-- =>\n")
- (let ((start (point)))
- (insert output "\n")
- (indent-rigidly start (point) -4)))))
-
- (defun haskell-interactive-mode-insert-garbage (session message)
- "Echo a read only piece of text before the prompt."
- (with-current-buffer (haskell-session-interactive-buffer session)
- (save-excursion
- (haskell-interactive-mode-goto-end-point)
- (insert (propertize message
- 'front-sticky t
- 'font-lock-face 'haskell-interactive-face-garbage
- 'read-only t
- 'rear-nonsticky t)))))
-
- ;;;###autoload
- (defun haskell-process-show-repl-response (line)
- "Send LINE to the GHCi process and echo the result in some fashion.
- Result will be printed in the minibuffer or presented using
- function `haskell-presentation-present', depending on variable
- `haskell-process-use-presentation-mode'."
- (let ((process (haskell-interactive-process)))
- (haskell-process-queue-command
- process
- (make-haskell-command
- :state (cons process line)
- :go (lambda (state)
- (haskell-process-send-string (car state) (cdr state)))
- :complete (lambda (state response)
- (if haskell-process-use-presentation-mode
- (haskell-presentation-present
- (haskell-process-session (car state))
- response)
- (haskell-mode-message-line response)))))))
-
- (provide 'haskell-interactive-mode)
-
- ;;; haskell-interactive-mode.el ends here
|