Skip to content

HTTPS clone URL

Subversion checkout URL

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