Skip to content

HTTPS clone URL

Subversion checkout URL

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