Skip to content
Newer
Older
100644 690 lines (618 sloc) 29.3 KB
4418871 New file.
monnier authored
1 ;;; inf-haskell.el --- Interaction with an inferior Haskell process.
2
439a89e (inferior-haskell-cabal-of-buf)
monnier authored
3 ;; Copyright (C) 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
4418871 New file.
monnier authored
4
5 ;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
6 ;; Keywords: Haskell
7
8 ;; This file is free software; you can redistribute it and/or modify
9 ;; it under the terms of the GNU General Public License as published by
30d658f (displayed-month): Remove declaration since it's not used here.
monnier authored
10 ;; the Free Software Foundation; either version 3, or (at your option)
4418871 New file.
monnier authored
11 ;; any later version.
12
13 ;; This file is distributed in the hope that it will be useful,
14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ;; GNU General Public License for more details.
17
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with GNU Emacs; see the file COPYING. If not, write to
20 ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 ;; Boston, MA 02111-1307, USA.
22
23 ;;; Commentary:
24
25 ;; The code is made of 2 parts: a major mode for the buffer that holds the
225a7c5 (inferior-haskell-load-file): Quote file name.
monnier authored
26 ;; inferior process's session and a minor mode for use in source buffers.
4418871 New file.
monnier authored
27
3e6c2e0 *** empty log message ***
monnier authored
28 ;; Todo:
29
6e300b7 (inferior-haskell-find-project-root): New var, to
monnier authored
30 ;; - Check out Shim for ideas.
9ed46b1 (inferior-haskell-load-file): Re-add the `reload' arg.
monnier authored
31 ;; - i-h-load-buffer and i-h-send-region.
3e6c2e0 *** empty log message ***
monnier authored
32
4418871 New file.
monnier authored
33 ;;; Code:
34
35 (require 'comint)
36 (require 'shell) ;For directory tracking.
37 (require 'compile)
36dd8bd (inferior-haskell-info-xref-re): New cst.
monnier authored
38 (require 'haskell-mode)
7410480 (inferior-haskell-wait-for-prompt): New fun, extracted
monnier authored
39 (eval-when-compile (require 'cl))
4418871 New file.
monnier authored
40
4456be9 (subst-char-in-string, make-temp-file): Add fallback definitions for …
monnier authored
41 ;; XEmacs compatibility.
42
43 (unless (fboundp 'subst-char-in-string)
44 (defun subst-char-in-string (fromchar tochar string &optional inplace)
45 ;; This is Haskell-mode, we don't want no stinkin' `aset'.
46 (apply 'string (mapcar (lambda (c) (if (eq c fromchar) tochar c)) string))))
47
48 (unless (fboundp 'make-temp-file)
49 (defun make-temp-file (prefix &optional dir-flag)
50 (catch 'done
51 (while t
52 (let ((f (make-temp-name (expand-file-name prefix (temp-directory)))))
53 (condition-case ()
54 (progn
55 (if dir-flag (make-directory f)
56 (write-region "" nil f nil 'silent nil))
57 (throw 'done f))
58 (file-already-exists t)))))))
947a32d (inferior-haskell-info-xref-re): Allow a column-range.
monnier authored
59
4456be9 (subst-char-in-string, make-temp-file): Add fallback definitions for …
monnier authored
60
4418871 New file.
monnier authored
61 ;; Here I depart from the inferior-haskell- prefix.
62 ;; Not sure if it's a good idea.
27cc26c (haskell-program-name): Use ghci if hugs is absent.
monnier authored
63 (defcustom haskell-program-name
64 ;; Arbitrarily give preference to hugs over ghci.
65 (or (cond
66 ((not (fboundp 'executable-find)) nil)
67 ((executable-find "hugs") "hugs \"+.\"")
68 ((executable-find "ghci") "ghci"))
69 "hugs \"+.\"")
4418871 New file.
monnier authored
70 "The name of the command to start the inferior Haskell process.
71 The command can include arguments."
8b661ca (haskell-program-name): Fix defcustom delcaration.
monnier authored
72 ;; Custom only supports the :options keyword for a few types, e.g. not
73 ;; for string.
74 ;; :options '("hugs \"+.\"" "ghci")
4418871 New file.
monnier authored
75 :group 'haskell
76 :type '(choice string (repeat string)))
77
36dd8bd (inferior-haskell-info-xref-re): New cst.
monnier authored
78 (defconst inferior-haskell-info-xref-re
947a32d (inferior-haskell-info-xref-re): Allow a column-range.
monnier authored
79 "\t-- Defined at \\(.+\\):\\([0-9]+\\):\\([0-9]+\\)\\(?:-\\([0-9]+\\)\\)?$")
36dd8bd (inferior-haskell-info-xref-re): New cst.
monnier authored
80
c5f1425 (inferior-haskell-module-alist-file)
monnier authored
81 (defconst inferior-haskell-module-re
82 "\t-- Defined in \\(.+\\)$"
83 "Regular expression for matching module names in :info.")
84
4418871 New file.
monnier authored
85 (defconst inferior-haskell-error-regexp-alist
86 ;; The format of error messages used by Hugs.
e19bf49 (inferior-haskell-error-regexp-alist): Fix GHCi regexp, support warni…
monnier authored
87 `(("^ERROR \"\\(.+?\\)\"\\(:\\| line \\)\\([0-9]+\\) - " 1 3)
4418871 New file.
monnier authored
88 ;; Format of error messages used by GHCi.
a9fe15b (inferior-haskell-error-regexp-alist): Be more careful
monnier authored
89 ("^\\(.+?\\):\\([0-9]+\\):\\(\\([0-9]+\\):\\)?\\( \\|\n *\\)\\(Warning\\)?"
90 1 2 4 ,@(if (fboundp 'compilation-fake-loc)
91 '((6) nil (5 '(face nil font-lock-multiline t)))))
5a72afa (inferior-haskell-error-regexp-alist): Add entries for GHCI's excepti…
monnier authored
92 ;; Runtime exceptions, from ghci.
93 ("^\\*\\*\\* Exception: \\(.+?\\):(\\([0-9]+\\),\\([0-9]+\\))-(\\([0-9]+\\),\\([0-9]+\\)): .*"
94 1 ,@(if (fboundp 'compilation-fake-loc) '((2 . 4) (3 . 5)) '(2 3)))
a9fe15b (inferior-haskell-error-regexp-alist): Be more careful
monnier authored
95 ;; GHCi uses two different forms for line/col ranges, depending on
ff46f40 (inferior-haskell-error-regexp-alist):
monnier authored
96 ;; whether it's all on the same line or not :-( In Emacs-23, I could use
97 ;; explicitly numbered subgroups to merge the two patterns.
5a72afa (inferior-haskell-error-regexp-alist): Add entries for GHCI's excepti…
monnier authored
98 ("^\\*\\*\\* Exception: \\(.+?\\):\\([0-9]+\\):\\([0-9]+\\)-\\([0-9]+\\): .*"
99 1 2 ,(if (fboundp 'compilation-fake-loc) '(3 . 4) 3))
a9fe15b (inferior-haskell-error-regexp-alist): Be more careful
monnier authored
100 ;; Info messages. Not errors per se.
ff46f40 (inferior-haskell-error-regexp-alist):
monnier authored
101 ,@(when (fboundp 'compilation-fake-loc)
102 `(;; Other GHCi patterns used in type errors.
103 ("^[ \t]+at \\(.+\\):\\([0-9]+\\):\\([0-9]+\\)-\\([0-9]+\\)$"
104 1 2 (3 . 4) 0)
105 ;; Foo.hs:318:80:
106 ;; Ambiguous occurrence `Bar'
107 ;; It could refer to either `Bar', defined at Zork.hs:311:5
108 ;; or `Bar', imported from Bars at Frob.hs:32:0-16
109 ;; (defined at Location.hs:97:5)
110 ("[ (]defined at \\(.+\\):\\([0-9]+\\):\\([0-9]+\\))?$" 1 2 3 0)
111 ("imported from .* at \\(.+\\):\\([0-9]+\\):\\([0-9]+\\)-\\([0-9]+\\)$"
112 1 2 (3 . 4) 0)
113 ;; Info xrefs.
947a32d (inferior-haskell-info-xref-re): Allow a column-range.
monnier authored
114 (,inferior-haskell-info-xref-re 1 2 (3 . 4) 0))))
4418871 New file.
monnier authored
115 "Regexps for error messages generated by inferior Haskell processes.
116 The format should be the same as for `compilation-error-regexp-alist'.")
117
6e300b7 (inferior-haskell-find-project-root): New var, to
monnier authored
118 (defcustom inferior-haskell-find-project-root t
119 "If non-nil, try and find the project root directory of this file.
120 This will either look for a Cabal file or a \"module\" statement in the file."
c7c6278 (inferior-haskell-use-cabal): New custom var.
monnier authored
121 :type 'boolean)
122
4418871 New file.
monnier authored
123 (define-derived-mode inferior-haskell-mode comint-mode "Inf-Haskell"
124 "Major mode for interacting with an inferior Haskell process."
125 (set (make-local-variable 'comint-prompt-regexp)
126 "^\\*?[A-Z][\\._a-zA-Z0-9]*\\( \\*?[A-Z][\\._a-zA-Z0-9]*\\)*> ")
127 (set (make-local-variable 'comint-input-autoexpand) nil)
128
129 ;; Setup directory tracking.
130 (set (make-local-variable 'shell-cd-regexp) ":cd")
bbbe054 (inferior-haskell-mode): Use shell-dirtrack-mode if possible.
monnier authored
131 (condition-case nil
132 (shell-dirtrack-mode 1)
133 (error ;The minor mode function may not exist or not accept an arg.
134 (set (make-local-variable 'shell-dirtrackp) t)
135 (add-hook 'comint-input-filter-functions 'shell-directory-tracker
136 nil 'local)))
4418871 New file.
monnier authored
137
138 ;; Setup `compile' support so you can just use C-x ` and friends.
139 (set (make-local-variable 'compilation-error-regexp-alist)
140 inferior-haskell-error-regexp-alist)
5a72afa (inferior-haskell-error-regexp-alist): Add entries for GHCI's excepti…
monnier authored
141 (set (make-local-variable 'compilation-first-column) 0) ;GHCI counts from 0.
bfd15e1 (inferior-haskell-mode): Hide compilation-mode bindings.
monnier authored
142 (if (and (not (boundp 'minor-mode-overriding-map-alist))
143 (fboundp 'compilation-shell-minor-mode))
144 ;; If we can't remove compilation-minor-mode bindings, at least try to
145 ;; use compilation-shell-minor-mode, so there are fewer
146 ;; annoying bindings.
147 (compilation-shell-minor-mode 1)
148 ;; Else just use compilation-minor-mode but without its bindings because
149 ;; things like mouse-2 are simply too annoying.
150 (compilation-minor-mode 1)
151 (let ((map (make-sparse-keymap)))
152 (dolist (keys '([menu-bar] [follow-link]))
153 ;; Preserve some of the bindings.
154 (define-key map keys (lookup-key compilation-minor-mode-map keys)))
155 (add-to-list 'minor-mode-overriding-map-alist
156 (cons 'compilation-minor-mode map)))))
4418871 New file.
monnier authored
157
6784a8b (inferior-haskell-string-to-strings): Remove `separator' argument. Ca…
monnier authored
158 (defun inferior-haskell-string-to-strings (string)
159 "Split the STRING into a list of strings."
160 (let ((i (string-match "[\"]" string)))
161 (if (null i) (split-string string) ; no quoting: easy
162 (append (unless (eq i 0) (split-string (substring string 0 i)))
4418871 New file.
monnier authored
163 (let ((rfs (read-from-string string i)))
164 (cons (car rfs)
165 (inferior-haskell-string-to-strings
6784a8b (inferior-haskell-string-to-strings): Remove `separator' argument. Ca…
monnier authored
166 (substring string (cdr rfs)))))))))
4418871 New file.
monnier authored
167
168 (defun inferior-haskell-command (arg)
169 (inferior-haskell-string-to-strings
170 (if (null arg) haskell-program-name
6dd0f3a (inferior-haskell-command): Provide a default.
monnier authored
171 (read-string "Command to run haskell: " haskell-program-name))))
4418871 New file.
monnier authored
172
173 (defvar inferior-haskell-buffer nil
174 "The buffer in which the inferior process is running.")
175
176 (defun inferior-haskell-start-process (command)
177 "Start an inferior haskell process.
6784a8b (inferior-haskell-string-to-strings): Remove `separator' argument. Ca…
monnier authored
178 With universal prefix \\[universal-argument], prompts for a COMMAND,
4418871 New file.
monnier authored
179 otherwise uses `haskell-program-name'.
180 It runs the hook `inferior-haskell-hook' after starting the process and
181 setting up the inferior-haskell buffer."
182 (interactive (list (inferior-haskell-command current-prefix-arg)))
183 (setq inferior-haskell-buffer
184 (apply 'make-comint "haskell" (car command) nil (cdr command)))
185 (with-current-buffer inferior-haskell-buffer
186 (inferior-haskell-mode)
187 (run-hooks 'inferior-haskell-hook)))
188
189 (defun inferior-haskell-process (&optional arg)
190 (or (if (buffer-live-p inferior-haskell-buffer)
191 (get-buffer-process inferior-haskell-buffer))
192 (progn
193 (let ((current-prefix-arg arg))
194 (call-interactively 'inferior-haskell-start-process))
195 ;; Try again.
196 (inferior-haskell-process arg))))
197
198 ;;;###autoload
199 (defalias 'run-haskell 'switch-to-haskell)
200 ;;;###autoload
201 (defun switch-to-haskell (&optional arg)
202 "Show the inferior-haskell buffer. Start the process if needed."
203 (interactive "P")
204 (let ((proc (inferior-haskell-process arg)))
205 (pop-to-buffer (process-buffer proc))))
206
ce51306 (with-selected-window): Define while compiling.
monnier authored
207 (eval-when-compile
208 (unless (fboundp 'with-selected-window)
209 (defmacro with-selected-window (win &rest body)
210 `(save-selected-window
211 (select-window ,win)
212 ,@body))))
6dd0f3a (inferior-haskell-command): Provide a default.
monnier authored
213
7410480 (inferior-haskell-wait-for-prompt): New fun, extracted
monnier authored
214 (defcustom inferior-haskell-wait-and-jump nil
215 "If non-nil, wait for file loading to terminate and jump to the error."
216 :type 'boolean
217 :group 'haskell)
218
c6cbffc (inferior-haskell-wait-for-prompt): Add timeout arg.
monnier authored
219 (defun inferior-haskell-wait-for-prompt (proc &optional timeout)
7410480 (inferior-haskell-wait-for-prompt): New fun, extracted
monnier authored
220 "Wait until PROC sends us a prompt.
221 The process PROC should be associated to a comint buffer."
222 (with-current-buffer (process-buffer proc)
c6cbffc (inferior-haskell-wait-for-prompt): Add timeout arg.
monnier authored
223 (let ((found nil))
224 (while (progn
225 (goto-char comint-last-input-end)
226 (and (not (setq found
227 (re-search-forward comint-prompt-regexp nil t)))
228 (accept-process-output proc timeout))))
229 (unless found (error "Can't find the prompt.")))))
7410480 (inferior-haskell-wait-for-prompt): New fun, extracted
monnier authored
230
c7c6278 (inferior-haskell-use-cabal): New custom var.
monnier authored
231 (defvar inferior-haskell-cabal-buffer nil)
232
233 (defun inferior-haskell-cabal-of-buf (buf)
234 (require 'haskell-cabal)
235 (with-current-buffer buf
236 (or inferior-haskell-cabal-buffer
439a89e (inferior-haskell-cabal-of-buf)
monnier authored
237 (and (not (local-variable-p 'inferior-haskell-cabal-buffer
238 ;; XEmacs needs this argument.
837c948 (inferior-haskell-cabal-of-buf): Fix typo.
monnier authored
239 (current-buffer)))
c7c6278 (inferior-haskell-use-cabal): New custom var.
monnier authored
240 (set (make-local-variable 'inferior-haskell-cabal-buffer)
241 (haskell-cabal-find-file))))))
242
6e300b7 (inferior-haskell-find-project-root): New var, to
monnier authored
243 (defun inferior-haskell-find-project-root (buf)
244 (with-current-buffer buf
245 (let ((cabal (inferior-haskell-cabal-of-buf buf)))
246 (or (when cabal
247 (with-current-buffer cabal
f11a557 (inferior-haskell-find-project-root): Use it.
monnier authored
248 (let ((hsd (haskell-cabal-get-setting "hs-source-dirs")))
249 (if (null hsd)
6e300b7 (inferior-haskell-find-project-root): New var, to
monnier authored
250 ;; If there's a Cabal file with no Hs-Source-Dirs, then
251 ;; just use the Cabal file's directory.
252 default-directory
253 ;; If there is an HSD, then check that it's an existing
254 ;; dir (otherwise, it may be a list of dirs and we don't
255 ;; know what to do with those). If it doesn't exist, then
256 ;; give up.
3afce2f (inferior-haskell-find-project-root): Minor simplification.
monnier authored
257 (if (file-directory-p hsd) (expand-file-name hsd))))))
6e300b7 (inferior-haskell-find-project-root): New var, to
monnier authored
258 ;; If there's no Cabal file or it's not helpful, try to look for
259 ;; a "module" statement and count the number of "." in the
260 ;; module name.
261 (save-excursion
262 (goto-char (point-min))
263 (let ((case-fold-search nil))
264 (when (re-search-forward
265 "^module[ \t]+\\([^- \t\n]+\\.[^- \t\n]+\\)[ \t]+where\\>" nil t)
266 (let* ((dir default-directory)
267 (module (match-string 1))
268 (pos 0))
269 (while (string-match "\\." module pos)
270 (setq pos (match-end 0))
271 (setq dir (expand-file-name ".." dir)))
272 ;; Let's check that the module name matches the file name,
273 ;; otherwise the project root is probably not what we think.
274 (if (eq t (compare-strings
275 (file-name-sans-extension buffer-file-name)
276 nil nil
277 (expand-file-name
278 (replace-regexp-in-string "\\." "/" module)
279 dir)
280 nil nil t))
281 dir
282 ;; If they're not equal, it means the local directory
283 ;; hierarchy doesn't match the module name. This seems
284 ;; odd, so let's warn the user about it. May help us
285 ;; debug this code as well.
286 (message "Ignoring inconsistent `module' info: %s in %s"
287 module buffer-file-name)
288 nil)))))))))
947a32d (inferior-haskell-info-xref-re): Allow a column-range.
monnier authored
289
290
6e300b7 (inferior-haskell-find-project-root): New var, to
monnier authored
291
4418871 New file.
monnier authored
292 ;;;###autoload
9ed46b1 (inferior-haskell-load-file): Re-add the `reload' arg.
monnier authored
293 (defun inferior-haskell-load-file (&optional reload)
0294d90 (inferior-haskell-load-file): Do reload if prefix arg.
monnier authored
294 "Pass the current buffer's file to the inferior haskell process.
295 If prefix arg \\[universal-argument] is given, just reload the previous file."
8af5157 (inferior-haskell-load-file): Typo.
monnier authored
296 (interactive "P")
2eaced4 (inferior-haskell-load-file): Save buffer before using buffer-file-name.
monnier authored
297 ;; Save first, so we're sure that `buffer-file-name' is non-nil afterward.
298 (save-buffer)
c7c6278 (inferior-haskell-use-cabal): New custom var.
monnier authored
299 (let ((buf (current-buffer))
300 (file buffer-file-name)
4418871 New file.
monnier authored
301 (proc (inferior-haskell-process)))
302 (with-current-buffer (process-buffer proc)
303 (compilation-forget-errors)
c7c6278 (inferior-haskell-use-cabal): New custom var.
monnier authored
304 (let ((parsing-end (marker-position (process-mark proc)))
6e300b7 (inferior-haskell-find-project-root): New var, to
monnier authored
305 root)
c7c6278 (inferior-haskell-use-cabal): New custom var.
monnier authored
306 ;; Go to the root of the Cabal project, if applicable.
6e300b7 (inferior-haskell-find-project-root): New var, to
monnier authored
307 (when (and inferior-haskell-find-project-root
308 (setq root (inferior-haskell-find-project-root buf)))
c7c6278 (inferior-haskell-use-cabal): New custom var.
monnier authored
309 ;; Not sure if it's useful/needed and if it actually works.
6e300b7 (inferior-haskell-find-project-root): New var, to
monnier authored
310 (unless (equal default-directory root)
311 (setq default-directory root)
c7c6278 (inferior-haskell-use-cabal): New custom var.
monnier authored
312 (inferior-haskell-send-command
313 proc (concat ":cd " default-directory)))
314 (setq file (file-relative-name file)))
9ed46b1 (inferior-haskell-load-file): Re-add the `reload' arg.
monnier authored
315 (inferior-haskell-send-command
316 proc (if reload ":reload" (concat ":load \"" file "\"")))
30d658f (displayed-month): Remove declaration since it's not used here.
monnier authored
317 ;; Move the parsing-end marker *after* sending the command so
d8a5cc8 (inferior-haskell-load-file): Fix the compilation-parsing-end fiddlin…
monnier authored
318 ;; that it doesn't point just to the insertion point.
319 ;; Otherwise insertion may move the marker (if done with
320 ;; insert-before-markers) and we'd then miss some errors.
321 (if (boundp 'compilation-parsing-end)
322 (if (markerp compilation-parsing-end)
323 (set-marker compilation-parsing-end parsing-end)
324 (setq compilation-parsing-end parsing-end))))
1cda384 (inferior-haskell-load-file): Simplify and make more
monnier authored
325 (with-selected-window (display-buffer (current-buffer))
7410480 (inferior-haskell-wait-for-prompt): New fun, extracted
monnier authored
326 (goto-char (point-max)))
30d658f (displayed-month): Remove declaration since it's not used here.
monnier authored
327 ;; Use compilation-auto-jump-to-first-error if available.
328 ;; (if (and (boundp 'compilation-auto-jump-to-first-error)
329 ;; compilation-auto-jump-to-first-error
330 ;; (boundp 'compilation-auto-jump-to-next))
331 ;; (setq compilation-auto-jump-to-next t)
332 (when inferior-haskell-wait-and-jump
333 (inferior-haskell-wait-for-prompt proc)
334 (ignore-errors ;Don't beep if there were no errors.
335 (next-error)))))) ;; )
8512ba1 (inferior-haskell-mode): Typo.
monnier authored
336
3afdf6b (inferior-haskell-run-command): New var.
monnier authored
337 (defvar inferior-haskell-run-command ":main")
338
339 (defun inferior-haskell-load-and-run (command)
9ed46b1 (inferior-haskell-load-file): Re-add the `reload' arg.
monnier authored
340 "Pass the current buffer's file to haskell and then run a COMMAND."
3afdf6b (inferior-haskell-run-command): New var.
monnier authored
341 (interactive
342 (list
343 (if (and inferior-haskell-run-command (not current-prefix-arg))
344 inferior-haskell-run-command
345 (read-string "Command to run: " nil nil inferior-haskell-run-command))))
346 (setq inferior-haskell-run-command command)
f818e57 Removed support for :reload (e.g. removed the C-c C-r binding).
monnier authored
347 (inferior-haskell-load-file)
9ed46b1 (inferior-haskell-load-file): Re-add the `reload' arg.
monnier authored
348 ;; FIXME: if the :load encountered errors, we should not `run'.
3afdf6b (inferior-haskell-run-command): New var.
monnier authored
349 (inferior-haskell-send-command (inferior-haskell-process) command))
350
8512ba1 (inferior-haskell-mode): Typo.
monnier authored
351 (defun inferior-haskell-send-command (proc str)
352 (setq str (concat str "\n"))
353 (with-current-buffer (process-buffer proc)
7410480 (inferior-haskell-wait-for-prompt): New fun, extracted
monnier authored
354 (inferior-haskell-wait-for-prompt proc)
8512ba1 (inferior-haskell-mode): Typo.
monnier authored
355 (goto-char (process-mark proc))
356 (insert-before-markers str)
357 (move-marker comint-last-input-end (point))
358 (comint-send-string proc str)))
4418871 New file.
monnier authored
359
9ed46b1 (inferior-haskell-load-file): Re-add the `reload' arg.
monnier authored
360 (defun inferior-haskell-reload-file ()
361 "Tell the inferior haskell process to reread the current buffer's file."
362 (interactive)
363 (inferior-haskell-load-file 'reload))
364
c5f1425 (inferior-haskell-module-alist-file)
monnier authored
365 ;;;###autoload
36dd8bd (inferior-haskell-info-xref-re): New cst.
monnier authored
366 (defun inferior-haskell-type (expr &optional insert-value)
367 "Query the haskell process for the type of the given expression.
368 If optional argument `insert-value' is non-nil, insert the type above point
369 in the buffer. This can be done interactively with the \\[universal-argument] prefix.
370 The returned info is cached for reuse by `haskell-doc-mode'."
371 (interactive
372 (let ((sym (haskell-ident-at-point)))
373 (list (read-string (if (> (length sym) 0)
374 (format "Show type of (default %s): " sym)
375 "Show type of: ")
376 nil nil sym)
377 current-prefix-arg)))
378 (if (string-match "\\`\\s_+\\'" expr) (setq expr (concat "(" expr ")")))
379 (let* ((proc (inferior-haskell-process))
380 (type
381 (with-current-buffer (process-buffer proc)
382 (let ((parsing-end ; Remember previous spot.
383 (marker-position (process-mark proc))))
384 (inferior-haskell-send-command proc (concat ":type " expr))
385 ;; Find new point.
386 (goto-char (point-max))
387 (inferior-haskell-wait-for-prompt proc)
388 ;; Back up to the previous end-of-line.
389 (end-of-line 0)
390 ;; Extract the type output
391 (buffer-substring-no-properties
392 (save-excursion (goto-char parsing-end)
393 (line-beginning-position 2))
394 (point))))))
395 (if (not (string-match (concat "\\`" (regexp-quote expr) "[ \t]+::[ \t]*")
396 type))
397 (error "No type info: %s" type)
398
399 ;; Cache for reuse by haskell-doc.
400 (when (and (boundp 'haskell-doc-mode) haskell-doc-mode
401 (boundp 'haskell-doc-user-defined-ids)
402 ;; Haskell-doc only works for idents, not arbitrary expr.
403 (string-match "\\`(?\\(\\s_+\\|\\(\\sw\\|\\s'\\)+\\)?[ \t]*::[ \t]*"
404 type))
405 (let ((sym (match-string 1 type)))
406 (setq haskell-doc-user-defined-ids
407 (cons (cons sym (substring type (match-end 0)))
408 (remove-if (lambda (item) (equal (car item) sym))
409 haskell-doc-user-defined-ids)))))
410
011c502 (inferior-haskell-type): Fix call to message.
monnier authored
411 (if (interactive-p) (message "%s" type))
36dd8bd (inferior-haskell-info-xref-re): New cst.
monnier authored
412 (when insert-value
413 (beginning-of-line)
414 (insert type "\n"))
415 type)))
416
c5f1425 (inferior-haskell-module-alist-file)
monnier authored
417 ;;;###autoload
36dd8bd (inferior-haskell-info-xref-re): New cst.
monnier authored
418 (defun inferior-haskell-info (sym)
419 "Query the haskell process for the info of the given expression."
420 (interactive
421 (let ((sym (haskell-ident-at-point)))
422 (list (read-string (if (> (length sym) 0)
423 (format "Show info of (default %s): " sym)
424 "Show info of: ")
425 nil nil sym))))
426 (let ((proc (inferior-haskell-process)))
427 (with-current-buffer (process-buffer proc)
428 (let ((parsing-end ; Remember previous spot.
429 (marker-position (process-mark proc))))
430 (inferior-haskell-send-command proc (concat ":info " sym))
431 ;; Find new point.
432 (goto-char (point-max))
433 (inferior-haskell-wait-for-prompt proc)
434 ;; Move to previous end-of-line
435 (end-of-line 0)
436 (let ((result
437 (buffer-substring-no-properties
438 (save-excursion (goto-char parsing-end)
439 (line-beginning-position 2))
440 (point))))
441 ;; Move back to end of process buffer
442 (goto-char (point-max))
443 (if (interactive-p) (message "%s" result))
444 result)))))
445
c5f1425 (inferior-haskell-module-alist-file)
monnier authored
446 ;;;###autoload
36dd8bd (inferior-haskell-info-xref-re): New cst.
monnier authored
447 (defun inferior-haskell-find-definition (sym)
448 "Attempt to locate and jump to the definition of the given expression."
449 (interactive
450 (let ((sym (haskell-ident-at-point)))
451 (list (read-string (if (> (length sym) 0)
452 (format "Find definition of (default %s): " sym)
453 "Find definition of: ")
454 nil nil sym))))
455 (let ((info (inferior-haskell-info sym)))
456 (if (not (string-match inferior-haskell-info-xref-re info))
457 (error "No source information available")
458 (let ((file (match-string-no-properties 1 info))
459 (line (string-to-number
460 (match-string-no-properties 2 info)))
461 (col (string-to-number
462 (match-string-no-properties 3 info))))
463 (when file
c6cbffc (inferior-haskell-wait-for-prompt): Add timeout arg.
monnier authored
464 (with-current-buffer (process-buffer (inferior-haskell-process))
465 ;; The file name is relative to the process's cwd.
466 (setq file (expand-file-name file)))
36dd8bd (inferior-haskell-info-xref-re): New cst.
monnier authored
467 ;; Push current location marker on the ring used by `find-tag'
468 (require 'etags)
469 (ring-insert find-tag-marker-ring (point-marker))
470 (pop-to-buffer (find-file-noselect file))
471 (when line
472 (goto-line line)
473 (when col (move-to-column col))))))))
474
c5f1425 (inferior-haskell-module-alist-file)
monnier authored
475 ;;; Functions to find the documentation of a given function.
476 ;;
947a32d (inferior-haskell-info-xref-re): Allow a column-range.
monnier authored
477 ;; TODO for this section:
478 ;;
c5f1425 (inferior-haskell-module-alist-file)
monnier authored
479 ;; * Support fetching of local Haddock docs pulled directly from source files.
480 ;; * Display docs locally? w3m?
481
482 (defcustom inferior-haskell-use-web-docs
483 'fallback
484 "Whether to use the online documentation. Possible values:
485 `never', meaning always use local documentation, unless the local
486 file doesn't exist, when do nothing, `fallback', which means only
487 use the online documentation when the local file doesn't exist,
488 or `always', meaning always use the online documentation,
489 regardless of existance of local files. Default is `fallback'."
490 :group 'haskell
491 :type '(choice (const :tag "Never" never)
492 (const :tag "As fallback" fallback)
493 (const :tag "Always" always)))
494
495 (defcustom inferior-haskell-web-docs-base
496 "http://haskell.org/ghc/docs/latest/html/libraries/"
497 "The base URL of the online libraries documentation. This will
498 only be used if the value of `inferior-haskell-use-web-docs' is
499 `always' or `fallback'."
500 :group 'haskell
501 :type 'string)
502
503 (defcustom haskell-package-manager-name "ghc-pkg"
504 "Name of the program to consult regarding package details."
505 :group 'haskell
506 :type 'string)
507
508 (defcustom haskell-package-conf-file
744f5ce (haskell-package-conf-file): Don't use `ignore-errors'
monnier authored
509 (condition-case nil
510 (with-temp-buffer
511 (call-process "ghc" nil t nil "--print-libdir")
512 (expand-file-name "package.conf"
513 (buffer-substring (point-min) (1- (point-max)))))
514 ;; Don't use `ignore-errors' because this form is not byte-compiled :-(
515 (error nil))
c5f1425 (inferior-haskell-module-alist-file)
monnier authored
516 "Where the package configuration file for the package manager resides.
517 By default this is set to `ghc --print-libdir`/package.conf."
518 :group 'haskell
519 :type 'string)
520
521 (defun inferior-haskell-get-module (sym)
522 "Fetch the module in which SYM is defined."
523 (let ((info (inferior-haskell-info sym)))
524 (unless (string-match inferior-haskell-module-re info)
947a32d (inferior-haskell-info-xref-re): Allow a column-range.
monnier authored
525 (error
c5f1425 (inferior-haskell-module-alist-file)
monnier authored
526 "No documentation information available. Did you forget to C-c C-l?"))
527 (match-string-no-properties 1 info)))
528
529 (defun inferior-haskell-query-ghc-pkg (&rest args)
530 "Send ARGS to ghc-pkg, or whatever the value of
531 `haskell-package-manager' is. Insert the output into the current
532 buffer."
533 (apply 'call-process haskell-package-manager-name nil t nil args))
534
535 (defun inferior-haskell-get-package-list ()
536 "Get the list of packages from ghc-pkg, or whatever
537 `haskell-package-manager-name' is."
538 (with-temp-buffer
539 (inferior-haskell-query-ghc-pkg "--simple-output" "list")
540 (split-string (buffer-substring (point-min) (point-max)))))
541
b74e553 (inferior-haskell-module-alist-file): Use a file in /tmp rather than ~/.
monnier authored
542 (defun inferior-haskell-compute-module-alist ()
543 "Compute a list mapping modules to package names and haddock URLs using ghc-pkg."
c5f1425 (inferior-haskell-module-alist-file)
monnier authored
544 (message "Generating module alist...")
b74e553 (inferior-haskell-module-alist-file): Use a file in /tmp rather than ~/.
monnier authored
545 (let ((module-alist ()))
546 (with-temp-buffer
547 (dolist (package (inferior-haskell-get-package-list))
548 (erase-buffer)
c5f1425 (inferior-haskell-module-alist-file)
monnier authored
549 (inferior-haskell-query-ghc-pkg "describe" package)
b74e553 (inferior-haskell-module-alist-file): Use a file in /tmp rather than ~/.
monnier authored
550
551 (let ((package-w/o-version
552 (replace-regexp-in-string "[-.0-9]*\\'" "" package))
553 ;; Find the Haddock documentation URL for this package
554 (haddock
555 (progn
556 (goto-char (point-min))
bf80267 (inferior-haskell-compute-module-alist): Fix regexps.
monnier authored
557 (when (re-search-forward "haddock-html:[ \t]+\\(.*[^ \t\n]\\)"
b74e553 (inferior-haskell-module-alist-file): Use a file in /tmp rather than ~/.
monnier authored
558 nil t)
559 (match-string 1)))))
560
561 ;; Fetch the list of exposed modules for this package
562 (goto-char (point-min))
bf80267 (inferior-haskell-compute-module-alist): Fix regexps.
monnier authored
563 (when (re-search-forward "^exposed-modules:\\(.*\\(\n[ \t].*\\)*\\)"
564 nil t)
565 (dolist (module (split-string (match-string 1)))
566 (push (list module package-w/o-version haddock)
567 module-alist)))))
b74e553 (inferior-haskell-module-alist-file): Use a file in /tmp rather than ~/.
monnier authored
568
569 (message "Generating module alist... done")
570 module-alist)))
571
572
573 (defcustom inferior-haskell-module-alist-file
574 ;; (expand-file-name "~/.inf-haskell-module-alist")
bf80267 (inferior-haskell-compute-module-alist): Fix regexps.
monnier authored
575 (expand-file-name (concat "inf-haskell-module-alist-"
576 (number-to-string (user-uid)))
439a89e (inferior-haskell-cabal-of-buf)
monnier authored
577 (if (fboundp 'temp-directory)
578 (temp-directory)
579 temporary-file-directory))
b74e553 (inferior-haskell-module-alist-file): Use a file in /tmp rather than ~/.
monnier authored
580 "Where to save the module -> package lookup table.
581 Set this to `nil' to never cache to a file."
582 :group 'haskell
583 :type '(choice (const :tag "Don't cache to file" nil) string))
584
585 (defvar inferior-haskell-module-alist nil
586 "Association list of modules to their packages.
587 Each element is of the form (MODULE PACKAGE HADDOCK), where
588 MODULE is the name of a module,
589 PACKAGE is the package it belongs to, and
590 HADDOCK is the path to that package's Haddock documentation.
591
592 This is calculated on-demand using `inferior-haskell-compute-module-alist'.
593 It's also cached in the file `inferior-haskell-module-alist-file',
594 so that it can be obtained more quickly next time.")
595
596 (defun inferior-haskell-module-alist ()
597 "Get the module alist from cache or ghc-pkg's info."
598 (or
599 ;; If we already have computed the alist, use it...
600 inferior-haskell-module-alist
601 (setq inferior-haskell-module-alist
602 (or
603 ;; ...otherwise try to read it from the cache file...
604 (and
605 inferior-haskell-module-alist-file
606 (file-readable-p inferior-haskell-module-alist-file)
607 (file-newer-than-file-p inferior-haskell-module-alist-file
608 haskell-package-conf-file)
609 (with-temp-buffer
610 (insert-file-contents inferior-haskell-module-alist-file)
611 (goto-char (point-min))
612 (prog1 (read (current-buffer))
613 (message "Read module alist from file cache."))))
614
615 ;; ...or generate it again and save it in a file for later.
616 (let ((alist (inferior-haskell-compute-module-alist)))
617 (when inferior-haskell-module-alist-file
6e300b7 (inferior-haskell-find-project-root): New var, to
monnier authored
618 (with-temp-buffer
619 (print alist (current-buffer))
620 ;; Do the write to a temp file first, then rename it.
621 ;; This makes it more atomic, and suffers from fewer security
622 ;; holes related to race conditions if the file is in /tmp.
623 (let ((tmp (make-temp-file inferior-haskell-module-alist-file)))
624 (write-region (point-min) (point-max) tmp)
625 (rename-file tmp inferior-haskell-module-alist-file
626 'ok-if-already-exists))))
b74e553 (inferior-haskell-module-alist-file): Use a file in /tmp rather than ~/.
monnier authored
627 alist)))))
c5f1425 (inferior-haskell-module-alist-file)
monnier authored
628
3afdf6b (inferior-haskell-run-command): New var.
monnier authored
629 (defvar inferior-haskell-ghc-internal-ident-alist
630 ;; FIXME: Fill this table, ideally semi-automatically.
631 '(("GHC.Base.return" . "Control.Monad.return")
632 ("GHC.List" . "Data.List")))
633
634 (defun inferior-haskell-map-internal-ghc-ident (ident)
635 "Try to translate some internal GHC identifier to its alter ego in haskell docs."
636 (let ((head ident)
637 (tail "")
638 remapped)
639 (while (and (not
640 (setq remapped
641 (cdr (assoc head
642 inferior-haskell-ghc-internal-ident-alist))))
643 (string-match "\\.[^.]+\\'" head))
644 (setq tail (concat (match-string 0 head) tail))
645 (setq head (substring head 0 (match-beginning 0))))
646 (concat (or remapped head) tail)))
647
c5f1425 (inferior-haskell-module-alist-file)
monnier authored
648 ;;;###autoload
649 (defun inferior-haskell-find-haddock (sym)
650 "Find and open the Haddock documentation of SYM.
651 Make sure to load the file into GHCi or Hugs first by using C-c C-l.
652 Only works for functions in a package installed with ghc-pkg, or
653 whatever the value of `haskell-package-manager-name' is.
654
655 This function needs to find which package a given module belongs
656 to. In order to do this, it computes a module-to-package lookup
657 alist, which is expensive to compute (it takes upwards of five
658 seconds with more than about thirty installed packages). As a
659 result, we cache it across sessions using the cache file
660 referenced by `inferior-haskell-module-alist-file'. We test to
661 see if this is newer than `haskell-package-conf-file' every time
662 we load it."
663 (interactive
664 (let ((sym (haskell-ident-at-point)))
665 (list (read-string (if (> (length sym) 0)
666 (format "Find documentation of (default %s): " sym)
667 "Find documentation of: ")
668 nil nil sym))))
3afdf6b (inferior-haskell-run-command): New var.
monnier authored
669 (setq sym (inferior-haskell-map-internal-ghc-ident sym))
b74e553 (inferior-haskell-module-alist-file): Use a file in /tmp rather than ~/.
monnier authored
670 (let* (;; Find the module and look it up in the alist
c5f1425 (inferior-haskell-module-alist-file)
monnier authored
671 (module (inferior-haskell-get-module sym))
b74e553 (inferior-haskell-module-alist-file): Use a file in /tmp rather than ~/.
monnier authored
672 (alist-record (assoc module (inferior-haskell-module-alist)))
c5f1425 (inferior-haskell-module-alist-file)
monnier authored
673 (package (nth 1 alist-record))
674 (file-name (concat (subst-char-in-string ?. ?- module) ".html"))
675 (local-path (concat (nth 2 alist-record) "/" file-name))
676 (url (if (or (eq inferior-haskell-use-web-docs 'always)
677 (and (not (file-exists-p local-path))
678 (eq inferior-haskell-use-web-docs 'fallback)))
f2fd5e5 (inferior-haskell-find-haddock): Jump to the symbol anchor within Had…
monnier authored
679 (concat inferior-haskell-web-docs-base package "/" file-name
680 ;; Jump to the symbol anchor within Haddock.
681 "#v:" sym)
b74e553 (inferior-haskell-module-alist-file): Use a file in /tmp rather than ~/.
monnier authored
682 (and (file-exists-p local-path)
c5f1425 (inferior-haskell-module-alist-file)
monnier authored
683 (concat "file://" local-path)))))
684 (if url (browse-url url) (error "Local file doesn't exist."))))
685
4418871 New file.
monnier authored
686 (provide 'inf-haskell)
bfd15e1 (inferior-haskell-mode): Hide compilation-mode bindings.
monnier authored
687
688 ;; arch-tag: 61804287-63dd-4052-bc0e-90f691b34b40
4418871 New file.
monnier authored
689 ;;; inf-haskell.el ends here
Something went wrong with that request. Please try again.