Mercurial > hg > Members > kokubo > emacs
diff .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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.emacs.d/haskell-mode/haskell-process.el Mon Apr 21 04:30:59 2014 +0900 @@ -0,0 +1,1425 @@ +;;; haskell-process.el --- Communicating with the inferior Haskell process + +;; Copyright (C) 2011-2012 Chris Done + +;; Author: Chris Done <chrisdone@gmail.com> + +;; This file is not part of GNU Emacs. + +;; This file is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 3, or (at your option) +;; any later version. + +;; This file is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to +;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: + +;;; Todo: + +;;; Code: + +(require 'haskell-mode) +(require 'haskell-session) +(require 'haskell-compat) +(require 'haskell-str) +(require 'haskell-utils) +(require 'haskell-presentation-mode) +(require 'haskell-navigate-imports) +(with-no-warnings (require 'cl)) + +;; FIXME: haskell-process shouldn't depend on haskell-interactive-mode to avoid module-dep cycles +(declare-function haskell-interactive-mode-echo "haskell-interactive-mode" (session message &optional mode)) +(declare-function haskell-interactive-mode-compile-error "haskell-interactive-mode" (session message)) +(declare-function haskell-interactive-mode-compile-warning "haskell-interactive-mode" (session message)) +(declare-function haskell-interactive-mode-insert "haskell-interactive-mode" (session message)) +(declare-function haskell-interactive-mode-reset-error "haskell-interactive-mode" (session)) +(declare-function haskell-interactive-show-load-message "haskell-interactive-mode" (session type module-name file-name echo)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +(defgroup haskell-interactive nil + "Settings for REPL interaction via `haskell-interactive-mode'" + :link '(custom-manual "(haskell-mode)haskell-interactive-mode") + :group 'haskell) + +(defcustom haskell-process-path-ghci + "ghci" + "The path for starting ghci." + :group 'haskell-interactive + :type '(choice string (repeat string))) + +(defcustom haskell-process-path-cabal + "cabal" + "Path to the `cabal' executable." + :group 'haskell-interactive + :type '(choice string (repeat string))) + +(defcustom haskell-process-path-cabal-ghci + "cabal-ghci" + "The path for starting cabal-ghci." + :group 'haskell-interactive + :type '(choice string (repeat string))) + +(defcustom haskell-process-path-cabal-dev + "cabal-dev" + "The path for starting cabal-dev." + :group 'haskell-interactive + :type '(choice string (repeat string))) + +(defcustom haskell-process-args-ghci + '("-ferror-spans") + "Any arguments for starting ghci." + :group 'haskell-interactive + :type '(repeat (string :tag "Argument"))) + +(defcustom haskell-process-args-cabal-repl + '("--ghc-option=-ferror-spans") + "Additional arguments to for `cabal repl' invocation. +Note: The settings in `haskell-process-path-ghci' and +`haskell-process-args-ghci' are not automatically reused as `cabal repl' +currently invokes `ghc --interactive'. Use +`--with-ghc=<path-to-executable>' if you want to use a different +interactive GHC frontend; use `--ghc-option=<ghc-argument>' to +pass additional flags to `ghc'." + :group 'haskell-interactive + :type '(repeat (string :tag "Argument"))) + +(defcustom haskell-process-do-cabal-format-string + ":!cd %s && %s" + "The way to run cabal comands. It takes two arguments -- the directory and the command. +See `haskell-process-do-cabal' for more details." + :group 'haskell-interactive + :type 'string) + +(defcustom haskell-process-type + 'ghci + "The inferior Haskell process type to use." + :type '(choice (const ghci) (const cabal-repl) (const cabal-dev) (const cabal-ghci)) + :group 'haskell-interactive) + +(defcustom haskell-process-log + nil + "Enable debug logging to \"*haskell-process-log*\" buffer." + :type 'boolean + :group 'haskell-interactive) + +(defcustom haskell-process-show-debug-tips + t + "Show debugging tips when starting the process." + :type 'boolean + :group 'haskell-interactive) + +(defcustom haskell-notify-p + nil + "Notify using notifications.el (if loaded)?" + :type 'boolean + :group 'haskell-interactive) + +(defcustom haskell-process-suggest-no-warn-orphans + t + "Suggest adding -fno-warn-orphans pragma to file when getting orphan warnings." + :type 'boolean + :group 'haskell-interactive) + +(defcustom haskell-process-suggest-hoogle-imports + nil + "Suggest to add import statements using Hoogle as a backend." + :type 'boolean + :group 'haskell-interactive) + +(defcustom haskell-process-suggest-add-package + t + "Suggest to add packages to your .cabal file when Cabal says it +is a member of the hidden package, blah blah." + :type 'boolean + :group 'haskell-interactive) + +(defcustom haskell-process-suggest-language-pragmas + t + "Suggest adding LANGUAGE pragmas recommended by GHC." + :type 'boolean + :group 'haskell-interactive) + +(defcustom haskell-process-suggest-remove-import-lines + nil + "Suggest removing import lines as warned by GHC." + :type 'boolean + :group 'haskell-interactive) + +(defcustom haskell-process-suggest-overloaded-strings + t + "Suggest adding OverloadedStrings pragma to file when getting type mismatches with [Char]." + :type 'boolean + :group 'haskell-interactive) + +(defcustom haskell-process-check-cabal-config-on-load + t + "Check changes cabal config on loading Haskell files and +restart the GHCi process if changed.." + :type 'boolean + :group 'haskell-interactive) + +(defcustom haskell-process-prompt-restart-on-cabal-change + t + "Ask whether to restart the GHCi process when the Cabal file +has changed?" + :type 'boolean + :group 'haskell-interactive) + +(defcustom haskell-process-auto-import-loaded-modules + nil + "Auto import the modules reported by GHC to have been loaded?" + :type 'boolean + :group 'haskell-interactive) + +(defcustom haskell-process-reload-with-fbytecode + nil + "When using -fobject-code, auto reload with -fbyte-code (and +then restore the -fobject-code) so that all module info and +imports become available?" + :type 'boolean + :group 'haskell-interactive) + +(defcustom haskell-process-use-presentation-mode + nil + "Use presentation mode to show things like type info instead of + printing to the message area." + :type 'boolean + :group 'haskell-interactive) + +(defvar haskell-imported-suggested nil) +(defvar haskell-process-prompt-regex "\\(^[> ]*> $\\|\n[> ]*> $\\)") +(defvar haskell-reload-p nil) + +(defvar haskell-process-greetings + (list "Hello, Haskell!" + "The lambdas must flow." + "Hours of hacking await!" + "The next big Haskell project is about to start!" + "Your wish is my IO ().") + "Greetings for when the Haskell process starts up.") + +(defconst haskell-process-logo + (expand-file-name "logo.svg" haskell-mode-pkg-base-dir) + "Haskell logo for notifications.") + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Accessing commands -- using cl 'defstruct' +(defstruct haskell-command + "Data structure representing a command to be executed when with + a custom state and three callback." + ;; hold the custom command state + ;; state :: a + state + ;; called when to execute a command + ;; go :: a -> () + go + ;; called whenever output was collected from the haskell process + ;; live :: a -> Response -> Bool + live + ;; called when the output from the haskell process indicates that the command + ;; is complete + ;; complete :: a -> Response -> () + complete) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Accessing commands + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Specialised commands + +;;;###autoload +(defun haskell-process-generate-tags (&optional and-then-find-this-tag) + "Regenerate the TAGS table." + (interactive) + (let ((process (haskell-process))) + (haskell-process-queue-command + process + (make-haskell-command + :state (cons process and-then-find-this-tag) + :go (lambda (state) + (haskell-process-send-string + (car state) + (format ":!cd %s && %s | %s | %s" + (haskell-session-cabal-dir + (haskell-process-session (car state))) + "find . -name '*.hs*'" + "grep -v '#'" ; To avoid Emacs back-up files. Yeah. + "xargs hasktags -e -x"))) + :complete (lambda (state response) + (when (cdr state) + (let ((tags-file-name + (haskell-session-tags-filename + (haskell-process-session (car state))))) + (find-tag (cdr state)))) + (haskell-mode-message-line "Tags generated.")))))) + +;;;###autoload +(defun haskell-process-do-type (&optional insert-value) + "Print the type of the given expression." + (interactive "P") + (if insert-value + (haskell-process-insert-type) + (haskell-process-do-simple-echo + (let ((ident (haskell-ident-at-point))) + ;; TODO: Generalize all these `string-match' of ident calls into + ;; one function. + (format (if (string-match "^[_[:lower:][:upper:]]" ident) + ":type %s" + ":type (%s)") + ident)) + 'haskell-mode))) + +(defun haskell-process-insert-type () + "Get the identifer at the point and insert its type, if +possible, using GHCi's :type." + (let ((process (haskell-process)) + (query (let ((ident (haskell-ident-at-point))) + (format (if (string-match "^[_[:lower:][:upper:]]" ident) + ":type %s" + ":type (%s)") + ident)))) + (haskell-process-queue-command + process + (make-haskell-command + :state (list process query (current-buffer)) + :go (lambda (state) + (haskell-process-send-string (nth 0 state) + (nth 1 state))) + :complete (lambda (state response) + (cond + ;; TODO: Generalize this into a function. + ((or (string-match "^Top level" response) + (string-match "^<interactive>" response)) + (message response)) + (t + (with-current-buffer (nth 2 state) + (goto-char (line-beginning-position)) + (insert (format "%s\n" response)))))))))) + +;;;###autoload +(defun haskell-process-do-info (&optional prompt-value) + "Print info on the identifier at point. +If PROMPT-VALUE is non-nil, request identifier via mini-buffer." + (interactive "P") + (haskell-process-do-simple-echo + (let ((ident (if prompt-value + (read-from-minibuffer "Info: " (haskell-ident-at-point)) + (haskell-ident-at-point))) + (modname (unless prompt-value + (haskell-utils-parse-import-statement-at-point)))) + (if modname + (format ":browse! %s" modname) + (format (if (string-match "^[a-zA-Z_]" ident) + ":info %s" + ":info (%s)") + (or ident + (haskell-ident-at-point))))) + 'haskell-mode)) + +(defun haskell-process-do-try-info (sym) + "Get info of `sym' and echo in the minibuffer." + (let ((process (haskell-process))) + (haskell-process-queue-command + process + (make-haskell-command + :state (cons process sym) + :go (lambda (state) + (haskell-process-send-string + (car state) + (if (string-match "^[A-Za-z_]" (cdr state)) + (format ":info %s" (cdr state)) + (format ":info (%s)" (cdr state))))) + :complete (lambda (state response) + (unless (or (string-match "^Top level" response) + (string-match "^<interactive>" response)) + (haskell-mode-message-line response))))))) + +(defun haskell-process-do-simple-echo (line &optional mode) + "Send LINE to the GHCi process and echo the result in some +fashion, such as printing in the minibuffer, or using +haskell-present, depending on configuration." + (let ((process (haskell-process))) + (haskell-process-queue-command + process + (make-haskell-command + :state (list process line mode) + :go (lambda (state) + (haskell-process-send-string (car state) (cadr state))) + :complete (lambda (state response) + ;; TODO: TBD: don't do this if + ;; `haskell-process-use-presentation-mode' is t. + (haskell-interactive-mode-echo + (haskell-process-session (car state)) + response + (caddr state)) + (if haskell-process-use-presentation-mode + (progn (haskell-present (cadr state) + (haskell-process-session (car state)) + response) + (haskell-session-assign + (haskell-process-session (car state)))) + (haskell-mode-message-line response))))))) + +(defun haskell-process-look-config-changes (session) + "Checks whether a cabal configuration file has +changed. Restarts the process if that is the case." + (let ((current-checksum (haskell-session-get session 'cabal-checksum)) + (new-checksum (haskell-cabal-compute-checksum + (haskell-session-get session 'cabal-dir)))) + (when (not (string= current-checksum new-checksum)) + (haskell-interactive-mode-echo session (format "Cabal file changed: %s" new-checksum)) + (haskell-session-set-cabal-checksum session + (haskell-session-get session 'cabal-dir)) + (unless (and haskell-process-prompt-restart-on-cabal-change + (not (y-or-n-p "Cabal file changed; restart GHCi process? "))) + (haskell-process-start (haskell-session)))))) + +;;;###autoload +(defun haskell-process-load-file () + "Load the current buffer file." + (interactive) + (save-buffer) + (haskell-interactive-mode-reset-error (haskell-session)) + (haskell-process-file-loadish (concat "load " (buffer-file-name)) + nil + (current-buffer))) + +;;;###autoload +(defun haskell-process-reload-file () + "Re-load the current buffer file." + (interactive) + (save-buffer) + (haskell-interactive-mode-reset-error (haskell-session)) + (haskell-process-file-loadish "reload" t nil)) + +;;;###autoload +(defun haskell-process-load-or-reload (&optional toggle) + "Load or reload. Universal argument toggles which." + (interactive "P") + (if toggle + (progn (setq haskell-reload-p (not haskell-reload-p)) + (message "%s (No action taken this time)" + (if haskell-reload-p + "Now running :reload." + "Now running :load <buffer-filename>."))) + (if haskell-reload-p (haskell-process-reload-file) (haskell-process-load-file)))) + +(defun haskell-process-file-loadish (command reload-p module-buffer) + "Run a loading-ish COMMAND that wants to pick up type errors +and things like that. RELOAD-P indicates whether the notification +should say 'reloaded' or 'loaded'. MODULE-BUFFER may be used +for various things, but is optional." + (let ((session (haskell-session))) + (haskell-session-current-dir session) + (when haskell-process-check-cabal-config-on-load + (haskell-process-look-config-changes session)) + (let ((process (haskell-process))) + (haskell-process-queue-command + process + (make-haskell-command + :state (list session process command reload-p module-buffer) + :go (lambda (state) + (haskell-process-send-string + (cadr state) (format ":%s" (caddr state)))) + :live (lambda (state buffer) + (haskell-process-live-build + (cadr state) buffer nil)) + :complete (lambda (state response) + (haskell-process-load-complete + (car state) + (cadr state) + response + (cadddr state) + (cadddr (cdr state))))))))) + +;;;###autoload +(defun haskell-process-cabal-build () + "Build the Cabal project." + (interactive) + (haskell-process-do-cabal "build") + (haskell-process-add-cabal-autogen)) + +;;;###autoload +(defun haskell-process-cabal () + "Prompts for a Cabal command to run." + (interactive) + (haskell-process-do-cabal + (funcall haskell-completing-read-function "Cabal command: " + haskell-cabal-commands))) + +(defun haskell-process-add-cabal-autogen () + "Add <cabal-project-dir>/dist/build/autogen/ to the ghci search +path. This allows modules such as 'Path_...', generated by cabal, +to be loaded by ghci." + (unless (eq 'cabal-repl haskell-process-type) ;; redundant with "cabal repl" + (let* + ((session (haskell-session)) + (cabal-dir (haskell-session-cabal-dir session)) + (ghci-gen-dir (format "%sdist/build/autogen/" cabal-dir))) + (haskell-process-queue-without-filters + (haskell-process) + (format ":set -i%s" ghci-gen-dir))))) + +(defun haskell-process-do-cabal (command) + "Run a Cabal command." + (let ((process (haskell-process))) + (haskell-process-queue-command + process + (make-haskell-command + :state (list (haskell-session) process command 0) + + :go + (lambda (state) + (haskell-process-send-string + (cadr state) + (format haskell-process-do-cabal-format-string + (haskell-session-cabal-dir (car state)) + (format "%s %s" + (ecase haskell-process-type + ('ghci "cabal") + ('cabal-repl "cabal") + ('cabal-ghci "cabal") + ('cabal-dev "cabal-dev")) + (caddr state))))) + + :live + (lambda (state buffer) + (let ((cmd (replace-regexp-in-string "^\\([a-z]+\\).*" + "\\1" + (caddr state)))) + (cond ((or (string= cmd "build") + (string= cmd "install")) + (haskell-process-live-build (cadr state) buffer t)) + (t + (haskell-process-cabal-live state buffer))))) + + :complete + (lambda (state response) + (let* ((process (cadr state)) + (session (haskell-process-session process)) + (message-count 0) + (cursor (haskell-process-response-cursor process)) + (haskell-imported-suggested (list))) + (haskell-process-set-response-cursor process 0) + (while (haskell-process-errors-warnings session process response) + (setq message-count (1+ message-count))) + (haskell-process-set-response-cursor process cursor) + (let ((msg (format "Complete: cabal %s (%s compiler messages)" + (caddr state) + message-count))) + (haskell-interactive-mode-echo session msg) + (haskell-mode-message-line msg) + (when (and haskell-notify-p + (fboundp 'notifications-notify)) + (notifications-notify + :title (format "*%s*" (haskell-session-name (car state))) + :body msg + :app-name (ecase haskell-process-type + ('ghci "cabal") + ('cabal-repl "cabal") + ('cabal-ghci "cabal") + ('cabal-dev "cabal-dev")) + :app-icon haskell-process-logo + ))))))))) + +(defun haskell-process-cabal-live (state buffer) + "Do live updates for Cabal processes." + (haskell-interactive-mode-insert + (haskell-process-session (cadr state)) + (replace-regexp-in-string + haskell-process-prompt-regex + "" + (substring buffer (cadddr state)))) + (setf (cdddr state) (list (length buffer))) + nil) + +(defun haskell-process-load-complete (session process buffer reload module-buffer &optional cont) + "Handle the complete loading response. BUFFER is the string of +text being sent over the process pipe. MODULE-BUFFER is the +actual Emacs buffer of the module being loaded." + (cond ((haskell-process-consume process "Ok, modules loaded: \\(.+\\)\\.$") + (let* ((modules (haskell-process-extract-modules buffer)) + (cursor (haskell-process-response-cursor process))) + (haskell-process-set-response-cursor process 0) + (let ((warning-count 0)) + (while (haskell-process-errors-warnings session process buffer) + (setq warning-count (1+ warning-count))) + (haskell-process-set-response-cursor process cursor) + (if (and (not reload) + haskell-process-reload-with-fbytecode) + (haskell-process-reload-with-fbytecode process module-buffer) + (haskell-process-import-modules process (car modules))) + (haskell-mode-message-line + (if reload "Reloaded OK." "OK.")) + (when cont + (funcall cont t))))) + ((haskell-process-consume process "Failed, modules loaded: \\(.+\\)\\.$") + (let* ((modules (haskell-process-extract-modules buffer)) + (cursor (haskell-process-response-cursor process)) + (haskell-imported-suggested (list))) + (haskell-process-set-response-cursor process 0) + (while (haskell-process-errors-warnings session process buffer)) + (haskell-process-set-response-cursor process cursor) + (if (and (not reload) haskell-process-reload-with-fbytecode) + (haskell-process-reload-with-fbytecode process module-buffer) + (haskell-process-import-modules process (car modules))) + (haskell-interactive-mode-compile-error session "Compilation failed.") + (when cont + (funcall cont nil)))))) + +(defun haskell-process-reload-with-fbytecode (process module-buffer) + "Reload FILE-NAME with -fbyte-code set, and then restore -fobject-code." + (haskell-process-queue-without-filters process ":set -fbyte-code") + (haskell-process-touch-buffer process module-buffer) + (haskell-process-queue-without-filters process ":reload") + (haskell-process-queue-without-filters process ":set -fobject-code")) + +(defun haskell-process-touch-buffer (process buffer) + "Updates mtime on the file for BUFFER by queing a touch on +PROCESS." + (interactive) + (haskell-process-queue-command + process + (make-haskell-command + :state (cons process buffer) + :go (lambda (state) + (haskell-process-send-string + (car state) + (format ":!%s %s" + "touch" + (shell-quote-argument (buffer-file-name + (cdr state)))))) + :complete (lambda (state _) + (with-current-buffer (cdr state) + (clear-visited-file-modtime)))))) + +(defun haskell-process-extract-modules (buffer) + "Extract the modules from the process buffer." + (let* ((modules-string (match-string 1 buffer)) + (modules (split-string modules-string ", "))) + (cons modules modules-string))) + +(defun haskell-process-import-modules (process modules) + "Import `modules' with :m +, and send any import statements +from `module-buffer'." + (when haskell-process-auto-import-loaded-modules + (haskell-process-queue-command + process + (make-haskell-command + :state (cons process modules) + :go (lambda (state) + (haskell-process-send-string + (car state) + (format ":m + %s" (mapconcat 'identity (cdr state) " ")))))))) + +(defun haskell-process-live-build (process buffer echo-in-repl) + "Show live updates for loading files." + (cond ((haskell-process-consume + process + (concat "\\[[ ]*\\([0-9]+\\) of \\([0-9]+\\)\\]" + " Compiling \\([^ ]+\\)[ ]+" + "( \\([^ ]+\\), \\([^ ]+\\) )[^\r\n]*[\r\n]+")) + (let ((session (haskell-process-session process)) + (module-name (match-string 3 buffer)) + (file-name (match-string 4 buffer))) + (haskell-interactive-show-load-message + session + 'compiling + module-name + (haskell-session-strip-dir session file-name) + echo-in-repl)) + t) + ((haskell-process-consume process "Loading package \\([^ ]+\\) ... linking ... done.\n") + (haskell-mode-message-line + (format "Loading: %s" + (match-string 1 buffer))) + t) + ((haskell-process-consume + process + "^Preprocessing executables for \\(.+?\\)\\.\\.\\.") + (let ((msg (format "Preprocessing: %s" (match-string 1 buffer)))) + (haskell-interactive-mode-echo + (haskell-process-session process) + msg) + (haskell-mode-message-line msg))) + ((haskell-process-consume process "Linking \\(.+?\\) \\.\\.\\.") + (let ((msg (format "Linking: %s" (match-string 1 buffer)))) + (haskell-interactive-mode-echo (haskell-process-session process) msg) + (haskell-mode-message-line msg))) + ((haskell-process-consume process "\nBuilding \\(.+?\\)\\.\\.\\.") + (let ((msg (format "Building: %s" (match-string 1 buffer)))) + (haskell-interactive-mode-echo + (haskell-process-session process) + msg) + (haskell-mode-message-line msg))))) + +(defun haskell-process-errors-warnings (session process buffer) + "Trigger handling type errors or warnings." + (cond + ((haskell-process-consume + process + (concat "[\r\n]\\([^ \r\n:][^:\n\r]+\\):\\([0-9]+\\):\\([0-9]+\\)\\(-[0-9]+\\)?:" + "[ \n\r]+\\([[:unibyte:][:nonascii:]]+?\\)\n[^ ]")) + (haskell-process-set-response-cursor process + (- (haskell-process-response-cursor process) 1)) + (let* ((buffer (haskell-process-response process)) + (file (match-string 1 buffer)) + (line (string-to-number (match-string 2 buffer))) + (col (match-string 3 buffer)) + (col2 (match-string 4 buffer)) + (error-msg (match-string 5 buffer)) + (warning (string-match "^Warning:" error-msg)) + (final-msg (format "%s:%s:%s%s: %s" + (haskell-session-strip-dir session file) + line + col (or col2 "") + error-msg))) + (funcall (if warning + 'haskell-interactive-mode-compile-warning + 'haskell-interactive-mode-compile-error) + session final-msg) + (unless warning + (haskell-mode-message-line final-msg)) + (haskell-process-trigger-suggestions session error-msg file line)) + t))) + +(defun haskell-process-trigger-suggestions (session msg file line) + "Trigger prompting to add any extension suggestions." + (cond ((let ((case-fold-search nil)) (string-match " -X\\([A-Z][A-Za-z]+\\)" msg)) + (when haskell-process-suggest-language-pragmas + (haskell-process-suggest-pragma session "LANGUAGE" (match-string 1 msg) file))) + ((string-match " The \\(qualified \\)?import of[ ]`\\([^ ]+\\)' is redundant" msg) + (when haskell-process-suggest-remove-import-lines + (haskell-process-suggest-remove-import session + file + (match-string 2 msg) + line))) + ((string-match "Warning: orphan instance: " msg) + (when haskell-process-suggest-no-warn-orphans + (haskell-process-suggest-pragma session "OPTIONS" "-fno-warn-orphans" file))) + ((string-match "against inferred type `\\[Char\\]'" msg) + (when haskell-process-suggest-overloaded-strings + (haskell-process-suggest-pragma session "LANGUAGE" "OverloadedStrings" file))) + ((string-match "^Not in scope: .*`\\(.+\\)'$" msg) + (when haskell-process-suggest-hoogle-imports + (haskell-process-suggest-hoogle-imports session msg file))) + ((string-match "^[ ]+It is a member of the hidden package `\\(.+\\)'.$" msg) + (when haskell-process-suggest-add-package + (haskell-process-suggest-add-package session msg))))) + +(defun haskell-process-suggest-add-package (session msg) + "Add the (matched) module to your cabal file." + (let* ((suggested-package (match-string 1 msg)) + (package-name (replace-regexp-in-string "-[^-]+$" "" suggested-package)) + (version (progn (string-match "\\([^-]+\\)$" suggested-package) + (match-string 1 suggested-package))) + (cabal-file (concat (haskell-session-name session) + ".cabal"))) + (when (y-or-n-p + (format "Add `%s' to %s?" + package-name + cabal-file)) + (haskell-process-add-dependency package-name version)))) + +(defun haskell-process-add-dependency (package &optional version no-prompt) + "Add PACKAGE (and optionally suffix -VERSION) to the cabal +file. Prompts the user before doing so." + (interactive + (list (read-from-minibuffer "Package entry: ") + nil + t)) + (let ((buffer (current-buffer))) + (find-file (haskell-cabal-find-file)) + (let ((entry (if no-prompt + package + (read-from-minibuffer "Package entry: " + (concat package + (if version + (concat " >= " + version) + "")))))) + (save-excursion + (goto-char (point-min)) + (when (search-forward-regexp "^library$" nil t 1) + (search-forward-regexp "build-depends:[ ]+") + (let ((column (current-column))) + (when (y-or-n-p "Add to library?") + (insert entry ",\n") + (indent-to column)))) + (goto-char (point-min)) + (while (search-forward-regexp "^executable " nil t 1) + (let ((name (buffer-substring-no-properties (point) (line-end-position)))) + (search-forward-regexp "build-depends:[ ]+") + (let ((column (current-column))) + (when (y-or-n-p (format "Add to executable `%s'?" name)) + (insert entry ",\n") + (indent-to column))))) + (save-buffer) + (switch-to-buffer buffer))))) + +(defun haskell-process-suggest-hoogle-imports (session msg file) + "Given an out of scope identifier, Hoogle for that identifier, +and if a result comes back, suggest to import that identifier +now." + (let* ((ident (let ((i (match-string 1 msg))) + (if (string-match "^[A-Z]\\.\\(.+\\)$" i) + (match-string 1 i) + i))) + (modules (haskell-process-hoogle-ident ident)) + (module + (cond + ((> (length modules) 1) + (when (y-or-n-p (format "Identifier `%s' not in scope, choose module to import?" + ident)) + (funcall haskell-completing-read-function "Module: " modules))) + ((= (length modules) 1) + (when (y-or-n-p (format "Identifier `%s' not in scope, import `%s'?" + ident + (car modules))) + (car modules)))))) + (when module + (unless (member module haskell-imported-suggested) + (push module haskell-imported-suggested) + (haskell-process-find-file session file) + (save-excursion + (goto-char (point-max)) + (haskell-navigate-imports) + (insert (read-from-minibuffer "Import line: " (concat "import " module)) + "\n") + (haskell-sort-imports) + (haskell-align-imports)))))) + +(defun haskell-process-hoogle-ident (ident) + "Hoogle for IDENT, returns a list of modules." + (with-temp-buffer + (call-process "hoogle" nil t nil "search" "--exact" ident) + (goto-char (point-min)) + (unless (or (looking-at "^No results found") + (looking-at "^package ")) + (while (re-search-forward "^\\([^ ]+\\).*$" nil t) + (replace-match "\\1" nil nil)) + (remove-if (lambda (a) (string= "" a)) + (split-string (buffer-string) + "\n"))))) + +(defun haskell-process-suggest-remove-import (session file import line) + "Suggest removing or commenting out IMPORT on LINE." + (case (read-key (propertize (format "The import line `%s' is redundant. Remove? (y, n, c: comment out) " + import) + 'face 'minibuffer-prompt)) + (?y + (haskell-process-find-file session file) + (save-excursion + (goto-char (point-min)) + (forward-line (1- line)) + (goto-char (line-beginning-position)) + (delete-region (line-beginning-position) + (line-end-position)))) + (?c + (haskell-process-find-file session file) + (save-excursion + (goto-char (point-min)) + (forward-line (1- line)) + (goto-char (line-beginning-position)) + (insert "-- "))))) + +(defun haskell-process-suggest-pragma (session pragma extension file) + "Suggest to add something to the top of the file." + (let ((string (format "{-# %s %s #-}" pragma extension))) + (when (y-or-n-p (format "Add %s to the top of the file? " string)) + (haskell-process-find-file session file) + (save-excursion + (goto-char (point-min)) + (insert (concat string "\n")))))) + +(defun haskell-process-find-file (session file) + "Find the given file in the project." + (find-file (cond ((file-exists-p (concat (haskell-session-current-dir session) "/" file)) + (concat (haskell-session-current-dir session) "/" file)) + ((file-exists-p (concat (haskell-session-cabal-dir session) "/" file)) + (concat (haskell-session-cabal-dir session) "/" file)) + (t file)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Building the process + +;;;###autoload +(defun haskell-process-start (session) + "Start the inferior Haskell process." + (let ((existing-process (get-process (haskell-session-name (haskell-session))))) + (when (processp existing-process) + (haskell-interactive-mode-echo session "Restarting process ...") + (haskell-process-set (haskell-session-process session) 'is-restarting t) + (delete-process existing-process))) + (let ((process (or (haskell-session-process session) + (haskell-process-make (haskell-session-name session)))) + (old-queue (haskell-process-get (haskell-session-process session) + 'command-queue))) + (haskell-session-set-process session process) + (haskell-process-set-session process session) + (haskell-process-set-cmd process nil) + (haskell-process-set (haskell-session-process session) 'is-restarting nil) + (let ((default-directory (haskell-session-cabal-dir session))) + (haskell-session-pwd session) + (haskell-process-set-process + process + (ecase haskell-process-type + ('ghci + (haskell-process-log (format "Starting inferior GHCi process %s ..." + haskell-process-path-ghci)) + (apply #'start-process + (append (list (haskell-session-name session) + nil + haskell-process-path-ghci) + haskell-process-args-ghci))) + ('cabal-repl + (haskell-process-log (format "Starting inferior `cabal repl' process using %s ..." + haskell-process-path-cabal)) + + (apply #'start-process + (append (list (haskell-session-name session) + nil + haskell-process-path-cabal) + '("repl") haskell-process-args-cabal-repl + (let ((target (haskell-session-target session))) + (if target (list target) nil))))) + ('cabal-ghci + (haskell-process-log (format "Starting inferior cabal-ghci process using %s ..." + haskell-process-path-cabal-ghci)) + (start-process (haskell-session-name session) + nil + haskell-process-path-cabal-ghci)) + ('cabal-dev + (let ((dir (concat (haskell-session-cabal-dir session) + "/cabal-dev"))) + (haskell-process-log (format "Starting inferior cabal-dev process %s -s %s ..." + haskell-process-path-cabal-dev + dir)) + (start-process (haskell-session-name session) + nil + haskell-process-path-cabal-dev + "ghci" + "-s" + dir)))))) + (progn (set-process-sentinel (haskell-process-process process) 'haskell-process-sentinel) + (set-process-filter (haskell-process-process process) 'haskell-process-filter)) + (haskell-process-send-startup process) + (unless (eq 'cabal-repl haskell-process-type) ;; "cabal repl" sets the proper CWD + (haskell-process-change-dir session + process + (haskell-session-current-dir session))) + (haskell-process-set process 'command-queue + (append (haskell-process-get (haskell-session-process session) + 'command-queue) + old-queue)) + process)) + +(defun haskell-process-clear () + "Clear the current process." + (interactive) + (haskell-process-reset (haskell-process)) + (haskell-process-set (haskell-process) 'command-queue nil)) + +(defun haskell-process-restart () + "Restart the inferior Haskell process." + (interactive) + (haskell-process-reset (haskell-process)) + (haskell-process-set (haskell-process) 'command-queue nil) + (haskell-process-start (haskell-session))) + +(defun haskell-kill-session-process (&optional session) + "Kill the process." + (interactive) + (let* ((session (or session (haskell-session))) + (existing-process (get-process (haskell-session-name session)))) + (when (processp existing-process) + (haskell-interactive-mode-echo session "Killing process ...") + (haskell-process-set (haskell-session-process session) 'is-restarting t) + (delete-process existing-process)))) + +(defun haskell-process-make (name) + "Make an inferior Haskell process." + (list (cons 'name name))) + +;;;###autoload +(defun haskell-process () + "Get the current process from the current session." + (haskell-session-process (haskell-session))) + +(defun haskell-process-interrupt () + "Interrupt the process (SIGINT)." + (interactive) + (interrupt-process (haskell-process-process (haskell-process)))) + +(defun haskell-process-cd (&optional not-interactive) + "Change directory." + (interactive) + (let* ((session (haskell-session)) + (dir (haskell-session-pwd session t))) + (haskell-process-log (format "Changing directory to %s ...\n" dir)) + (haskell-process-change-dir session + (haskell-process) + dir))) + +(defun haskell-session-pwd (session &optional change) + "Prompt for the current directory." + (or (unless change + (haskell-session-get session 'current-dir)) + (progn (haskell-session-set-current-dir + session + (haskell-utils-read-directory-name + (if change "Change directory: " "Set current directory: ") + (or (haskell-session-get session 'current-dir) + (haskell-session-get session 'cabal-dir) + (if (buffer-file-name) + (file-name-directory (buffer-file-name)) + "~/")))) + (haskell-session-get session 'current-dir)))) + +(defun haskell-process-change-dir (session process dir) + "Change the directory of the current process." + (haskell-process-queue-command + process + (make-haskell-command + :state (list session process dir) + + :go + (lambda (state) + (haskell-process-send-string + (cadr state) (format ":cd %s" (caddr state)))) + + :complete + (lambda (state _) + (haskell-session-set-current-dir (car state) (caddr state)) + (haskell-interactive-mode-echo (car state) + (format "Changed directory: %s" + (caddr state))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Process communication + +(defun haskell-process-send-startup (process) + "Send the necessary start messages." + (haskell-process-queue-command + process + (make-haskell-command + :state process + + :go (lambda (process) + (haskell-process-send-string process ":set prompt \"> \"") + (haskell-process-send-string process "Prelude.putStrLn \"\"") + (haskell-process-send-string process ":set -v1")) + + :live (lambda (process buffer) + (when (haskell-process-consume + process + "^\*\*\* WARNING: \\(.+\\) is writable by someone else, IGNORING!$") + (let ((path (match-string 1 buffer))) + (haskell-session-modify + (haskell-process-session process) + 'ignored-files + (lambda (files) + (remove-duplicates (cons path files) :test 'string=))) + (haskell-interactive-mode-compile-warning + (haskell-process-session process) + (format "GHCi is ignoring: %s (run M-x haskell-process-unignore)" + path))))) + + :complete (lambda (process _) + (haskell-interactive-mode-echo + (haskell-process-session process) + (concat (nth (random (length haskell-process-greetings)) + haskell-process-greetings) + (when haskell-process-show-debug-tips + " +If I break, you can: + 1. Restart: M-x haskell-process-restart + 2. Configure logging: C-h v haskell-process-log (useful for debugging) + 3. General config: M-x customize-mode + 4. Hide these tips: C-h v haskell-process-show-debug-tips"))))))) + +(defun haskell-process-sentinel (proc event) + "The sentinel for the process pipe." + (let ((session (haskell-process-project-by-proc proc))) + (when session + (let* ((process (haskell-session-process session))) + (unless (haskell-process-restarting process) + (haskell-process-log (format "Event: %S\n" event)) + (haskell-process-log "Process reset.\n") + (haskell-process-prompt-restart process)))))) + +(defun haskell-process-filter (proc response) + "The filter for the process pipe." + (haskell-process-log (format "<- %S\n" response)) + (let ((session (haskell-process-project-by-proc proc))) + (when session + (when (haskell-process-cmd (haskell-session-process session)) + (haskell-process-collect session + response + (haskell-session-process session)))))) + +(defun haskell-process-log (msg) + "Write MSG to the process log (if enabled)." + (when haskell-process-log + (with-current-buffer (get-buffer-create "*haskell-process-log*") + (goto-char (point-max)) + (insert msg)))) + +(defun haskell-process-project-by-proc (proc) + "Find project by process." + (find-if (lambda (project) + (string= (haskell-session-name project) + (process-name proc))) + haskell-sessions)) + +(defun haskell-process-collect (session response process) + "Collect input for the response until receives a prompt." + (haskell-process-set-response process + (concat (haskell-process-response process) response)) + (while (haskell-process-live-updates process)) + (when (string-match haskell-process-prompt-regex + (haskell-process-response process)) + (haskell-command-exec-complete + (haskell-process-cmd process) + (replace-regexp-in-string + haskell-process-prompt-regex + "" + (haskell-process-response process))) + (haskell-process-reset process) + (haskell-process-trigger-queue process))) + +(defun haskell-process-reset (process) + "Reset the process's state, ready for the next send/reply." + (progn (haskell-process-set-response-cursor process 0) + (haskell-process-set-response process "") + (haskell-process-set-cmd process nil))) + +(defun haskell-process-consume (process regex) + "Consume a regex from the response and move the cursor along if succeed." + (when (string-match regex + (haskell-process-response process) + (haskell-process-response-cursor process)) + (haskell-process-set-response-cursor process (match-end 0)) + t)) + +(defun haskell-process-send-string (process string) + "Try to send a string to the process's process. Ask to restart if it's not running." + (let ((child (haskell-process-process process))) + (if (equal 'run (process-status child)) + (let ((out (concat string "\n"))) + (haskell-process-log (format "-> %S\n" out)) + (process-send-string child out)) + (unless (haskell-process-restarting process) + (haskell-process-prompt-restart process))))) + +(defun haskell-process-prompt-restart (process) + "Prompt to restart the died process." + (when (y-or-n-p (format "The Haskell process `%s' has died. Restart? " + (haskell-process-name process))) + (haskell-process-start (haskell-process-session process)))) + +(defun haskell-process-live-updates (process) + "Process live updates." + (haskell-command-exec-live (haskell-process-cmd process) + (haskell-process-response process))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Making commands + +(defun haskell-process-queue-without-filters (process line) + "Queue LINE to be sent to PROCESS without bothering to look at +the response." + (haskell-process-queue-command + process + (make-haskell-command + :state (cons process line) + :go (lambda (state) + (haskell-process-send-string (car state) + (cdr state)))))) + +(defun haskell-process-queue-command (process command) + "Add a command to the process command queue." + (haskell-process-cmd-queue-add process command) + (haskell-process-trigger-queue process)) + +(defun haskell-process-trigger-queue (process) + "Trigger the next command in the queue to be ran if there is no current command." + (if (and (haskell-process-process process) + (process-live-p (haskell-process-process process))) + (unless (haskell-process-cmd process) + (let ((cmd (haskell-process-cmd-queue-pop process))) + (when cmd + (haskell-process-set-cmd process cmd) + (haskell-command-exec-go cmd)))) + (progn (haskell-process-reset process) + (haskell-process-set (haskell-process) 'command-queue nil) + (haskell-process-prompt-restart process)))) + +(defun haskell-process-queue-flushed-p (process) + "Return t if command queue has been completely processed." + (not (or (haskell-process-cmd-queue process) + (haskell-process-cmd process)))) + +(defun haskell-process-queue-flush (process) + "Block till PROCESS' command queue has been completely processed. +This uses `accept-process-output' internally." + (while (not (haskell-process-queue-flushed-p process)) + (haskell-process-trigger-queue process) + (accept-process-output (haskell-process-process process) 1))) + +(defun haskell-process-queue-sync-request (process reqstr) + "Queue submitting REQSTR to PROCESS and return response blockingly." + (let ((cmd (make-haskell-command + :state (cons nil process) + :go `(lambda (s) (haskell-process-send-string (cdr s) ,reqstr)) + :complete 'setcar))) + (haskell-process-queue-command process cmd) + (haskell-process-queue-flush process) + (car-safe (haskell-command-state cmd)))) + +(defun haskell-process-get-repl-completions (process inputstr) + "Perform `:complete repl ...' query for INPUTSTR using PROCESS." + (let* ((reqstr (concat ":complete repl " + (haskell-str-literal-encode inputstr))) + (rawstr (haskell-process-queue-sync-request process reqstr))) + (if (string-prefix-p "unknown command " rawstr) + (error "GHCi lacks `:complete' support") + (let* ((s1 (split-string rawstr "\r?\n")) + (cs (mapcar #'haskell-str-literal-decode (cdr s1))) + (h0 (car s1))) ;; "<cnt1> <cnt2> <quoted-str>" + (unless (string-match "\\`\\([0-9]+\\) \\([0-9]+\\) \\(\".*\"\\)\\'" h0) + (error "Invalid `:complete' response")) + (let ((cnt1 (match-string 1 h0)) + (h1 (haskell-str-literal-decode (match-string 3 h0)))) + (unless (= (string-to-number cnt1) (length cs)) + (error "Lengths inconsistent in `:complete' reponse")) + (cons h1 cs)))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Accessing the process + +(defun haskell-process-get (process key) + "Get the PROCESS's KEY value. +Returns nil if KEY not set." + (cdr (assq key process))) + +(defun haskell-process-set (process key value) + "Set the PROCESS's KEY to VALUE. +Returns newly set VALUE." + (if process + (let ((cell (assq key process))) + (if cell + (setcdr cell value) ; modify cell in-place + (setcdr process (cons (cons key value) (cdr process))) ; new cell + value)) + (display-warning 'haskell-interactive + "`haskell-process-set' called with nil process"))) + +;; Wrappers using haskell-process-{get,set} + +(defun haskell-process-set-process (p v) + "Set the process's inferior process." + (haskell-process-set p 'inferior-process v)) + +(defun haskell-process-process (p) + "Get the process child." + (haskell-process-get p 'inferior-process)) + +(defun haskell-process-name (p) + "Get the process name." + (haskell-process-get p 'name)) + +(defun haskell-process-cmd (p) + "Get the process's current command. +Return nil if no current command." + (haskell-process-get p 'current-command)) + +(defun haskell-process-set-cmd (p v) + "Set the process's current command." + (haskell-process-set p 'current-command v)) + +(defun haskell-process-response (p) + "Get the process's current response." + (haskell-process-get p 'current-response)) + +(defun haskell-process-session (p) + "Get the process's current session." + (haskell-process-get p 'session)) + +(defun haskell-process-set-response (p v) + "Set the process's current response." + (haskell-process-set p 'current-response v)) + +(defun haskell-process-set-session (p v) + "Set the process's current session." + (haskell-process-set p 'session v)) + +(defun haskell-process-response-cursor (p) + "Get the process's current response cursor." + (haskell-process-get p 'current-response-cursor)) + +(defun haskell-process-set-response-cursor (p v) + "Set the process's response cursor." + (haskell-process-set p 'current-response-cursor v)) + +;; low-level command queue operations + +(defun haskell-process-restarting (process) + "Is the PROCESS restarting?" + (haskell-process-get process 'is-restarting)) + +(defun haskell-process-cmd-queue (process) + "Get the PROCESS' command queue. +New entries get added to the end of the list. Use +`haskell-process-cmd-queue-add' and +`haskell-process-cmd-queue-pop' to modify the command queue." + (haskell-process-get process 'command-queue)) + +(defun haskell-process-cmd-queue-add (process cmd) + "Add CMD to end of PROCESS's command queue." + (check-type cmd haskell-command) + (haskell-process-set process + 'command-queue + (append (haskell-process-cmd-queue process) + (list cmd)))) + +(defun haskell-process-cmd-queue-pop (process) + "Pop the PROCESS' next entry from command queue. +Returns nil if queue is empty." + (let ((queue (haskell-process-cmd-queue process))) + (when queue + (haskell-process-set process 'command-queue (cdr queue)) + (car queue)))) + +(defun haskell-process-unignore () + "Unignore any files that were specified as being ignored by the + inferior GHCi process." + (interactive) + (let ((session (haskell-session)) + (changed nil)) + (if (null (haskell-session-get session + 'ignored-files)) + (message "Nothing to unignore!") + (loop for file in (haskell-session-get session + 'ignored-files) + do (case (read-key + (propertize (format "Set permissions? %s (y, n, v: stop and view file)" + file) + 'face 'minibuffer-prompt)) + (?y + (haskell-process-unignore-file session file) + (setq changed t)) + (?v + (find-file file) + (return)))) + (when (and changed + (y-or-n-p "Restart GHCi process now? ")) + (haskell-process-restart))))) + +(defun haskell-process-reload-devel-main () + "Reload the module `DevelMain' and then run +`DevelMain.update'. This is for doing live update of the code of +servers or GUI applications. Put your development version of the +program in `DevelMain', and define `update' to auto-start the +program on a new thread, and use the `foreign-store' package to +access the running context across :load/:reloads in GHCi." + (interactive) + (with-current-buffer (get-buffer "DevelMain.hs") + (let ((session (haskell-session))) + (let ((process (haskell-process))) + (haskell-process-queue-command + process + (make-haskell-command + :state (list :session session + :process process + :buffer (current-buffer)) + :go (lambda (state) + (haskell-process-send-string (plist-get state ':process) + ":l DevelMain")) + :live (lambda (state buffer) + (haskell-process-live-build (plist-get state ':process) + buffer + nil)) + :complete (lambda (state response) + (haskell-process-load-complete + (plist-get state ':session) + (plist-get state ':process) + response + nil + (plist-get state ':buffer) + (lambda (ok) + (when ok + (haskell-process-queue-without-filters + (haskell-process) + "DevelMain.update") + (message "DevelMain updated."))))))))))) + +(defun haskell-process-unignore-file (session file) + " + +Note to Windows Emacs hackers: + +chmod is how to change the mode of files in POSIX +systems. This will not work on your operating +system. + +There is a command a bit like chmod called \"Calcs\" +that you can try using here: + +http://technet.microsoft.com/en-us/library/bb490872.aspx + +If it works, you can submit a patch to this +function and remove this comment. +" + (shell-command (read-from-minibuffer "Permissions command: " + (concat "chmod 700 " + file))) + (haskell-session-modify + (haskell-session) + 'ignored-files + (lambda (files) + (remove-if (lambda (path) + (string= path file)) + files)))) + +(defun haskell-command-exec-go (command) + "Call the command's go function." + (let ((go-func (haskell-command-go command))) + (when go-func + (funcall go-func (haskell-command-state command))))) + +(defun haskell-command-exec-complete (command response) + "Call the command's complete function." + (let ((comp-func (haskell-command-complete command))) + (when comp-func + (funcall comp-func + (haskell-command-state command) + response)))) + +(defun haskell-command-exec-live (command response) + "Trigger the command's live updates callback." + (let ((live-func (haskell-command-live command))) + (when live-func + (funcall live-func + (haskell-command-state command) + response)))) + +(provide 'haskell-process) + +;; Local Variables: +;; byte-compile-warnings: (not cl-functions) +;; End: + +;;; haskell-process.el ends here