comparison .emacs.d/haskell-mode/haskell-process.el @ 0:2764b4f45f9f

1st commit
author Shohei KOKUBO <e105744@ie.u-ryukyu.ac.jp>
date Mon, 21 Apr 2014 04:30:59 +0900
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:2764b4f45f9f
1 ;;; haskell-process.el --- Communicating with the inferior Haskell process
2
3 ;; Copyright (C) 2011-2012 Chris Done
4
5 ;; Author: Chris Done <chrisdone@gmail.com>
6
7 ;; This file is not part of GNU Emacs.
8
9 ;; This file is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 3, or (at your option)
12 ;; any later version.
13
14 ;; This file is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING. If not, write to
21 ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 ;; Boston, MA 02110-1301, USA.
23
24 ;;; Commentary:
25
26 ;;; Todo:
27
28 ;;; Code:
29
30 (require 'haskell-mode)
31 (require 'haskell-session)
32 (require 'haskell-compat)
33 (require 'haskell-str)
34 (require 'haskell-utils)
35 (require 'haskell-presentation-mode)
36 (require 'haskell-navigate-imports)
37 (with-no-warnings (require 'cl))
38
39 ;; FIXME: haskell-process shouldn't depend on haskell-interactive-mode to avoid module-dep cycles
40 (declare-function haskell-interactive-mode-echo "haskell-interactive-mode" (session message &optional mode))
41 (declare-function haskell-interactive-mode-compile-error "haskell-interactive-mode" (session message))
42 (declare-function haskell-interactive-mode-compile-warning "haskell-interactive-mode" (session message))
43 (declare-function haskell-interactive-mode-insert "haskell-interactive-mode" (session message))
44 (declare-function haskell-interactive-mode-reset-error "haskell-interactive-mode" (session))
45 (declare-function haskell-interactive-show-load-message "haskell-interactive-mode" (session type module-name file-name echo))
46
47 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
48 ;; Configuration
49 (defgroup haskell-interactive nil
50 "Settings for REPL interaction via `haskell-interactive-mode'"
51 :link '(custom-manual "(haskell-mode)haskell-interactive-mode")
52 :group 'haskell)
53
54 (defcustom haskell-process-path-ghci
55 "ghci"
56 "The path for starting ghci."
57 :group 'haskell-interactive
58 :type '(choice string (repeat string)))
59
60 (defcustom haskell-process-path-cabal
61 "cabal"
62 "Path to the `cabal' executable."
63 :group 'haskell-interactive
64 :type '(choice string (repeat string)))
65
66 (defcustom haskell-process-path-cabal-ghci
67 "cabal-ghci"
68 "The path for starting cabal-ghci."
69 :group 'haskell-interactive
70 :type '(choice string (repeat string)))
71
72 (defcustom haskell-process-path-cabal-dev
73 "cabal-dev"
74 "The path for starting cabal-dev."
75 :group 'haskell-interactive
76 :type '(choice string (repeat string)))
77
78 (defcustom haskell-process-args-ghci
79 '("-ferror-spans")
80 "Any arguments for starting ghci."
81 :group 'haskell-interactive
82 :type '(repeat (string :tag "Argument")))
83
84 (defcustom haskell-process-args-cabal-repl
85 '("--ghc-option=-ferror-spans")
86 "Additional arguments to for `cabal repl' invocation.
87 Note: The settings in `haskell-process-path-ghci' and
88 `haskell-process-args-ghci' are not automatically reused as `cabal repl'
89 currently invokes `ghc --interactive'. Use
90 `--with-ghc=<path-to-executable>' if you want to use a different
91 interactive GHC frontend; use `--ghc-option=<ghc-argument>' to
92 pass additional flags to `ghc'."
93 :group 'haskell-interactive
94 :type '(repeat (string :tag "Argument")))
95
96 (defcustom haskell-process-do-cabal-format-string
97 ":!cd %s && %s"
98 "The way to run cabal comands. It takes two arguments -- the directory and the command.
99 See `haskell-process-do-cabal' for more details."
100 :group 'haskell-interactive
101 :type 'string)
102
103 (defcustom haskell-process-type
104 'ghci
105 "The inferior Haskell process type to use."
106 :type '(choice (const ghci) (const cabal-repl) (const cabal-dev) (const cabal-ghci))
107 :group 'haskell-interactive)
108
109 (defcustom haskell-process-log
110 nil
111 "Enable debug logging to \"*haskell-process-log*\" buffer."
112 :type 'boolean
113 :group 'haskell-interactive)
114
115 (defcustom haskell-process-show-debug-tips
116 t
117 "Show debugging tips when starting the process."
118 :type 'boolean
119 :group 'haskell-interactive)
120
121 (defcustom haskell-notify-p
122 nil
123 "Notify using notifications.el (if loaded)?"
124 :type 'boolean
125 :group 'haskell-interactive)
126
127 (defcustom haskell-process-suggest-no-warn-orphans
128 t
129 "Suggest adding -fno-warn-orphans pragma to file when getting orphan warnings."
130 :type 'boolean
131 :group 'haskell-interactive)
132
133 (defcustom haskell-process-suggest-hoogle-imports
134 nil
135 "Suggest to add import statements using Hoogle as a backend."
136 :type 'boolean
137 :group 'haskell-interactive)
138
139 (defcustom haskell-process-suggest-add-package
140 t
141 "Suggest to add packages to your .cabal file when Cabal says it
142 is a member of the hidden package, blah blah."
143 :type 'boolean
144 :group 'haskell-interactive)
145
146 (defcustom haskell-process-suggest-language-pragmas
147 t
148 "Suggest adding LANGUAGE pragmas recommended by GHC."
149 :type 'boolean
150 :group 'haskell-interactive)
151
152 (defcustom haskell-process-suggest-remove-import-lines
153 nil
154 "Suggest removing import lines as warned by GHC."
155 :type 'boolean
156 :group 'haskell-interactive)
157
158 (defcustom haskell-process-suggest-overloaded-strings
159 t
160 "Suggest adding OverloadedStrings pragma to file when getting type mismatches with [Char]."
161 :type 'boolean
162 :group 'haskell-interactive)
163
164 (defcustom haskell-process-check-cabal-config-on-load
165 t
166 "Check changes cabal config on loading Haskell files and
167 restart the GHCi process if changed.."
168 :type 'boolean
169 :group 'haskell-interactive)
170
171 (defcustom haskell-process-prompt-restart-on-cabal-change
172 t
173 "Ask whether to restart the GHCi process when the Cabal file
174 has changed?"
175 :type 'boolean
176 :group 'haskell-interactive)
177
178 (defcustom haskell-process-auto-import-loaded-modules
179 nil
180 "Auto import the modules reported by GHC to have been loaded?"
181 :type 'boolean
182 :group 'haskell-interactive)
183
184 (defcustom haskell-process-reload-with-fbytecode
185 nil
186 "When using -fobject-code, auto reload with -fbyte-code (and
187 then restore the -fobject-code) so that all module info and
188 imports become available?"
189 :type 'boolean
190 :group 'haskell-interactive)
191
192 (defcustom haskell-process-use-presentation-mode
193 nil
194 "Use presentation mode to show things like type info instead of
195 printing to the message area."
196 :type 'boolean
197 :group 'haskell-interactive)
198
199 (defvar haskell-imported-suggested nil)
200 (defvar haskell-process-prompt-regex "\\(^[> ]*> $\\|\n[> ]*> $\\)")
201 (defvar haskell-reload-p nil)
202
203 (defvar haskell-process-greetings
204 (list "Hello, Haskell!"
205 "The lambdas must flow."
206 "Hours of hacking await!"
207 "The next big Haskell project is about to start!"
208 "Your wish is my IO ().")
209 "Greetings for when the Haskell process starts up.")
210
211 (defconst haskell-process-logo
212 (expand-file-name "logo.svg" haskell-mode-pkg-base-dir)
213 "Haskell logo for notifications.")
214
215
216 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
217 ;; Accessing commands -- using cl 'defstruct'
218 (defstruct haskell-command
219 "Data structure representing a command to be executed when with
220 a custom state and three callback."
221 ;; hold the custom command state
222 ;; state :: a
223 state
224 ;; called when to execute a command
225 ;; go :: a -> ()
226 go
227 ;; called whenever output was collected from the haskell process
228 ;; live :: a -> Response -> Bool
229 live
230 ;; called when the output from the haskell process indicates that the command
231 ;; is complete
232 ;; complete :: a -> Response -> ()
233 complete)
234
235 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
236 ;; Accessing commands
237
238
239 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
240 ;; Specialised commands
241
242 ;;;###autoload
243 (defun haskell-process-generate-tags (&optional and-then-find-this-tag)
244 "Regenerate the TAGS table."
245 (interactive)
246 (let ((process (haskell-process)))
247 (haskell-process-queue-command
248 process
249 (make-haskell-command
250 :state (cons process and-then-find-this-tag)
251 :go (lambda (state)
252 (haskell-process-send-string
253 (car state)
254 (format ":!cd %s && %s | %s | %s"
255 (haskell-session-cabal-dir
256 (haskell-process-session (car state)))
257 "find . -name '*.hs*'"
258 "grep -v '#'" ; To avoid Emacs back-up files. Yeah.
259 "xargs hasktags -e -x")))
260 :complete (lambda (state response)
261 (when (cdr state)
262 (let ((tags-file-name
263 (haskell-session-tags-filename
264 (haskell-process-session (car state)))))
265 (find-tag (cdr state))))
266 (haskell-mode-message-line "Tags generated."))))))
267
268 ;;;###autoload
269 (defun haskell-process-do-type (&optional insert-value)
270 "Print the type of the given expression."
271 (interactive "P")
272 (if insert-value
273 (haskell-process-insert-type)
274 (haskell-process-do-simple-echo
275 (let ((ident (haskell-ident-at-point)))
276 ;; TODO: Generalize all these `string-match' of ident calls into
277 ;; one function.
278 (format (if (string-match "^[_[:lower:][:upper:]]" ident)
279 ":type %s"
280 ":type (%s)")
281 ident))
282 'haskell-mode)))
283
284 (defun haskell-process-insert-type ()
285 "Get the identifer at the point and insert its type, if
286 possible, using GHCi's :type."
287 (let ((process (haskell-process))
288 (query (let ((ident (haskell-ident-at-point)))
289 (format (if (string-match "^[_[:lower:][:upper:]]" ident)
290 ":type %s"
291 ":type (%s)")
292 ident))))
293 (haskell-process-queue-command
294 process
295 (make-haskell-command
296 :state (list process query (current-buffer))
297 :go (lambda (state)
298 (haskell-process-send-string (nth 0 state)
299 (nth 1 state)))
300 :complete (lambda (state response)
301 (cond
302 ;; TODO: Generalize this into a function.
303 ((or (string-match "^Top level" response)
304 (string-match "^<interactive>" response))
305 (message response))
306 (t
307 (with-current-buffer (nth 2 state)
308 (goto-char (line-beginning-position))
309 (insert (format "%s\n" response))))))))))
310
311 ;;;###autoload
312 (defun haskell-process-do-info (&optional prompt-value)
313 "Print info on the identifier at point.
314 If PROMPT-VALUE is non-nil, request identifier via mini-buffer."
315 (interactive "P")
316 (haskell-process-do-simple-echo
317 (let ((ident (if prompt-value
318 (read-from-minibuffer "Info: " (haskell-ident-at-point))
319 (haskell-ident-at-point)))
320 (modname (unless prompt-value
321 (haskell-utils-parse-import-statement-at-point))))
322 (if modname
323 (format ":browse! %s" modname)
324 (format (if (string-match "^[a-zA-Z_]" ident)
325 ":info %s"
326 ":info (%s)")
327 (or ident
328 (haskell-ident-at-point)))))
329 'haskell-mode))
330
331 (defun haskell-process-do-try-info (sym)
332 "Get info of `sym' and echo in the minibuffer."
333 (let ((process (haskell-process)))
334 (haskell-process-queue-command
335 process
336 (make-haskell-command
337 :state (cons process sym)
338 :go (lambda (state)
339 (haskell-process-send-string
340 (car state)
341 (if (string-match "^[A-Za-z_]" (cdr state))
342 (format ":info %s" (cdr state))
343 (format ":info (%s)" (cdr state)))))
344 :complete (lambda (state response)
345 (unless (or (string-match "^Top level" response)
346 (string-match "^<interactive>" response))
347 (haskell-mode-message-line response)))))))
348
349 (defun haskell-process-do-simple-echo (line &optional mode)
350 "Send LINE to the GHCi process and echo the result in some
351 fashion, such as printing in the minibuffer, or using
352 haskell-present, depending on configuration."
353 (let ((process (haskell-process)))
354 (haskell-process-queue-command
355 process
356 (make-haskell-command
357 :state (list process line mode)
358 :go (lambda (state)
359 (haskell-process-send-string (car state) (cadr state)))
360 :complete (lambda (state response)
361 ;; TODO: TBD: don't do this if
362 ;; `haskell-process-use-presentation-mode' is t.
363 (haskell-interactive-mode-echo
364 (haskell-process-session (car state))
365 response
366 (caddr state))
367 (if haskell-process-use-presentation-mode
368 (progn (haskell-present (cadr state)
369 (haskell-process-session (car state))
370 response)
371 (haskell-session-assign
372 (haskell-process-session (car state))))
373 (haskell-mode-message-line response)))))))
374
375 (defun haskell-process-look-config-changes (session)
376 "Checks whether a cabal configuration file has
377 changed. Restarts the process if that is the case."
378 (let ((current-checksum (haskell-session-get session 'cabal-checksum))
379 (new-checksum (haskell-cabal-compute-checksum
380 (haskell-session-get session 'cabal-dir))))
381 (when (not (string= current-checksum new-checksum))
382 (haskell-interactive-mode-echo session (format "Cabal file changed: %s" new-checksum))
383 (haskell-session-set-cabal-checksum session
384 (haskell-session-get session 'cabal-dir))
385 (unless (and haskell-process-prompt-restart-on-cabal-change
386 (not (y-or-n-p "Cabal file changed; restart GHCi process? ")))
387 (haskell-process-start (haskell-session))))))
388
389 ;;;###autoload
390 (defun haskell-process-load-file ()
391 "Load the current buffer file."
392 (interactive)
393 (save-buffer)
394 (haskell-interactive-mode-reset-error (haskell-session))
395 (haskell-process-file-loadish (concat "load " (buffer-file-name))
396 nil
397 (current-buffer)))
398
399 ;;;###autoload
400 (defun haskell-process-reload-file ()
401 "Re-load the current buffer file."
402 (interactive)
403 (save-buffer)
404 (haskell-interactive-mode-reset-error (haskell-session))
405 (haskell-process-file-loadish "reload" t nil))
406
407 ;;;###autoload
408 (defun haskell-process-load-or-reload (&optional toggle)
409 "Load or reload. Universal argument toggles which."
410 (interactive "P")
411 (if toggle
412 (progn (setq haskell-reload-p (not haskell-reload-p))
413 (message "%s (No action taken this time)"
414 (if haskell-reload-p
415 "Now running :reload."
416 "Now running :load <buffer-filename>.")))
417 (if haskell-reload-p (haskell-process-reload-file) (haskell-process-load-file))))
418
419 (defun haskell-process-file-loadish (command reload-p module-buffer)
420 "Run a loading-ish COMMAND that wants to pick up type errors
421 and things like that. RELOAD-P indicates whether the notification
422 should say 'reloaded' or 'loaded'. MODULE-BUFFER may be used
423 for various things, but is optional."
424 (let ((session (haskell-session)))
425 (haskell-session-current-dir session)
426 (when haskell-process-check-cabal-config-on-load
427 (haskell-process-look-config-changes session))
428 (let ((process (haskell-process)))
429 (haskell-process-queue-command
430 process
431 (make-haskell-command
432 :state (list session process command reload-p module-buffer)
433 :go (lambda (state)
434 (haskell-process-send-string
435 (cadr state) (format ":%s" (caddr state))))
436 :live (lambda (state buffer)
437 (haskell-process-live-build
438 (cadr state) buffer nil))
439 :complete (lambda (state response)
440 (haskell-process-load-complete
441 (car state)
442 (cadr state)
443 response
444 (cadddr state)
445 (cadddr (cdr state)))))))))
446
447 ;;;###autoload
448 (defun haskell-process-cabal-build ()
449 "Build the Cabal project."
450 (interactive)
451 (haskell-process-do-cabal "build")
452 (haskell-process-add-cabal-autogen))
453
454 ;;;###autoload
455 (defun haskell-process-cabal ()
456 "Prompts for a Cabal command to run."
457 (interactive)
458 (haskell-process-do-cabal
459 (funcall haskell-completing-read-function "Cabal command: "
460 haskell-cabal-commands)))
461
462 (defun haskell-process-add-cabal-autogen ()
463 "Add <cabal-project-dir>/dist/build/autogen/ to the ghci search
464 path. This allows modules such as 'Path_...', generated by cabal,
465 to be loaded by ghci."
466 (unless (eq 'cabal-repl haskell-process-type) ;; redundant with "cabal repl"
467 (let*
468 ((session (haskell-session))
469 (cabal-dir (haskell-session-cabal-dir session))
470 (ghci-gen-dir (format "%sdist/build/autogen/" cabal-dir)))
471 (haskell-process-queue-without-filters
472 (haskell-process)
473 (format ":set -i%s" ghci-gen-dir)))))
474
475 (defun haskell-process-do-cabal (command)
476 "Run a Cabal command."
477 (let ((process (haskell-process)))
478 (haskell-process-queue-command
479 process
480 (make-haskell-command
481 :state (list (haskell-session) process command 0)
482
483 :go
484 (lambda (state)
485 (haskell-process-send-string
486 (cadr state)
487 (format haskell-process-do-cabal-format-string
488 (haskell-session-cabal-dir (car state))
489 (format "%s %s"
490 (ecase haskell-process-type
491 ('ghci "cabal")
492 ('cabal-repl "cabal")
493 ('cabal-ghci "cabal")
494 ('cabal-dev "cabal-dev"))
495 (caddr state)))))
496
497 :live
498 (lambda (state buffer)
499 (let ((cmd (replace-regexp-in-string "^\\([a-z]+\\).*"
500 "\\1"
501 (caddr state))))
502 (cond ((or (string= cmd "build")
503 (string= cmd "install"))
504 (haskell-process-live-build (cadr state) buffer t))
505 (t
506 (haskell-process-cabal-live state buffer)))))
507
508 :complete
509 (lambda (state response)
510 (let* ((process (cadr state))
511 (session (haskell-process-session process))
512 (message-count 0)
513 (cursor (haskell-process-response-cursor process))
514 (haskell-imported-suggested (list)))
515 (haskell-process-set-response-cursor process 0)
516 (while (haskell-process-errors-warnings session process response)
517 (setq message-count (1+ message-count)))
518 (haskell-process-set-response-cursor process cursor)
519 (let ((msg (format "Complete: cabal %s (%s compiler messages)"
520 (caddr state)
521 message-count)))
522 (haskell-interactive-mode-echo session msg)
523 (haskell-mode-message-line msg)
524 (when (and haskell-notify-p
525 (fboundp 'notifications-notify))
526 (notifications-notify
527 :title (format "*%s*" (haskell-session-name (car state)))
528 :body msg
529 :app-name (ecase haskell-process-type
530 ('ghci "cabal")
531 ('cabal-repl "cabal")
532 ('cabal-ghci "cabal")
533 ('cabal-dev "cabal-dev"))
534 :app-icon haskell-process-logo
535 )))))))))
536
537 (defun haskell-process-cabal-live (state buffer)
538 "Do live updates for Cabal processes."
539 (haskell-interactive-mode-insert
540 (haskell-process-session (cadr state))
541 (replace-regexp-in-string
542 haskell-process-prompt-regex
543 ""
544 (substring buffer (cadddr state))))
545 (setf (cdddr state) (list (length buffer)))
546 nil)
547
548 (defun haskell-process-load-complete (session process buffer reload module-buffer &optional cont)
549 "Handle the complete loading response. BUFFER is the string of
550 text being sent over the process pipe. MODULE-BUFFER is the
551 actual Emacs buffer of the module being loaded."
552 (cond ((haskell-process-consume process "Ok, modules loaded: \\(.+\\)\\.$")
553 (let* ((modules (haskell-process-extract-modules buffer))
554 (cursor (haskell-process-response-cursor process)))
555 (haskell-process-set-response-cursor process 0)
556 (let ((warning-count 0))
557 (while (haskell-process-errors-warnings session process buffer)
558 (setq warning-count (1+ warning-count)))
559 (haskell-process-set-response-cursor process cursor)
560 (if (and (not reload)
561 haskell-process-reload-with-fbytecode)
562 (haskell-process-reload-with-fbytecode process module-buffer)
563 (haskell-process-import-modules process (car modules)))
564 (haskell-mode-message-line
565 (if reload "Reloaded OK." "OK."))
566 (when cont
567 (funcall cont t)))))
568 ((haskell-process-consume process "Failed, modules loaded: \\(.+\\)\\.$")
569 (let* ((modules (haskell-process-extract-modules buffer))
570 (cursor (haskell-process-response-cursor process))
571 (haskell-imported-suggested (list)))
572 (haskell-process-set-response-cursor process 0)
573 (while (haskell-process-errors-warnings session process buffer))
574 (haskell-process-set-response-cursor process cursor)
575 (if (and (not reload) haskell-process-reload-with-fbytecode)
576 (haskell-process-reload-with-fbytecode process module-buffer)
577 (haskell-process-import-modules process (car modules)))
578 (haskell-interactive-mode-compile-error session "Compilation failed.")
579 (when cont
580 (funcall cont nil))))))
581
582 (defun haskell-process-reload-with-fbytecode (process module-buffer)
583 "Reload FILE-NAME with -fbyte-code set, and then restore -fobject-code."
584 (haskell-process-queue-without-filters process ":set -fbyte-code")
585 (haskell-process-touch-buffer process module-buffer)
586 (haskell-process-queue-without-filters process ":reload")
587 (haskell-process-queue-without-filters process ":set -fobject-code"))
588
589 (defun haskell-process-touch-buffer (process buffer)
590 "Updates mtime on the file for BUFFER by queing a touch on
591 PROCESS."
592 (interactive)
593 (haskell-process-queue-command
594 process
595 (make-haskell-command
596 :state (cons process buffer)
597 :go (lambda (state)
598 (haskell-process-send-string
599 (car state)
600 (format ":!%s %s"
601 "touch"
602 (shell-quote-argument (buffer-file-name
603 (cdr state))))))
604 :complete (lambda (state _)
605 (with-current-buffer (cdr state)
606 (clear-visited-file-modtime))))))
607
608 (defun haskell-process-extract-modules (buffer)
609 "Extract the modules from the process buffer."
610 (let* ((modules-string (match-string 1 buffer))
611 (modules (split-string modules-string ", ")))
612 (cons modules modules-string)))
613
614 (defun haskell-process-import-modules (process modules)
615 "Import `modules' with :m +, and send any import statements
616 from `module-buffer'."
617 (when haskell-process-auto-import-loaded-modules
618 (haskell-process-queue-command
619 process
620 (make-haskell-command
621 :state (cons process modules)
622 :go (lambda (state)
623 (haskell-process-send-string
624 (car state)
625 (format ":m + %s" (mapconcat 'identity (cdr state) " "))))))))
626
627 (defun haskell-process-live-build (process buffer echo-in-repl)
628 "Show live updates for loading files."
629 (cond ((haskell-process-consume
630 process
631 (concat "\\[[ ]*\\([0-9]+\\) of \\([0-9]+\\)\\]"
632 " Compiling \\([^ ]+\\)[ ]+"
633 "( \\([^ ]+\\), \\([^ ]+\\) )[^\r\n]*[\r\n]+"))
634 (let ((session (haskell-process-session process))
635 (module-name (match-string 3 buffer))
636 (file-name (match-string 4 buffer)))
637 (haskell-interactive-show-load-message
638 session
639 'compiling
640 module-name
641 (haskell-session-strip-dir session file-name)
642 echo-in-repl))
643 t)
644 ((haskell-process-consume process "Loading package \\([^ ]+\\) ... linking ... done.\n")
645 (haskell-mode-message-line
646 (format "Loading: %s"
647 (match-string 1 buffer)))
648 t)
649 ((haskell-process-consume
650 process
651 "^Preprocessing executables for \\(.+?\\)\\.\\.\\.")
652 (let ((msg (format "Preprocessing: %s" (match-string 1 buffer))))
653 (haskell-interactive-mode-echo
654 (haskell-process-session process)
655 msg)
656 (haskell-mode-message-line msg)))
657 ((haskell-process-consume process "Linking \\(.+?\\) \\.\\.\\.")
658 (let ((msg (format "Linking: %s" (match-string 1 buffer))))
659 (haskell-interactive-mode-echo (haskell-process-session process) msg)
660 (haskell-mode-message-line msg)))
661 ((haskell-process-consume process "\nBuilding \\(.+?\\)\\.\\.\\.")
662 (let ((msg (format "Building: %s" (match-string 1 buffer))))
663 (haskell-interactive-mode-echo
664 (haskell-process-session process)
665 msg)
666 (haskell-mode-message-line msg)))))
667
668 (defun haskell-process-errors-warnings (session process buffer)
669 "Trigger handling type errors or warnings."
670 (cond
671 ((haskell-process-consume
672 process
673 (concat "[\r\n]\\([^ \r\n:][^:\n\r]+\\):\\([0-9]+\\):\\([0-9]+\\)\\(-[0-9]+\\)?:"
674 "[ \n\r]+\\([[:unibyte:][:nonascii:]]+?\\)\n[^ ]"))
675 (haskell-process-set-response-cursor process
676 (- (haskell-process-response-cursor process) 1))
677 (let* ((buffer (haskell-process-response process))
678 (file (match-string 1 buffer))
679 (line (string-to-number (match-string 2 buffer)))
680 (col (match-string 3 buffer))
681 (col2 (match-string 4 buffer))
682 (error-msg (match-string 5 buffer))
683 (warning (string-match "^Warning:" error-msg))
684 (final-msg (format "%s:%s:%s%s: %s"
685 (haskell-session-strip-dir session file)
686 line
687 col (or col2 "")
688 error-msg)))
689 (funcall (if warning
690 'haskell-interactive-mode-compile-warning
691 'haskell-interactive-mode-compile-error)
692 session final-msg)
693 (unless warning
694 (haskell-mode-message-line final-msg))
695 (haskell-process-trigger-suggestions session error-msg file line))
696 t)))
697
698 (defun haskell-process-trigger-suggestions (session msg file line)
699 "Trigger prompting to add any extension suggestions."
700 (cond ((let ((case-fold-search nil)) (string-match " -X\\([A-Z][A-Za-z]+\\)" msg))
701 (when haskell-process-suggest-language-pragmas
702 (haskell-process-suggest-pragma session "LANGUAGE" (match-string 1 msg) file)))
703 ((string-match " The \\(qualified \\)?import of[ ]`\\([^ ]+\\)' is redundant" msg)
704 (when haskell-process-suggest-remove-import-lines
705 (haskell-process-suggest-remove-import session
706 file
707 (match-string 2 msg)
708 line)))
709 ((string-match "Warning: orphan instance: " msg)
710 (when haskell-process-suggest-no-warn-orphans
711 (haskell-process-suggest-pragma session "OPTIONS" "-fno-warn-orphans" file)))
712 ((string-match "against inferred type `\\[Char\\]'" msg)
713 (when haskell-process-suggest-overloaded-strings
714 (haskell-process-suggest-pragma session "LANGUAGE" "OverloadedStrings" file)))
715 ((string-match "^Not in scope: .*`\\(.+\\)'$" msg)
716 (when haskell-process-suggest-hoogle-imports
717 (haskell-process-suggest-hoogle-imports session msg file)))
718 ((string-match "^[ ]+It is a member of the hidden package `\\(.+\\)'.$" msg)
719 (when haskell-process-suggest-add-package
720 (haskell-process-suggest-add-package session msg)))))
721
722 (defun haskell-process-suggest-add-package (session msg)
723 "Add the (matched) module to your cabal file."
724 (let* ((suggested-package (match-string 1 msg))
725 (package-name (replace-regexp-in-string "-[^-]+$" "" suggested-package))
726 (version (progn (string-match "\\([^-]+\\)$" suggested-package)
727 (match-string 1 suggested-package)))
728 (cabal-file (concat (haskell-session-name session)
729 ".cabal")))
730 (when (y-or-n-p
731 (format "Add `%s' to %s?"
732 package-name
733 cabal-file))
734 (haskell-process-add-dependency package-name version))))
735
736 (defun haskell-process-add-dependency (package &optional version no-prompt)
737 "Add PACKAGE (and optionally suffix -VERSION) to the cabal
738 file. Prompts the user before doing so."
739 (interactive
740 (list (read-from-minibuffer "Package entry: ")
741 nil
742 t))
743 (let ((buffer (current-buffer)))
744 (find-file (haskell-cabal-find-file))
745 (let ((entry (if no-prompt
746 package
747 (read-from-minibuffer "Package entry: "
748 (concat package
749 (if version
750 (concat " >= "
751 version)
752 ""))))))
753 (save-excursion
754 (goto-char (point-min))
755 (when (search-forward-regexp "^library$" nil t 1)
756 (search-forward-regexp "build-depends:[ ]+")
757 (let ((column (current-column)))
758 (when (y-or-n-p "Add to library?")
759 (insert entry ",\n")
760 (indent-to column))))
761 (goto-char (point-min))
762 (while (search-forward-regexp "^executable " nil t 1)
763 (let ((name (buffer-substring-no-properties (point) (line-end-position))))
764 (search-forward-regexp "build-depends:[ ]+")
765 (let ((column (current-column)))
766 (when (y-or-n-p (format "Add to executable `%s'?" name))
767 (insert entry ",\n")
768 (indent-to column)))))
769 (save-buffer)
770 (switch-to-buffer buffer)))))
771
772 (defun haskell-process-suggest-hoogle-imports (session msg file)
773 "Given an out of scope identifier, Hoogle for that identifier,
774 and if a result comes back, suggest to import that identifier
775 now."
776 (let* ((ident (let ((i (match-string 1 msg)))
777 (if (string-match "^[A-Z]\\.\\(.+\\)$" i)
778 (match-string 1 i)
779 i)))
780 (modules (haskell-process-hoogle-ident ident))
781 (module
782 (cond
783 ((> (length modules) 1)
784 (when (y-or-n-p (format "Identifier `%s' not in scope, choose module to import?"
785 ident))
786 (funcall haskell-completing-read-function "Module: " modules)))
787 ((= (length modules) 1)
788 (when (y-or-n-p (format "Identifier `%s' not in scope, import `%s'?"
789 ident
790 (car modules)))
791 (car modules))))))
792 (when module
793 (unless (member module haskell-imported-suggested)
794 (push module haskell-imported-suggested)
795 (haskell-process-find-file session file)
796 (save-excursion
797 (goto-char (point-max))
798 (haskell-navigate-imports)
799 (insert (read-from-minibuffer "Import line: " (concat "import " module))
800 "\n")
801 (haskell-sort-imports)
802 (haskell-align-imports))))))
803
804 (defun haskell-process-hoogle-ident (ident)
805 "Hoogle for IDENT, returns a list of modules."
806 (with-temp-buffer
807 (call-process "hoogle" nil t nil "search" "--exact" ident)
808 (goto-char (point-min))
809 (unless (or (looking-at "^No results found")
810 (looking-at "^package "))
811 (while (re-search-forward "^\\([^ ]+\\).*$" nil t)
812 (replace-match "\\1" nil nil))
813 (remove-if (lambda (a) (string= "" a))
814 (split-string (buffer-string)
815 "\n")))))
816
817 (defun haskell-process-suggest-remove-import (session file import line)
818 "Suggest removing or commenting out IMPORT on LINE."
819 (case (read-key (propertize (format "The import line `%s' is redundant. Remove? (y, n, c: comment out) "
820 import)
821 'face 'minibuffer-prompt))
822 (?y
823 (haskell-process-find-file session file)
824 (save-excursion
825 (goto-char (point-min))
826 (forward-line (1- line))
827 (goto-char (line-beginning-position))
828 (delete-region (line-beginning-position)
829 (line-end-position))))
830 (?c
831 (haskell-process-find-file session file)
832 (save-excursion
833 (goto-char (point-min))
834 (forward-line (1- line))
835 (goto-char (line-beginning-position))
836 (insert "-- ")))))
837
838 (defun haskell-process-suggest-pragma (session pragma extension file)
839 "Suggest to add something to the top of the file."
840 (let ((string (format "{-# %s %s #-}" pragma extension)))
841 (when (y-or-n-p (format "Add %s to the top of the file? " string))
842 (haskell-process-find-file session file)
843 (save-excursion
844 (goto-char (point-min))
845 (insert (concat string "\n"))))))
846
847 (defun haskell-process-find-file (session file)
848 "Find the given file in the project."
849 (find-file (cond ((file-exists-p (concat (haskell-session-current-dir session) "/" file))
850 (concat (haskell-session-current-dir session) "/" file))
851 ((file-exists-p (concat (haskell-session-cabal-dir session) "/" file))
852 (concat (haskell-session-cabal-dir session) "/" file))
853 (t file))))
854
855 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
856 ;; Building the process
857
858 ;;;###autoload
859 (defun haskell-process-start (session)
860 "Start the inferior Haskell process."
861 (let ((existing-process (get-process (haskell-session-name (haskell-session)))))
862 (when (processp existing-process)
863 (haskell-interactive-mode-echo session "Restarting process ...")
864 (haskell-process-set (haskell-session-process session) 'is-restarting t)
865 (delete-process existing-process)))
866 (let ((process (or (haskell-session-process session)
867 (haskell-process-make (haskell-session-name session))))
868 (old-queue (haskell-process-get (haskell-session-process session)
869 'command-queue)))
870 (haskell-session-set-process session process)
871 (haskell-process-set-session process session)
872 (haskell-process-set-cmd process nil)
873 (haskell-process-set (haskell-session-process session) 'is-restarting nil)
874 (let ((default-directory (haskell-session-cabal-dir session)))
875 (haskell-session-pwd session)
876 (haskell-process-set-process
877 process
878 (ecase haskell-process-type
879 ('ghci
880 (haskell-process-log (format "Starting inferior GHCi process %s ..."
881 haskell-process-path-ghci))
882 (apply #'start-process
883 (append (list (haskell-session-name session)
884 nil
885 haskell-process-path-ghci)
886 haskell-process-args-ghci)))
887 ('cabal-repl
888 (haskell-process-log (format "Starting inferior `cabal repl' process using %s ..."
889 haskell-process-path-cabal))
890
891 (apply #'start-process
892 (append (list (haskell-session-name session)
893 nil
894 haskell-process-path-cabal)
895 '("repl") haskell-process-args-cabal-repl
896 (let ((target (haskell-session-target session)))
897 (if target (list target) nil)))))
898 ('cabal-ghci
899 (haskell-process-log (format "Starting inferior cabal-ghci process using %s ..."
900 haskell-process-path-cabal-ghci))
901 (start-process (haskell-session-name session)
902 nil
903 haskell-process-path-cabal-ghci))
904 ('cabal-dev
905 (let ((dir (concat (haskell-session-cabal-dir session)
906 "/cabal-dev")))
907 (haskell-process-log (format "Starting inferior cabal-dev process %s -s %s ..."
908 haskell-process-path-cabal-dev
909 dir))
910 (start-process (haskell-session-name session)
911 nil
912 haskell-process-path-cabal-dev
913 "ghci"
914 "-s"
915 dir))))))
916 (progn (set-process-sentinel (haskell-process-process process) 'haskell-process-sentinel)
917 (set-process-filter (haskell-process-process process) 'haskell-process-filter))
918 (haskell-process-send-startup process)
919 (unless (eq 'cabal-repl haskell-process-type) ;; "cabal repl" sets the proper CWD
920 (haskell-process-change-dir session
921 process
922 (haskell-session-current-dir session)))
923 (haskell-process-set process 'command-queue
924 (append (haskell-process-get (haskell-session-process session)
925 'command-queue)
926 old-queue))
927 process))
928
929 (defun haskell-process-clear ()
930 "Clear the current process."
931 (interactive)
932 (haskell-process-reset (haskell-process))
933 (haskell-process-set (haskell-process) 'command-queue nil))
934
935 (defun haskell-process-restart ()
936 "Restart the inferior Haskell process."
937 (interactive)
938 (haskell-process-reset (haskell-process))
939 (haskell-process-set (haskell-process) 'command-queue nil)
940 (haskell-process-start (haskell-session)))
941
942 (defun haskell-kill-session-process (&optional session)
943 "Kill the process."
944 (interactive)
945 (let* ((session (or session (haskell-session)))
946 (existing-process (get-process (haskell-session-name session))))
947 (when (processp existing-process)
948 (haskell-interactive-mode-echo session "Killing process ...")
949 (haskell-process-set (haskell-session-process session) 'is-restarting t)
950 (delete-process existing-process))))
951
952 (defun haskell-process-make (name)
953 "Make an inferior Haskell process."
954 (list (cons 'name name)))
955
956 ;;;###autoload
957 (defun haskell-process ()
958 "Get the current process from the current session."
959 (haskell-session-process (haskell-session)))
960
961 (defun haskell-process-interrupt ()
962 "Interrupt the process (SIGINT)."
963 (interactive)
964 (interrupt-process (haskell-process-process (haskell-process))))
965
966 (defun haskell-process-cd (&optional not-interactive)
967 "Change directory."
968 (interactive)
969 (let* ((session (haskell-session))
970 (dir (haskell-session-pwd session t)))
971 (haskell-process-log (format "Changing directory to %s ...\n" dir))
972 (haskell-process-change-dir session
973 (haskell-process)
974 dir)))
975
976 (defun haskell-session-pwd (session &optional change)
977 "Prompt for the current directory."
978 (or (unless change
979 (haskell-session-get session 'current-dir))
980 (progn (haskell-session-set-current-dir
981 session
982 (haskell-utils-read-directory-name
983 (if change "Change directory: " "Set current directory: ")
984 (or (haskell-session-get session 'current-dir)
985 (haskell-session-get session 'cabal-dir)
986 (if (buffer-file-name)
987 (file-name-directory (buffer-file-name))
988 "~/"))))
989 (haskell-session-get session 'current-dir))))
990
991 (defun haskell-process-change-dir (session process dir)
992 "Change the directory of the current process."
993 (haskell-process-queue-command
994 process
995 (make-haskell-command
996 :state (list session process dir)
997
998 :go
999 (lambda (state)
1000 (haskell-process-send-string
1001 (cadr state) (format ":cd %s" (caddr state))))
1002
1003 :complete
1004 (lambda (state _)
1005 (haskell-session-set-current-dir (car state) (caddr state))
1006 (haskell-interactive-mode-echo (car state)
1007 (format "Changed directory: %s"
1008 (caddr state)))))))
1009
1010 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1011 ;; Process communication
1012
1013 (defun haskell-process-send-startup (process)
1014 "Send the necessary start messages."
1015 (haskell-process-queue-command
1016 process
1017 (make-haskell-command
1018 :state process
1019
1020 :go (lambda (process)
1021 (haskell-process-send-string process ":set prompt \"> \"")
1022 (haskell-process-send-string process "Prelude.putStrLn \"\"")
1023 (haskell-process-send-string process ":set -v1"))
1024
1025 :live (lambda (process buffer)
1026 (when (haskell-process-consume
1027 process
1028 "^\*\*\* WARNING: \\(.+\\) is writable by someone else, IGNORING!$")
1029 (let ((path (match-string 1 buffer)))
1030 (haskell-session-modify
1031 (haskell-process-session process)
1032 'ignored-files
1033 (lambda (files)
1034 (remove-duplicates (cons path files) :test 'string=)))
1035 (haskell-interactive-mode-compile-warning
1036 (haskell-process-session process)
1037 (format "GHCi is ignoring: %s (run M-x haskell-process-unignore)"
1038 path)))))
1039
1040 :complete (lambda (process _)
1041 (haskell-interactive-mode-echo
1042 (haskell-process-session process)
1043 (concat (nth (random (length haskell-process-greetings))
1044 haskell-process-greetings)
1045 (when haskell-process-show-debug-tips
1046 "
1047 If I break, you can:
1048 1. Restart: M-x haskell-process-restart
1049 2. Configure logging: C-h v haskell-process-log (useful for debugging)
1050 3. General config: M-x customize-mode
1051 4. Hide these tips: C-h v haskell-process-show-debug-tips")))))))
1052
1053 (defun haskell-process-sentinel (proc event)
1054 "The sentinel for the process pipe."
1055 (let ((session (haskell-process-project-by-proc proc)))
1056 (when session
1057 (let* ((process (haskell-session-process session)))
1058 (unless (haskell-process-restarting process)
1059 (haskell-process-log (format "Event: %S\n" event))
1060 (haskell-process-log "Process reset.\n")
1061 (haskell-process-prompt-restart process))))))
1062
1063 (defun haskell-process-filter (proc response)
1064 "The filter for the process pipe."
1065 (haskell-process-log (format "<- %S\n" response))
1066 (let ((session (haskell-process-project-by-proc proc)))
1067 (when session
1068 (when (haskell-process-cmd (haskell-session-process session))
1069 (haskell-process-collect session
1070 response
1071 (haskell-session-process session))))))
1072
1073 (defun haskell-process-log (msg)
1074 "Write MSG to the process log (if enabled)."
1075 (when haskell-process-log
1076 (with-current-buffer (get-buffer-create "*haskell-process-log*")
1077 (goto-char (point-max))
1078 (insert msg))))
1079
1080 (defun haskell-process-project-by-proc (proc)
1081 "Find project by process."
1082 (find-if (lambda (project)
1083 (string= (haskell-session-name project)
1084 (process-name proc)))
1085 haskell-sessions))
1086
1087 (defun haskell-process-collect (session response process)
1088 "Collect input for the response until receives a prompt."
1089 (haskell-process-set-response process
1090 (concat (haskell-process-response process) response))
1091 (while (haskell-process-live-updates process))
1092 (when (string-match haskell-process-prompt-regex
1093 (haskell-process-response process))
1094 (haskell-command-exec-complete
1095 (haskell-process-cmd process)
1096 (replace-regexp-in-string
1097 haskell-process-prompt-regex
1098 ""
1099 (haskell-process-response process)))
1100 (haskell-process-reset process)
1101 (haskell-process-trigger-queue process)))
1102
1103 (defun haskell-process-reset (process)
1104 "Reset the process's state, ready for the next send/reply."
1105 (progn (haskell-process-set-response-cursor process 0)
1106 (haskell-process-set-response process "")
1107 (haskell-process-set-cmd process nil)))
1108
1109 (defun haskell-process-consume (process regex)
1110 "Consume a regex from the response and move the cursor along if succeed."
1111 (when (string-match regex
1112 (haskell-process-response process)
1113 (haskell-process-response-cursor process))
1114 (haskell-process-set-response-cursor process (match-end 0))
1115 t))
1116
1117 (defun haskell-process-send-string (process string)
1118 "Try to send a string to the process's process. Ask to restart if it's not running."
1119 (let ((child (haskell-process-process process)))
1120 (if (equal 'run (process-status child))
1121 (let ((out (concat string "\n")))
1122 (haskell-process-log (format "-> %S\n" out))
1123 (process-send-string child out))
1124 (unless (haskell-process-restarting process)
1125 (haskell-process-prompt-restart process)))))
1126
1127 (defun haskell-process-prompt-restart (process)
1128 "Prompt to restart the died process."
1129 (when (y-or-n-p (format "The Haskell process `%s' has died. Restart? "
1130 (haskell-process-name process)))
1131 (haskell-process-start (haskell-process-session process))))
1132
1133 (defun haskell-process-live-updates (process)
1134 "Process live updates."
1135 (haskell-command-exec-live (haskell-process-cmd process)
1136 (haskell-process-response process)))
1137
1138 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1139 ;; Making commands
1140
1141 (defun haskell-process-queue-without-filters (process line)
1142 "Queue LINE to be sent to PROCESS without bothering to look at
1143 the response."
1144 (haskell-process-queue-command
1145 process
1146 (make-haskell-command
1147 :state (cons process line)
1148 :go (lambda (state)
1149 (haskell-process-send-string (car state)
1150 (cdr state))))))
1151
1152 (defun haskell-process-queue-command (process command)
1153 "Add a command to the process command queue."
1154 (haskell-process-cmd-queue-add process command)
1155 (haskell-process-trigger-queue process))
1156
1157 (defun haskell-process-trigger-queue (process)
1158 "Trigger the next command in the queue to be ran if there is no current command."
1159 (if (and (haskell-process-process process)
1160 (process-live-p (haskell-process-process process)))
1161 (unless (haskell-process-cmd process)
1162 (let ((cmd (haskell-process-cmd-queue-pop process)))
1163 (when cmd
1164 (haskell-process-set-cmd process cmd)
1165 (haskell-command-exec-go cmd))))
1166 (progn (haskell-process-reset process)
1167 (haskell-process-set (haskell-process) 'command-queue nil)
1168 (haskell-process-prompt-restart process))))
1169
1170 (defun haskell-process-queue-flushed-p (process)
1171 "Return t if command queue has been completely processed."
1172 (not (or (haskell-process-cmd-queue process)
1173 (haskell-process-cmd process))))
1174
1175 (defun haskell-process-queue-flush (process)
1176 "Block till PROCESS' command queue has been completely processed.
1177 This uses `accept-process-output' internally."
1178 (while (not (haskell-process-queue-flushed-p process))
1179 (haskell-process-trigger-queue process)
1180 (accept-process-output (haskell-process-process process) 1)))
1181
1182 (defun haskell-process-queue-sync-request (process reqstr)
1183 "Queue submitting REQSTR to PROCESS and return response blockingly."
1184 (let ((cmd (make-haskell-command
1185 :state (cons nil process)
1186 :go `(lambda (s) (haskell-process-send-string (cdr s) ,reqstr))
1187 :complete 'setcar)))
1188 (haskell-process-queue-command process cmd)
1189 (haskell-process-queue-flush process)
1190 (car-safe (haskell-command-state cmd))))
1191
1192 (defun haskell-process-get-repl-completions (process inputstr)
1193 "Perform `:complete repl ...' query for INPUTSTR using PROCESS."
1194 (let* ((reqstr (concat ":complete repl "
1195 (haskell-str-literal-encode inputstr)))
1196 (rawstr (haskell-process-queue-sync-request process reqstr)))
1197 (if (string-prefix-p "unknown command " rawstr)
1198 (error "GHCi lacks `:complete' support")
1199 (let* ((s1 (split-string rawstr "\r?\n"))
1200 (cs (mapcar #'haskell-str-literal-decode (cdr s1)))
1201 (h0 (car s1))) ;; "<cnt1> <cnt2> <quoted-str>"
1202 (unless (string-match "\\`\\([0-9]+\\) \\([0-9]+\\) \\(\".*\"\\)\\'" h0)
1203 (error "Invalid `:complete' response"))
1204 (let ((cnt1 (match-string 1 h0))
1205 (h1 (haskell-str-literal-decode (match-string 3 h0))))
1206 (unless (= (string-to-number cnt1) (length cs))
1207 (error "Lengths inconsistent in `:complete' reponse"))
1208 (cons h1 cs))))))
1209
1210 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1211 ;; Accessing the process
1212
1213 (defun haskell-process-get (process key)
1214 "Get the PROCESS's KEY value.
1215 Returns nil if KEY not set."
1216 (cdr (assq key process)))
1217
1218 (defun haskell-process-set (process key value)
1219 "Set the PROCESS's KEY to VALUE.
1220 Returns newly set VALUE."
1221 (if process
1222 (let ((cell (assq key process)))
1223 (if cell
1224 (setcdr cell value) ; modify cell in-place
1225 (setcdr process (cons (cons key value) (cdr process))) ; new cell
1226 value))
1227 (display-warning 'haskell-interactive
1228 "`haskell-process-set' called with nil process")))
1229
1230 ;; Wrappers using haskell-process-{get,set}
1231
1232 (defun haskell-process-set-process (p v)
1233 "Set the process's inferior process."
1234 (haskell-process-set p 'inferior-process v))
1235
1236 (defun haskell-process-process (p)
1237 "Get the process child."
1238 (haskell-process-get p 'inferior-process))
1239
1240 (defun haskell-process-name (p)
1241 "Get the process name."
1242 (haskell-process-get p 'name))
1243
1244 (defun haskell-process-cmd (p)
1245 "Get the process's current command.
1246 Return nil if no current command."
1247 (haskell-process-get p 'current-command))
1248
1249 (defun haskell-process-set-cmd (p v)
1250 "Set the process's current command."
1251 (haskell-process-set p 'current-command v))
1252
1253 (defun haskell-process-response (p)
1254 "Get the process's current response."
1255 (haskell-process-get p 'current-response))
1256
1257 (defun haskell-process-session (p)
1258 "Get the process's current session."
1259 (haskell-process-get p 'session))
1260
1261 (defun haskell-process-set-response (p v)
1262 "Set the process's current response."
1263 (haskell-process-set p 'current-response v))
1264
1265 (defun haskell-process-set-session (p v)
1266 "Set the process's current session."
1267 (haskell-process-set p 'session v))
1268
1269 (defun haskell-process-response-cursor (p)
1270 "Get the process's current response cursor."
1271 (haskell-process-get p 'current-response-cursor))
1272
1273 (defun haskell-process-set-response-cursor (p v)
1274 "Set the process's response cursor."
1275 (haskell-process-set p 'current-response-cursor v))
1276
1277 ;; low-level command queue operations
1278
1279 (defun haskell-process-restarting (process)
1280 "Is the PROCESS restarting?"
1281 (haskell-process-get process 'is-restarting))
1282
1283 (defun haskell-process-cmd-queue (process)
1284 "Get the PROCESS' command queue.
1285 New entries get added to the end of the list. Use
1286 `haskell-process-cmd-queue-add' and
1287 `haskell-process-cmd-queue-pop' to modify the command queue."
1288 (haskell-process-get process 'command-queue))
1289
1290 (defun haskell-process-cmd-queue-add (process cmd)
1291 "Add CMD to end of PROCESS's command queue."
1292 (check-type cmd haskell-command)
1293 (haskell-process-set process
1294 'command-queue
1295 (append (haskell-process-cmd-queue process)
1296 (list cmd))))
1297
1298 (defun haskell-process-cmd-queue-pop (process)
1299 "Pop the PROCESS' next entry from command queue.
1300 Returns nil if queue is empty."
1301 (let ((queue (haskell-process-cmd-queue process)))
1302 (when queue
1303 (haskell-process-set process 'command-queue (cdr queue))
1304 (car queue))))
1305
1306 (defun haskell-process-unignore ()
1307 "Unignore any files that were specified as being ignored by the
1308 inferior GHCi process."
1309 (interactive)
1310 (let ((session (haskell-session))
1311 (changed nil))
1312 (if (null (haskell-session-get session
1313 'ignored-files))
1314 (message "Nothing to unignore!")
1315 (loop for file in (haskell-session-get session
1316 'ignored-files)
1317 do (case (read-key
1318 (propertize (format "Set permissions? %s (y, n, v: stop and view file)"
1319 file)
1320 'face 'minibuffer-prompt))
1321 (?y
1322 (haskell-process-unignore-file session file)
1323 (setq changed t))
1324 (?v
1325 (find-file file)
1326 (return))))
1327 (when (and changed
1328 (y-or-n-p "Restart GHCi process now? "))
1329 (haskell-process-restart)))))
1330
1331 (defun haskell-process-reload-devel-main ()
1332 "Reload the module `DevelMain' and then run
1333 `DevelMain.update'. This is for doing live update of the code of
1334 servers or GUI applications. Put your development version of the
1335 program in `DevelMain', and define `update' to auto-start the
1336 program on a new thread, and use the `foreign-store' package to
1337 access the running context across :load/:reloads in GHCi."
1338 (interactive)
1339 (with-current-buffer (get-buffer "DevelMain.hs")
1340 (let ((session (haskell-session)))
1341 (let ((process (haskell-process)))
1342 (haskell-process-queue-command
1343 process
1344 (make-haskell-command
1345 :state (list :session session
1346 :process process
1347 :buffer (current-buffer))
1348 :go (lambda (state)
1349 (haskell-process-send-string (plist-get state ':process)
1350 ":l DevelMain"))
1351 :live (lambda (state buffer)
1352 (haskell-process-live-build (plist-get state ':process)
1353 buffer
1354 nil))
1355 :complete (lambda (state response)
1356 (haskell-process-load-complete
1357 (plist-get state ':session)
1358 (plist-get state ':process)
1359 response
1360 nil
1361 (plist-get state ':buffer)
1362 (lambda (ok)
1363 (when ok
1364 (haskell-process-queue-without-filters
1365 (haskell-process)
1366 "DevelMain.update")
1367 (message "DevelMain updated.")))))))))))
1368
1369 (defun haskell-process-unignore-file (session file)
1370 "
1371
1372 Note to Windows Emacs hackers:
1373
1374 chmod is how to change the mode of files in POSIX
1375 systems. This will not work on your operating
1376 system.
1377
1378 There is a command a bit like chmod called \"Calcs\"
1379 that you can try using here:
1380
1381 http://technet.microsoft.com/en-us/library/bb490872.aspx
1382
1383 If it works, you can submit a patch to this
1384 function and remove this comment.
1385 "
1386 (shell-command (read-from-minibuffer "Permissions command: "
1387 (concat "chmod 700 "
1388 file)))
1389 (haskell-session-modify
1390 (haskell-session)
1391 'ignored-files
1392 (lambda (files)
1393 (remove-if (lambda (path)
1394 (string= path file))
1395 files))))
1396
1397 (defun haskell-command-exec-go (command)
1398 "Call the command's go function."
1399 (let ((go-func (haskell-command-go command)))
1400 (when go-func
1401 (funcall go-func (haskell-command-state command)))))
1402
1403 (defun haskell-command-exec-complete (command response)
1404 "Call the command's complete function."
1405 (let ((comp-func (haskell-command-complete command)))
1406 (when comp-func
1407 (funcall comp-func
1408 (haskell-command-state command)
1409 response))))
1410
1411 (defun haskell-command-exec-live (command response)
1412 "Trigger the command's live updates callback."
1413 (let ((live-func (haskell-command-live command)))
1414 (when live-func
1415 (funcall live-func
1416 (haskell-command-state command)
1417 response))))
1418
1419 (provide 'haskell-process)
1420
1421 ;; Local Variables:
1422 ;; byte-compile-warnings: (not cl-functions)
1423 ;; End:
1424
1425 ;;; haskell-process.el ends here