Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1138 lines
47 KiB

5 years ago
  1. ;;; haskell-interactive-mode.el --- The interactive Haskell mode -*- lexical-binding: t -*-
  2. ;; Copyright © 2011-2012 Chris Done
  3. ;; 2016 Arthur Fayzrakhmanov
  4. ;; Author: Chris Done <chrisdone@gmail.com>
  5. ;; This file is not part of GNU Emacs.
  6. ;; This file is free software; you can redistribute it and/or modify
  7. ;; it under the terms of the GNU General Public License as published by
  8. ;; the Free Software Foundation; either version 3, or (at your option)
  9. ;; any later version.
  10. ;; This file is distributed in the hope that it will be useful,
  11. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ;; GNU General Public License for more details.
  14. ;; You should have received a copy of the GNU General Public License
  15. ;; along with GNU Emacs; see the file COPYING. If not, write to
  16. ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  17. ;; Boston, MA 02110-1301, USA.
  18. ;;; Commentary:
  19. ;;; Todo:
  20. ;;; Code:
  21. (require 'haskell-mode)
  22. (require 'haskell-compile)
  23. (require 'haskell-process)
  24. (require 'haskell-session)
  25. (require 'haskell-font-lock)
  26. (require 'haskell-presentation-mode)
  27. (require 'haskell-utils)
  28. (require 'haskell-string)
  29. (require 'ansi-color)
  30. (require 'cl-lib)
  31. (require 'etags)
  32. (defvar-local haskell-interactive-mode-history-index 0)
  33. (defvar-local haskell-interactive-mode-history (list))
  34. (defvar-local haskell-interactive-mode-old-prompt-start nil
  35. "Mark used for the old beginning of the prompt.")
  36. (defun haskell-interactive-prompt-regex ()
  37. "Generate a regex for searching for any occurence of the prompt\
  38. at the beginning of the line. This should prevent any
  39. interference with prompts that look like haskell expressions."
  40. (concat "^" (regexp-quote haskell-interactive-prompt)))
  41. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  42. ;; Globals used internally
  43. (declare-function haskell-interactive-kill "haskell")
  44. (defvar haskell-interactive-mode-map
  45. (let ((map (make-sparse-keymap)))
  46. (define-key map (kbd "RET") 'haskell-interactive-mode-return)
  47. (define-key map (kbd "SPC") 'haskell-interactive-mode-space)
  48. (define-key map (kbd "C-j") 'haskell-interactive-mode-newline-indent)
  49. (define-key map [remap move-beginning-of-line] 'haskell-interactive-mode-bol)
  50. (define-key map (kbd "<home>") 'haskell-interactive-mode-beginning)
  51. (define-key map (kbd "C-c C-k") 'haskell-interactive-mode-clear)
  52. (define-key map (kbd "C-c C-c") 'haskell-process-interrupt)
  53. (define-key map (kbd "C-c C-f") 'next-error-follow-minor-mode)
  54. (define-key map (kbd "C-c C-z") 'haskell-interactive-switch-back)
  55. (define-key map (kbd "M-p") 'haskell-interactive-mode-history-previous)
  56. (define-key map (kbd "M-n") 'haskell-interactive-mode-history-next)
  57. (define-key map (kbd "C-c C-p") 'haskell-interactive-mode-prompt-previous)
  58. (define-key map (kbd "C-c C-n") 'haskell-interactive-mode-prompt-next)
  59. (define-key map (kbd "C-<up>") 'haskell-interactive-mode-history-previous)
  60. (define-key map (kbd "C-<down>") 'haskell-interactive-mode-history-next)
  61. (define-key map (kbd "TAB") 'haskell-interactive-mode-tab)
  62. (define-key map (kbd "<C-S-backspace>") 'haskell-interactive-mode-kill-whole-line)
  63. map)
  64. "Keymap used in `haskell-interactive-mode'.")
  65. (define-derived-mode haskell-interactive-mode fundamental-mode "Interactive-Haskell"
  66. "Interactive mode for Haskell.
  67. Key bindings:
  68. \\{haskell-interactive-mode-map}"
  69. :group 'haskell-interactive
  70. :syntax-table haskell-mode-syntax-table
  71. (setq haskell-interactive-mode-history (list))
  72. (setq haskell-interactive-mode-history-index 0)
  73. (setq next-error-function #'haskell-interactive-next-error-function)
  74. (add-hook 'completion-at-point-functions
  75. #'haskell-interactive-mode-completion-at-point-function nil t)
  76. (add-hook 'kill-buffer-hook #'haskell-interactive-kill nil t)
  77. (haskell-interactive-mode-prompt))
  78. (defvar haskell-interactive-mode-prompt-start
  79. nil
  80. "Mark used for the beginning of the prompt.")
  81. (defvar haskell-interactive-mode-result-end
  82. nil
  83. "Mark used to figure out where the end of the current result output is.
  84. Used to distinguish betwen user input.")
  85. (defvar-local haskell-interactive-previous-buffer nil
  86. "Records the buffer to which `haskell-interactive-switch-back' should jump.
  87. This is set by `haskell-interactive-switch', and should otherwise
  88. be nil.")
  89. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  90. ;; Hooks
  91. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  92. ;; Mode
  93. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  94. ;; Faces
  95. ;;;###autoload
  96. (defface haskell-interactive-face-prompt
  97. '((t :inherit font-lock-function-name-face))
  98. "Face for the prompt."
  99. :group 'haskell-interactive)
  100. ;;;###autoload
  101. (defface haskell-interactive-face-prompt2
  102. '((t :inherit font-lock-keyword-face))
  103. "Face for the prompt2 in multi-line mode."
  104. :group 'haskell-interactive)
  105. ;;;###autoload
  106. (defface haskell-interactive-face-compile-error
  107. '((t :inherit compilation-error))
  108. "Face for compile errors."
  109. :group 'haskell-interactive)
  110. ;;;###autoload
  111. (defface haskell-interactive-face-compile-warning
  112. '((t :inherit compilation-warning))
  113. "Face for compiler warnings."
  114. :group 'haskell-interactive)
  115. ;;;###autoload
  116. (defface haskell-interactive-face-result
  117. '((t :inherit font-lock-string-face))
  118. "Face for the result."
  119. :group 'haskell-interactive)
  120. ;;;###autoload
  121. (defface haskell-interactive-face-garbage
  122. '((t :inherit font-lock-string-face))
  123. "Face for trailing garbage after a command has completed."
  124. :group 'haskell-interactive)
  125. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  126. ;; Actions
  127. (defun haskell-interactive-mode-newline-indent ()
  128. "Make newline and indent."
  129. (interactive)
  130. (newline)
  131. (indent-to (length haskell-interactive-prompt))
  132. (indent-relative))
  133. (defun haskell-interactive-mode-kill-whole-line ()
  134. "Kill the whole REPL line."
  135. (interactive)
  136. (kill-region haskell-interactive-mode-prompt-start
  137. (line-end-position)))
  138. (defun haskell-interactive-switch-back ()
  139. "Switch back to the buffer from which this interactive buffer was reached."
  140. (interactive)
  141. (if haskell-interactive-previous-buffer
  142. (switch-to-buffer-other-window haskell-interactive-previous-buffer)
  143. (message "No previous buffer.")))
  144. (defun haskell-interactive-copy-to-prompt ()
  145. "Copy the current line to the prompt, overwriting the current prompt."
  146. (interactive)
  147. (let ((l (buffer-substring-no-properties (line-beginning-position)
  148. (line-end-position))))
  149. ;; If it looks like the prompt is at the start of the line, chop
  150. ;; it off.
  151. (when (and (>= (length l) (length haskell-interactive-prompt))
  152. (string= (substring l 0 (length haskell-interactive-prompt))
  153. haskell-interactive-prompt))
  154. (setq l (substring l (length haskell-interactive-prompt))))
  155. (haskell-interactive-mode-set-prompt l)))
  156. (defun haskell-interactive-mode-space (n)
  157. "Handle the space key."
  158. (interactive "p")
  159. (if (and (bound-and-true-p god-local-mode)
  160. (fboundp 'god-mode-self-insert))
  161. (call-interactively 'god-mode-self-insert)
  162. (if (haskell-interactive-at-compile-message)
  163. (next-error-no-select 0)
  164. (self-insert-command n))))
  165. (defun haskell-interactive-at-prompt (&optional end-line)
  166. "If at prompt, return start position of user-input, otherwise return nil.
  167. If END-LINE is non-nil, then return non-nil when the end of line
  168. is at the prompt."
  169. (if (>= (if end-line (line-end-position) (point))
  170. haskell-interactive-mode-prompt-start)
  171. haskell-interactive-mode-prompt-start
  172. nil))
  173. (defun haskell-interactive-mode-bol ()
  174. "Go to beginning of current line, but after current prompt if any."
  175. (interactive)
  176. (let ((beg (line-beginning-position))
  177. (end (line-end-position)))
  178. (goto-char (if (>= end haskell-interactive-mode-prompt-start beg)
  179. haskell-interactive-mode-prompt-start
  180. beg))))
  181. (define-derived-mode haskell-error-mode
  182. special-mode "Error"
  183. "Major mode for viewing Haskell compile errors.")
  184. ;; (define-key haskell-error-mode-map (kbd "q") 'quit-window)
  185. (defun haskell-interactive-mode-handle-h ()
  186. "Handle ^H in output."
  187. (let ((bound (point-min))
  188. (inhibit-read-only t))
  189. (save-excursion
  190. (while (search-backward "\b" bound t 1)
  191. (save-excursion
  192. (forward-char)
  193. (let ((end (point)))
  194. (if (search-backward-regexp "[^\b]" bound t 1)
  195. (forward-char)
  196. (goto-char (point-min)))
  197. (let ((start (point)))
  198. (delete-region (max (- (point) (- end start))
  199. (point-min))
  200. end))))))))
  201. (defun haskell-interactive-mode-multi-line (expr)
  202. "If a multi-line expression EXPR has been entered, then reformat it to be:
  203. :{
  204. do the
  205. multi-liner
  206. expr
  207. :}"
  208. (if (not (string-match-p "\n" expr))
  209. expr
  210. (let ((pre (format "^%s" (regexp-quote haskell-interactive-prompt)))
  211. (lines (split-string expr "\n")))
  212. (cl-loop for elt on (cdr lines) do
  213. (setcar elt (replace-regexp-in-string pre "" (car elt))))
  214. ;; Temporarily set prompt2 to be empty to avoid unwanted output
  215. (concat ":set prompt2 \"\"\n"
  216. ":{\n"
  217. (mapconcat #'identity lines "\n")
  218. "\n:}\n"
  219. (format ":set prompt2 \"%s\"" haskell-interactive-prompt2)))))
  220. (defun haskell-interactive-mode-line-is-query (line)
  221. "Is LINE actually a :t/:k/:i?"
  222. (and (string-match "^:[itk] " line)
  223. t))
  224. (defun haskell-interactive-mode-beginning ()
  225. "Go to the start of the line."
  226. (interactive)
  227. (if (haskell-interactive-at-prompt)
  228. (goto-char haskell-interactive-mode-prompt-start)
  229. (move-beginning-of-line nil)))
  230. (defun haskell-interactive-mode-input-partial ()
  231. "Get the interactive mode input up to point."
  232. (let ((input-start (haskell-interactive-at-prompt)))
  233. (unless input-start
  234. (error "not at prompt"))
  235. (buffer-substring-no-properties input-start (point))))
  236. (defun haskell-interactive-mode-input ()
  237. "Get the interactive mode input."
  238. (buffer-substring-no-properties
  239. haskell-interactive-mode-prompt-start
  240. (point-max)))
  241. (defun haskell-interactive-mode-prompt (&optional session)
  242. "Show a prompt at the end of the REPL buffer.
  243. If SESSION is non-nil, use the REPL buffer associated with
  244. SESSION, otherwise operate on the current buffer."
  245. (with-current-buffer (if session
  246. (haskell-session-interactive-buffer session)
  247. (current-buffer))
  248. (save-excursion
  249. (goto-char (point-max))
  250. (let ((prompt (propertize haskell-interactive-prompt
  251. 'font-lock-face 'haskell-interactive-face-prompt
  252. 'prompt t
  253. 'read-only haskell-interactive-prompt-read-only
  254. 'rear-nonsticky t)))
  255. ;; At the time of writing, front-stickying the first char gives an error
  256. ;; Has unfortunate side-effect of being able to insert before the prompt
  257. (insert (substring prompt 0 1)
  258. (propertize (substring prompt 1)
  259. 'front-sticky t)))
  260. (let ((marker (setq-local haskell-interactive-mode-prompt-start (make-marker))))
  261. (set-marker marker (point))))
  262. (when (haskell-interactive-at-prompt t)
  263. (haskell-interactive-mode-scroll-to-bottom))))
  264. (defun haskell-interactive-mode-eval-result (session text)
  265. "Insert the result of an eval as plain text."
  266. (with-current-buffer (haskell-session-interactive-buffer session)
  267. (let ((at-end (eobp))
  268. (prop-text (propertize text
  269. 'font-lock-face 'haskell-interactive-face-result
  270. 'front-sticky t
  271. 'prompt t
  272. 'read-only haskell-interactive-mode-read-only
  273. 'rear-nonsticky t
  274. 'result t)))
  275. (save-excursion
  276. (goto-char (point-max))
  277. (when (string= text haskell-interactive-prompt2)
  278. (setq prop-text
  279. (propertize prop-text
  280. 'font-lock-face 'haskell-interactive-face-prompt2
  281. 'read-only haskell-interactive-prompt-read-only)))
  282. (insert (ansi-color-apply prop-text))
  283. (haskell-interactive-mode-handle-h)
  284. (let ((marker (setq-local haskell-interactive-mode-result-end (make-marker))))
  285. (set-marker marker (point))))
  286. (when at-end
  287. (haskell-interactive-mode-scroll-to-bottom)))))
  288. (defun haskell-interactive-mode-scroll-to-bottom ()
  289. "Scroll to bottom."
  290. (let ((w (get-buffer-window (current-buffer))))
  291. (when w
  292. (goto-char (point-max))
  293. (set-window-point w (point)))))
  294. (defun haskell-interactive-mode-compile-error (session message)
  295. "Echo an error."
  296. (haskell-interactive-mode-compile-message
  297. session message 'haskell-interactive-face-compile-error))
  298. (defun haskell-interactive-mode-compile-warning (session message)
  299. "Warning message."
  300. (haskell-interactive-mode-compile-message
  301. session message 'haskell-interactive-face-compile-warning))
  302. (defun haskell-interactive-mode-compile-message (session message type)
  303. "Echo a compiler warning."
  304. (with-current-buffer (haskell-session-interactive-buffer session)
  305. (setq next-error-last-buffer (current-buffer))
  306. (save-excursion
  307. (haskell-interactive-mode-goto-end-point)
  308. (let ((lines (string-match "^\\(.*\\)\n\\([[:unibyte:][:nonascii:]]+\\)" message)))
  309. (if lines
  310. (progn
  311. (insert (propertize (concat (match-string 1 message) "\n")
  312. 'expandable t
  313. 'font-lock-face type
  314. 'front-sticky t
  315. 'read-only haskell-interactive-mode-read-only
  316. 'rear-nonsticky t))
  317. (insert (propertize (concat (match-string 2 message) "\n")
  318. 'collapsible t
  319. 'font-lock-face type
  320. 'front-sticky t
  321. 'invisible haskell-interactive-mode-hide-multi-line-errors
  322. 'message-length (length (match-string 2 message))
  323. 'read-only haskell-interactive-mode-read-only
  324. 'rear-nonsticky t)))
  325. (insert (propertize (concat message "\n")
  326. 'font-lock-face type
  327. 'front-sticky t
  328. 'read-only haskell-interactive-mode-read-only
  329. 'rear-nonsticky t)))))))
  330. (defun haskell-interactive-mode-insert (session message)
  331. "Echo a read only piece of text before the prompt."
  332. (with-current-buffer (haskell-session-interactive-buffer session)
  333. (save-excursion
  334. (haskell-interactive-mode-goto-end-point)
  335. (insert (propertize message
  336. 'front-sticky t
  337. 'read-only t
  338. 'rear-nonsticky t)))))
  339. (defun haskell-interactive-mode-goto-end-point ()
  340. "Go to the 'end' of the buffer (before the prompt)."
  341. (goto-char haskell-interactive-mode-prompt-start)
  342. (goto-char (line-beginning-position)))
  343. (defun haskell-interactive-mode-history-add (input)
  344. "Add INPUT to the history."
  345. (setq haskell-interactive-mode-history
  346. (cons ""
  347. (cons input
  348. (cl-remove-if (lambda (i) (or (string= i input) (string= i "")))
  349. haskell-interactive-mode-history))))
  350. (setq haskell-interactive-mode-history-index
  351. 0))
  352. (defun haskell-interactive-mode-tab ()
  353. "Do completion if at prompt or else try collapse/expand."
  354. (interactive)
  355. (cond
  356. ((haskell-interactive-at-prompt)
  357. (completion-at-point))
  358. ((get-text-property (point) 'collapsible)
  359. (let ((column (current-column)))
  360. (search-backward-regexp "^[^ ]")
  361. (haskell-interactive-mode-tab-expand)
  362. (goto-char (+ column (line-beginning-position)))))
  363. (t (haskell-interactive-mode-tab-expand))))
  364. (defun haskell-interactive-mode-tab-expand ()
  365. "Expand the rest of the message."
  366. (cond ((get-text-property (point) 'expandable)
  367. (let* ((pos (1+ (line-end-position)))
  368. (visibility (get-text-property pos 'invisible))
  369. (length (1+ (get-text-property pos 'message-length))))
  370. (let ((inhibit-read-only t))
  371. (put-text-property pos
  372. (+ pos length)
  373. 'invisible
  374. (not visibility)))))))
  375. (defconst haskell-interactive-mode-error-regexp
  376. "^\\(\\(?:[A-Z]:\\)?[^ \r\n:][^\r\n:]*\\):\\([0-9()-:]+\\):?")
  377. (defun haskell-interactive-at-compile-message ()
  378. "Am I on a compile message?"
  379. (and (not (haskell-interactive-at-prompt))
  380. (save-excursion
  381. (goto-char (line-beginning-position))
  382. (looking-at haskell-interactive-mode-error-regexp))))
  383. (defun haskell-interactive-mode-error-backward (&optional count)
  384. "Go backward to the previous error."
  385. (interactive)
  386. (search-backward-regexp haskell-interactive-mode-error-regexp nil t count))
  387. (defun haskell-interactive-mode-error-forward (&optional count)
  388. "Go forward to the next error, or return to the REPL."
  389. (interactive)
  390. (goto-char (line-end-position))
  391. (if (search-forward-regexp haskell-interactive-mode-error-regexp nil t count)
  392. (progn (goto-char (line-beginning-position))
  393. t)
  394. (progn (goto-char (point-max))
  395. nil)))
  396. (defun haskell-interactive-mode-delete-compile-messages (session &optional file-name)
  397. "Delete compile messages in REPL buffer.
  398. If FILE-NAME is non-nil, restrict to removing messages concerning
  399. FILE-NAME only."
  400. (with-current-buffer (haskell-session-interactive-buffer session)
  401. (save-excursion
  402. (goto-char (point-min))
  403. (when (search-forward-regexp "^Compilation failed.$" nil t 1)
  404. (let ((inhibit-read-only t))
  405. (delete-region (line-beginning-position)
  406. (1+ (line-end-position))))
  407. (goto-char (point-min)))
  408. (while (when (re-search-forward haskell-interactive-mode-error-regexp nil t)
  409. (let ((msg-file-name (match-string-no-properties 1))
  410. (msg-startpos (line-beginning-position)))
  411. ;; skip over hanging continuation message lines
  412. (while (progn (forward-line) (looking-at "^[ ]+")))
  413. (when (or (not file-name) (string= file-name msg-file-name))
  414. (let ((inhibit-read-only t))
  415. (set-text-properties msg-startpos (point) nil))
  416. (delete-region msg-startpos (point))
  417. ))
  418. t)))))
  419. ;;;###autoload
  420. (defun haskell-interactive-mode-reset-error (session)
  421. "Reset the error cursor position."
  422. (interactive)
  423. (with-current-buffer (haskell-session-interactive-buffer session)
  424. (haskell-interactive-mode-goto-end-point)
  425. (let ((mrk (point-marker)))
  426. (haskell-session-set session 'next-error-locus nil)
  427. (haskell-session-set session 'next-error-region (cons mrk (copy-marker mrk t))))
  428. (goto-char (point-max))))
  429. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  430. ;; Misc
  431. (declare-function haskell-interactive-switch "haskell")
  432. (declare-function haskell-session "haskell")
  433. (defun haskell-session-interactive-buffer (s)
  434. "Get the session interactive buffer."
  435. (let ((buffer (haskell-session-get s 'interactive-buffer)))
  436. (if (and buffer (buffer-live-p buffer))
  437. buffer
  438. (let ((buffer-name (format "*%s*" (haskell-session-name s)))
  439. (index 0))
  440. (while (get-buffer buffer-name)
  441. (setq buffer-name (format "*%s <%d>*" (haskell-session-name s) index))
  442. (setq index (1+ index)))
  443. (let ((buffer (get-buffer-create buffer-name)))
  444. (haskell-session-set-interactive-buffer s buffer)
  445. (with-current-buffer buffer
  446. (haskell-interactive-mode)
  447. (haskell-session-assign s))
  448. (haskell-interactive-switch)
  449. buffer)))))
  450. (defun haskell-interactive-buffer ()
  451. "Get the interactive buffer of the session."
  452. (haskell-session-interactive-buffer (haskell-session)))
  453. (defun haskell-process-cabal-live (state buffer)
  454. "Do live updates for Cabal processes."
  455. (haskell-interactive-mode-insert
  456. (haskell-process-session (cadr state))
  457. (replace-regexp-in-string
  458. haskell-process-prompt-regex
  459. ""
  460. (substring buffer (cl-cadddr state))))
  461. (setf (cl-cdddr state) (list (length buffer)))
  462. nil)
  463. (defun haskell-process-parse-error (string)
  464. "Parse the line number from the error string STRING."
  465. (let ((span nil))
  466. (cl-loop for regex
  467. in haskell-compilation-error-regexp-alist
  468. do (when (string-match (car regex) string)
  469. (setq span
  470. (list :file (match-string 1 string)
  471. :line (string-to-number (match-string 2 string))
  472. :col (string-to-number (match-string 4 string))
  473. :line2 (when (match-string 3 string)
  474. (string-to-number (match-string 3 string)))
  475. :col2 (when (match-string 5 string)
  476. (string-to-number (match-string 5 string)))))))
  477. span))
  478. (defun haskell-process-suggest-add-package (session msg)
  479. "Add the (matched) module to your cabal file.
  480. Cabal file is selected using SESSION's name, module matching is done in MSG."
  481. (let* ((suggested-package (match-string 1 msg))
  482. (package-name (replace-regexp-in-string "-[^-]+$" "" suggested-package))
  483. (version (progn (string-match "\\([^-]+\\)$" suggested-package)
  484. (match-string 1 suggested-package)))
  485. (cabal-file (concat (haskell-session-name session)
  486. ".cabal")))
  487. (haskell-mode-toggle-interactive-prompt-state)
  488. (unwind-protect
  489. (when (y-or-n-p
  490. (format "Add `%s' to %s?"
  491. package-name
  492. cabal-file))
  493. (haskell-cabal-add-dependency package-name version nil t)
  494. (when (y-or-n-p (format "Enable -package %s in the GHCi session?" package-name))
  495. (haskell-process-queue-without-filters
  496. (haskell-session-process session)
  497. (format ":set -package %s" package-name))))
  498. (haskell-mode-toggle-interactive-prompt-state t))))
  499. (defun haskell-process-suggest-remove-import (session file import line)
  500. "Suggest removing or commenting out import statement.
  501. Asks user to handle redundant import statement using interactive
  502. SESSION in specified FILE to remove IMPORT on given LINE."
  503. (let ((first t))
  504. (haskell-mode-toggle-interactive-prompt-state)
  505. (unwind-protect
  506. (cl-case (read-event
  507. (propertize (format "%sThe import line `%s' is redundant. Remove? (y, n, c: comment out) "
  508. (if (not first)
  509. "Please answer n, y or c: "
  510. "")
  511. import)
  512. 'face
  513. 'minibuffer-prompt))
  514. (?y
  515. (haskell-process-find-file session file)
  516. (save-excursion
  517. (goto-char (point-min))
  518. (forward-line (1- line))
  519. (goto-char (line-beginning-position))
  520. (delete-region (line-beginning-position)
  521. (line-end-position))))
  522. (?n
  523. (message "Ignoring redundant import %s" import))
  524. (?c
  525. (haskell-process-find-file session file)
  526. (save-excursion
  527. (goto-char (point-min))
  528. (forward-line (1- line))
  529. (goto-char (line-beginning-position))
  530. (insert "-- "))))
  531. ;; unwind
  532. (haskell-mode-toggle-interactive-prompt-state t))))
  533. (defun haskell-process-find-file (session file)
  534. "Find the given file in the project."
  535. (find-file (cond ((file-exists-p (concat (haskell-session-current-dir session) "/" file))
  536. (concat (haskell-session-current-dir session) "/" file))
  537. ((file-exists-p (concat (haskell-session-cabal-dir session) "/" file))
  538. (concat (haskell-session-cabal-dir session) "/" file))
  539. (t file))))
  540. (defun haskell-process-suggest-pragma (session pragma extension file)
  541. "Suggest to add something to the top of the file.
  542. SESSION is used to search given file. Adds PRAGMA and EXTENSION
  543. wrapped in compiler directive at the top of FILE."
  544. (let ((string (format "{-# %s %s #-}" pragma extension)))
  545. (haskell-mode-toggle-interactive-prompt-state)
  546. (unwind-protect
  547. (when (y-or-n-p (format "Add %s to the top of the file? " string))
  548. (haskell-process-find-file session file)
  549. (save-excursion
  550. (goto-char (point-min))
  551. (insert (concat string "\n"))))
  552. (haskell-mode-toggle-interactive-prompt-state t))))
  553. (defun haskell-interactive-mode-insert-error (response)
  554. "Insert an error message."
  555. (insert "\n"
  556. (haskell-fontify-as-mode
  557. response
  558. 'haskell-mode))
  559. (haskell-interactive-mode-prompt))
  560. (defun haskell-interactive-popup-error (response)
  561. "Popup an error."
  562. (if haskell-interactive-popup-errors
  563. (let ((buf (get-buffer-create "*HS-Error*")))
  564. (pop-to-buffer buf nil t)
  565. (with-current-buffer buf
  566. (haskell-error-mode)
  567. (let ((inhibit-read-only t))
  568. (erase-buffer)
  569. (insert (propertize response
  570. 'font-lock-face
  571. 'haskell-interactive-face-compile-error))
  572. (goto-char (point-min))
  573. (delete-blank-lines)
  574. (insert (propertize "-- Hit `q' to close this window.\n\n"
  575. 'font-lock-face 'font-lock-comment-face))
  576. (save-excursion
  577. (goto-char (point-max))
  578. (insert (propertize "\n-- To disable popups, customize `haskell-interactive-popup-errors'.\n\n"
  579. 'font-lock-face 'font-lock-comment-face))))))
  580. (haskell-interactive-mode-insert-error response)))
  581. (defun haskell-interactive-next-error-function (&optional n reset)
  582. "See `next-error-function' for more information."
  583. (let* ((session (haskell-interactive-session))
  584. (next-error-region (haskell-session-get session 'next-error-region))
  585. (next-error-locus (haskell-session-get session 'next-error-locus))
  586. (reset-locus nil))
  587. (when (and next-error-region (or reset (and (/= n 0) (not next-error-locus))))
  588. (goto-char (car next-error-region))
  589. (unless (looking-at haskell-interactive-mode-error-regexp)
  590. (haskell-interactive-mode-error-forward))
  591. (setq reset-locus t)
  592. (unless (looking-at haskell-interactive-mode-error-regexp)
  593. (error "no errors found")))
  594. ;; move point if needed
  595. (cond
  596. (reset-locus nil)
  597. ((> n 0) (unless (haskell-interactive-mode-error-forward n)
  598. (error "no more errors")))
  599. ((< n 0) (unless (haskell-interactive-mode-error-backward (- n))
  600. (error "no more errors"))))
  601. (let ((orig-line (buffer-substring-no-properties (line-beginning-position) (line-end-position))))
  602. (when (string-match haskell-interactive-mode-error-regexp orig-line)
  603. (let* ((msgmrk (set-marker (make-marker) (line-beginning-position)))
  604. (location (haskell-process-parse-error orig-line))
  605. (file (plist-get location :file))
  606. (line (plist-get location :line))
  607. (col1 (plist-get location :col))
  608. (col2 (plist-get location :col2))
  609. (cabal-relative-file (expand-file-name file (haskell-session-cabal-dir session)))
  610. (src-relative-file (expand-file-name file (haskell-session-current-dir session)))
  611. (real-file (cond ((file-exists-p cabal-relative-file) cabal-relative-file)
  612. ((file-exists-p src-relative-file) src-relative-file))))
  613. (haskell-session-set session 'next-error-locus msgmrk)
  614. (if real-file
  615. (let ((m1 (make-marker))
  616. (m2 (make-marker)))
  617. (with-current-buffer (find-file-noselect real-file)
  618. (save-excursion
  619. (goto-char (point-min))
  620. (forward-line (1- line))
  621. (set-marker m1 (+ col1 (point) -1))
  622. (when col2
  623. (set-marker m2 (- (point) col2)))))
  624. ;; ...finally select&hilight error locus
  625. (compilation-goto-locus msgmrk m1 (and (marker-position m2) m2)))
  626. (error "don't know where to find %S" file)))))))
  627. (defun haskell-interactive-session ()
  628. "Get the `haskell-session', throw an error if it's not available."
  629. (or (haskell-session-maybe)
  630. (haskell-session-assign
  631. (or (haskell-session-from-buffer)
  632. (haskell-session-choose)
  633. (error "No session associated with this buffer. Try M-x haskell-session-change or report this as a bug.")))))
  634. (defun haskell-interactive-process ()
  635. "Get the Haskell session."
  636. (or (haskell-session-process (haskell-interactive-session))
  637. (error "No Haskell session/process associated with this
  638. buffer. Maybe run M-x haskell-process-restart?")))
  639. (defun haskell-interactive-mode-do-presentation (expr)
  640. "Present the given expression EXPR.
  641. Requires the `present' package to be installed.
  642. Will automatically import it qualified as Present."
  643. (let ((p (haskell-interactive-process)))
  644. ;; If Present.code isn't available, we probably need to run the
  645. ;; setup.
  646. (unless (string-match "^Present" (haskell-process-queue-sync-request p ":t Present.encode"))
  647. (haskell-interactive-mode-setup-presentation p))
  648. ;; Happily, let statements don't affect the `it' binding in any
  649. ;; way, so we can fake it, no pun intended.
  650. (let ((error (haskell-process-queue-sync-request
  651. p (concat "let it = Present.asData (" expr ")"))))
  652. (if (not (string= "" error))
  653. (haskell-interactive-mode-eval-result (haskell-interactive-session) (concat error "\n"))
  654. (let ((hash (haskell-interactive-mode-presentation-hash)))
  655. (haskell-process-queue-sync-request
  656. p (format "let %s = Present.asData (%s)" hash expr))
  657. (let* ((presentation (haskell-interactive-mode-present-id
  658. hash
  659. (list 0))))
  660. (insert "\n")
  661. (haskell-interactive-mode-insert-presentation hash presentation)
  662. (haskell-interactive-mode-eval-result (haskell-interactive-session) "\n"))))
  663. (haskell-interactive-mode-prompt (haskell-interactive-session)))))
  664. (defun haskell-interactive-mode-present-id (hash id)
  665. "Generate a presentation for the current expression at ID."
  666. ;; See below for commentary of this statement.
  667. (let ((p (haskell-interactive-process)))
  668. (haskell-process-queue-without-filters
  669. p "let _it = it")
  670. (let* ((text (haskell-process-queue-sync-request
  671. p
  672. (format "Present.putStr (Present.encode (Present.fromJust (Present.present (Present.fromJust (Present.fromList [%s])) %s)))"
  673. (mapconcat 'identity (mapcar 'number-to-string id) ",")
  674. hash)))
  675. (reply
  676. (if (string-match "^*** " text)
  677. '((rep nil))
  678. (read text))))
  679. ;; Not necessary, but nice to restore it to the expression that
  680. ;; the user actually typed in.
  681. (haskell-process-queue-without-filters
  682. p "let it = _it")
  683. reply)))
  684. (defun haskell-presentation-present-slot (btn)
  685. "The callback to evaluate the slot and present it in place of the button BTN."
  686. (let ((id (button-get btn 'presentation-id))
  687. (hash (button-get btn 'hash))
  688. (parent-rep (button-get btn 'parent-rep))
  689. (continuation (button-get btn 'continuation)))
  690. (let ((point (point)))
  691. (button-put btn 'invisible t)
  692. (delete-region (button-start btn) (button-end btn))
  693. (haskell-interactive-mode-insert-presentation
  694. hash
  695. (haskell-interactive-mode-present-id hash id)
  696. parent-rep
  697. continuation)
  698. (when (> (point) point)
  699. (goto-char (1+ point))))))
  700. (defun haskell-interactive-mode-presentation-slot (hash slot parent-rep &optional continuation)
  701. "Make a slot at point, pointing to ID."
  702. (let ((type (car slot))
  703. (id (cadr slot)))
  704. (if (member (intern type) '(Integer Char Int Float Double))
  705. (haskell-interactive-mode-insert-presentation
  706. hash
  707. (haskell-interactive-mode-present-id hash id)
  708. parent-rep
  709. continuation)
  710. (haskell-interactive-mode-presentation-slot-button slot parent-rep continuation hash))))
  711. (defun haskell-interactive-mode-presentation-slot-button (slot parent-rep continuation hash)
  712. (let ((start (point))
  713. (type (car slot))
  714. (id (cadr slot)))
  715. (insert (propertize type 'font-lock-face '(:height 0.8 :underline t :inherit font-lock-comment-face)))
  716. (let ((button (make-text-button start (point)
  717. :type 'haskell-presentation-slot-button)))
  718. (button-put button 'hide-on-click t)
  719. (button-put button 'presentation-id id)
  720. (button-put button 'parent-rep parent-rep)
  721. (button-put button 'continuation continuation)
  722. (button-put button 'hash hash))))
  723. (defun haskell-interactive-mode-insert-presentation (hash presentation &optional parent-rep continuation)
  724. "Insert the presentation, hooking up buttons for each slot."
  725. (let* ((rep (cadr (assoc 'rep presentation)))
  726. (text (cadr (assoc 'text presentation)))
  727. (slots (cadr (assoc 'slots presentation)))
  728. (nullary (null slots)))
  729. (cond
  730. ((string= "integer" rep)
  731. (insert (propertize text 'font-lock-face 'font-lock-constant)))
  732. ((string= "floating" rep)
  733. (insert (propertize text 'font-lock-face 'font-lock-constant)))
  734. ((string= "char" rep)
  735. (insert (propertize
  736. (if (string= "string" parent-rep)
  737. (replace-regexp-in-string "^'\\(.+\\)'$" "\\1" text)
  738. text)
  739. 'font-lock-face 'font-lock-string-face)))
  740. ((string= "tuple" rep)
  741. (insert "(")
  742. (let ((first t))
  743. (cl-loop for slot in slots
  744. do (unless first (insert ","))
  745. do (haskell-interactive-mode-presentation-slot hash slot rep)
  746. do (setq first nil)))
  747. (insert ")"))
  748. ((string= "list" rep)
  749. (if (null slots)
  750. (if continuation
  751. (progn (delete-char -1)
  752. (delete-indentation))
  753. (insert "[]"))
  754. (let ((i 0))
  755. (unless continuation
  756. (insert "["))
  757. (let ((start-column (current-column)))
  758. (cl-loop for slot in slots
  759. do (haskell-interactive-mode-presentation-slot
  760. hash
  761. slot
  762. rep
  763. (= i (1- (length slots))))
  764. do (when (not (= i (1- (length slots))))
  765. (insert "\n")
  766. (indent-to (1- start-column))
  767. (insert ","))
  768. do (setq i (1+ i))))
  769. (unless continuation
  770. (insert "]")))))
  771. ((string= "string" rep)
  772. (unless (string= "string" parent-rep)
  773. (insert (propertize "\"" 'font-lock-face 'font-lock-string-face)))
  774. (cl-loop for slot in slots
  775. do (haskell-interactive-mode-presentation-slot hash slot rep))
  776. (unless (string= "string" parent-rep)
  777. (insert (propertize "\"" 'font-lock-face 'font-lock-string-face))))
  778. ((string= "alg" rep)
  779. (when (and parent-rep
  780. (not nullary)
  781. (not (string= "list" parent-rep)))
  782. (insert "("))
  783. (let ((start-column (current-column)))
  784. (insert (propertize text 'font-lock-face 'font-lock-type-face))
  785. (cl-loop for slot in slots
  786. do (insert "\n")
  787. do (indent-to (+ 2 start-column))
  788. do (haskell-interactive-mode-presentation-slot hash slot rep)))
  789. (when (and parent-rep
  790. (not nullary)
  791. (not (string= "list" parent-rep)))
  792. (insert ")")))
  793. ((string= "record" rep)
  794. (let ((start-column (current-column)))
  795. (insert (propertize text 'font-lock-face 'font-lock-type-face)
  796. " { ")
  797. (cl-loop for field in slots
  798. do (insert "\n")
  799. do (indent-to (+ 2 start-column))
  800. do (let ((name (nth 0 field))
  801. (slot (nth 1 field)))
  802. (insert name " = ")
  803. (haskell-interactive-mode-presentation-slot hash slot rep)))
  804. (insert "\n")
  805. (indent-to start-column)
  806. (insert "}")))
  807. ((eq rep nil)
  808. (insert (propertize "?" 'font-lock-face 'font-lock-warning)))
  809. (t
  810. (let ((err "Unable to present! This very likely means Emacs
  811. is out of sync with the `present' package. You should make sure
  812. they're both up to date, or report a bug."))
  813. (insert err)
  814. (error err))))))
  815. (defun haskell-interactive-mode-setup-presentation (p)
  816. "Setup the GHCi REPL for using presentations.
  817. Using asynchronous queued commands as opposed to sync at this
  818. stage, as sync would freeze up the UI a bit, and we actually
  819. don't care when the thing completes as long as it's soonish."
  820. ;; Import dependencies under Present.* namespace
  821. (haskell-process-queue-without-filters p "import qualified Data.Maybe as Present")
  822. (haskell-process-queue-without-filters p "import qualified Data.ByteString.Lazy as Present")
  823. (haskell-process-queue-without-filters p "import qualified Data.AttoLisp as Present")
  824. (haskell-process-queue-without-filters p "import qualified Present.ID as Present")
  825. (haskell-process-queue-without-filters p "import qualified Present as Present")
  826. ;; Make a dummy expression to avoid "Loading package" nonsense
  827. (haskell-process-queue-without-filters
  828. p "Present.present (Present.fromJust (Present.fromList [0])) ()"))
  829. (defvar haskell-interactive-mode-presentation-hash 0
  830. "Counter for the hash.")
  831. (defun haskell-interactive-mode-presentation-hash ()
  832. "Generate a presentation hash."
  833. (format "_present_%s"
  834. (setq haskell-interactive-mode-presentation-hash
  835. (1+ haskell-interactive-mode-presentation-hash))))
  836. (define-button-type 'haskell-presentation-slot-button
  837. 'action 'haskell-presentation-present-slot
  838. 'follow-link t
  839. 'help-echo "Click to expand…")
  840. (defun haskell-interactive-mode-history-toggle (n)
  841. "Toggle the history N items up or down."
  842. (unless (null haskell-interactive-mode-history)
  843. (setq haskell-interactive-mode-history-index
  844. (mod (+ haskell-interactive-mode-history-index n)
  845. (length haskell-interactive-mode-history)))
  846. (unless (zerop haskell-interactive-mode-history-index)
  847. (message "History item: %d" haskell-interactive-mode-history-index))
  848. (haskell-interactive-mode-set-prompt
  849. (nth haskell-interactive-mode-history-index
  850. haskell-interactive-mode-history))))
  851. (defun haskell-interactive-mode-set-prompt (p)
  852. "Set (and overwrite) the current prompt."
  853. (with-current-buffer (haskell-session-interactive-buffer (haskell-interactive-session))
  854. (goto-char haskell-interactive-mode-prompt-start)
  855. (delete-region (point) (point-max))
  856. (insert p)))
  857. (defun haskell-interactive-mode-history-previous (arg)
  858. "Cycle backwards through input history."
  859. (interactive "*p")
  860. (when (haskell-interactive-at-prompt)
  861. (if (not (zerop arg))
  862. (haskell-interactive-mode-history-toggle arg)
  863. (setq haskell-interactive-mode-history-index 0)
  864. (haskell-interactive-mode-history-toggle 1))))
  865. (defun haskell-interactive-mode-history-next (arg)
  866. "Cycle forward through input history."
  867. (interactive "*p")
  868. (when (haskell-interactive-at-prompt)
  869. (if (not (zerop arg))
  870. (haskell-interactive-mode-history-toggle (- arg))
  871. (setq haskell-interactive-mode-history-index 0)
  872. (haskell-interactive-mode-history-toggle -1))))
  873. (defun haskell-interactive-mode-prompt-previous ()
  874. "Jump to the previous prompt."
  875. (interactive)
  876. (let ((prev-prompt-pos
  877. (save-excursion
  878. (beginning-of-line) ;; otherwise prompt at current line matches
  879. (and (search-backward-regexp (haskell-interactive-prompt-regex) nil t)
  880. (match-end 0)))))
  881. (when prev-prompt-pos (goto-char prev-prompt-pos))))
  882. (defun haskell-interactive-mode-prompt-next ()
  883. "Jump to the next prompt."
  884. (interactive)
  885. (search-forward-regexp (haskell-interactive-prompt-regex) nil t))
  886. (defun haskell-interactive-mode-clear ()
  887. "Clear the screen and put any current input into the history."
  888. (interactive)
  889. (let ((session (haskell-interactive-session)))
  890. (with-current-buffer (haskell-session-interactive-buffer session)
  891. (let ((inhibit-read-only t))
  892. (set-text-properties (point-min) (point-max) nil))
  893. (delete-region (point-min) (point-max))
  894. (remove-overlays)
  895. (haskell-interactive-mode-prompt session)
  896. (haskell-session-set session 'next-error-region nil)
  897. (haskell-session-set session 'next-error-locus nil))
  898. (with-current-buffer (get-buffer-create "*haskell-process-log*")
  899. (let ((inhibit-read-only t))
  900. (delete-region (point-min) (point-max)))
  901. (remove-overlays))))
  902. (defun haskell-interactive-mode-completion-at-point-function ()
  903. "Offer completions for partial expression between prompt and point.
  904. This completion function is used in interactive REPL buffer itself."
  905. (when (haskell-interactive-at-prompt)
  906. (let* ((process (haskell-interactive-process))
  907. (inp (haskell-interactive-mode-input-partial))
  908. (resp2 (haskell-process-get-repl-completions process inp))
  909. (rlen (- (length inp) (length (car resp2))))
  910. (coll (append (if (string-prefix-p inp "import") '("import"))
  911. (if (string-prefix-p inp "let") '("let"))
  912. (cdr resp2))))
  913. (list (- (point) rlen) (point) coll))))
  914. (defun haskell-interactive-mode-trigger-compile-error (state response)
  915. "Look for an <interactive> compile error.
  916. If there is one, pop that up in a buffer, similar to `debug-on-error'."
  917. (when (and haskell-interactive-types-for-show-ambiguous
  918. (string-match "^\n<interactive>:[-0-9]+:[-0-9]+:" response)
  919. (not (string-match "^\n<interactive>:[-0-9]+:[-0-9]+:[\n ]+[Ww]arning:" response)))
  920. (let ((inhibit-read-only t))
  921. (delete-region haskell-interactive-mode-prompt-start (point))
  922. (set-marker haskell-interactive-mode-prompt-start
  923. haskell-interactive-mode-old-prompt-start)
  924. (goto-char (point-max)))
  925. (cond
  926. ((and (not (haskell-interactive-mode-line-is-query (elt state 2)))
  927. (or (string-match "No instance for (?Show[ \n]" response)
  928. (string-match "Ambiguous type variable " response)))
  929. (haskell-process-reset (haskell-interactive-process))
  930. (let ((resp (haskell-process-queue-sync-request
  931. (haskell-interactive-process)
  932. (concat ":t "
  933. (buffer-substring-no-properties
  934. haskell-interactive-mode-prompt-start
  935. (point-max))))))
  936. (cond
  937. ((not (string-match "<interactive>:" resp))
  938. (haskell-interactive-mode-insert-error resp))
  939. (t (haskell-interactive-popup-error response)))))
  940. (t (haskell-interactive-popup-error response)
  941. t))
  942. t))
  943. ;;;###autoload
  944. (defun haskell-interactive-mode-echo (session message &optional mode)
  945. "Echo a read only piece of text before the prompt."
  946. (with-current-buffer (haskell-session-interactive-buffer session)
  947. (save-excursion
  948. (haskell-interactive-mode-goto-end-point)
  949. (insert (if mode
  950. (haskell-fontify-as-mode
  951. (concat message "\n")
  952. mode)
  953. (propertize (concat message "\n")
  954. 'front-sticky t
  955. 'read-only t
  956. 'rear-nonsticky t))))))
  957. (defun haskell-interactive-mode-splices-buffer (session)
  958. "Get the splices buffer for the current SESSION."
  959. (get-buffer-create (haskell-interactive-mode-splices-buffer-name session)))
  960. (defun haskell-interactive-mode-splices-buffer-name (session)
  961. (format "*%s:splices*" (haskell-session-name session)))
  962. (defun haskell-interactive-mode-compile-splice (session message)
  963. "Echo a compiler splice."
  964. (with-current-buffer (haskell-interactive-mode-splices-buffer session)
  965. (unless (eq major-mode 'haskell-mode)
  966. (haskell-mode))
  967. (let* ((parts (split-string message "\n ======>\n"))
  968. (file-and-decl-lines (split-string (nth 0 parts) "\n"))
  969. (file (nth 0 file-and-decl-lines))
  970. (decl (mapconcat #'identity (cdr file-and-decl-lines) "\n"))
  971. (output (nth 1 parts)))
  972. (insert "-- " file "\n")
  973. (let ((start (point)))
  974. (insert decl "\n")
  975. (indent-rigidly start (point) -4))
  976. (insert "-- =>\n")
  977. (let ((start (point)))
  978. (insert output "\n")
  979. (indent-rigidly start (point) -4)))))
  980. (defun haskell-interactive-mode-insert-garbage (session message)
  981. "Echo a read only piece of text before the prompt."
  982. (with-current-buffer (haskell-session-interactive-buffer session)
  983. (save-excursion
  984. (haskell-interactive-mode-goto-end-point)
  985. (insert (propertize message
  986. 'front-sticky t
  987. 'font-lock-face 'haskell-interactive-face-garbage
  988. 'read-only t
  989. 'rear-nonsticky t)))))
  990. ;;;###autoload
  991. (defun haskell-process-show-repl-response (line)
  992. "Send LINE to the GHCi process and echo the result in some fashion.
  993. Result will be printed in the minibuffer or presented using
  994. function `haskell-presentation-present', depending on variable
  995. `haskell-process-use-presentation-mode'."
  996. (let ((process (haskell-interactive-process)))
  997. (haskell-process-queue-command
  998. process
  999. (make-haskell-command
  1000. :state (cons process line)
  1001. :go (lambda (state)
  1002. (haskell-process-send-string (car state) (cdr state)))
  1003. :complete (lambda (state response)
  1004. (if haskell-process-use-presentation-mode
  1005. (haskell-presentation-present
  1006. (haskell-process-session (car state))
  1007. response)
  1008. (haskell-mode-message-line response)))))))
  1009. (provide 'haskell-interactive-mode)
  1010. ;;; haskell-interactive-mode.el ends here