diff --git a/doc/haskell-mode.texi b/doc/haskell-mode.texi index 7322b266d..068bff8ae 100644 --- a/doc/haskell-mode.texi +++ b/doc/haskell-mode.texi @@ -67,13 +67,13 @@ interpreter (e.g. GHCi). * Module templates:: Module templates * Declaration scanning:: How to navigate in a source file * Compilation:: How to compile -* Inferior Haskell interpreter:: How to interact with GHCi (1) -* Interactive Haskell:: How to interact with GHCi (2) +* Interactive Haskell:: How to interact with GHCi * Editing Cabal files:: Cabal support * Browsing Haddocks:: Using @code{w3m} to browse documentation * Spell checking strings and comments:: Using @code{flyspell-prog-mode} * Aligning code:: Aligning code using @code{align-regexp} * Rectangular commands:: Manage indentation manually +* REPL:: GHCi REPL * Collapsing Haskell code:: View more code on screen * Getting Help and Reporting Bugs:: How to improve Haskell Mode * Concept index:: Index of Haskell Mode concepts @@ -1148,49 +1148,6 @@ temporarily by invoking @code{haskell-compile} with a prefix argument same customized compile command, invoke @code{recompile} (bound to @kbd{g}) inside the @samp{*haskell-compilation*} buffer. -@node Inferior Haskell interpreter -@chapter Inferior Haskell interpreter - -@findex inferior-haskell-find-definition -@findex inferior-haskell-find-haddock -@findex inferior-haskell-info -@findex inferior-haskell-load-and-run -@findex inferior-haskell-load-file -@findex inferior-haskell-mode -@findex inferior-haskell-reload-file -@findex inferior-haskell-start-process -@findex inferior-haskell-type -@vindex haskell-program-name -@vindex inferior-haskell-mode-hook - -The major mode @code{inferior-haskell-mode} provides support for -interacting with an inferior Haskell process based on -@code{comint-mode}. - -By default the @code{haskell-mode-map} keymap is setup to use this mode: - -@table @kbd -@item C-c C-z -is bound to @code{switch-to-haskell} -@item C-c C-b -is bound to @code{switch-to-haskell} -@item C-c C-l -is bound to @code{inferior-haskell-load-file} -@item C-c C-t -is bound to @code{inferior-haskell-type} -@item C-c C-i -is bound to @code{inferior-haskell-info} -@end table - -The Haskell interpreter used by the inferior Haskell mode is -auto-detected by default, but is customizable via the -@code{haskell-program-name} variable. - -Currently, GHCi and Hugs are support as Haskell interpreter. - -TODO/WRITEME -@c write about supported features - @node Interactive Haskell @chapter Interactive Haskell @@ -2593,6 +2550,128 @@ This will insert the contents of the last killed rectangle. As with all Emacs modifier combos, you can type @kbd{C-x r C-h} to find out what keys are bound beginning with the @kbd{C-x r} prefix. +@node REPL +@chapter Using GHCi REPL within Emacs + +To start the REPL you can run one of the following: + +@itemize +@item @kbd{M-x run-haskell} +@item @kbd{M-x switch-to-haskell} +@end itemize + +This repl works with @uref{https://www.emacswiki.org/emacs/ComintMode, Comint}. +So you will feel at home if you are already using @kbd{M-x Shell} or @kbd{M-x ielm}. + +@code{Inf-Haskell} is a Major mode for running GHCi, with comint. + +Important key bindings in @code{Inf-haskell}: + +@table @kbd +@item RET +invokes @kbd{comint-send-input}. Sends the input to the GHCi process, evaluates +the line and returns the output. + +@item C-d or +deletes the forward character + +@item or M-p +invokes @kbd{comint-previous-input}. Cycle backwards through input history, +saving input. + +@item or M-n +invokes @kbd{comint-next-input}. Cycle forwards through input history. + +@item C-c C-c +invokes @kbd{comint-interrupt-subjob}. Sends KeyboardInterrupt signal. + +@item C-c C-\ +invokes @kbd{comint-quit-subjob}. Sends KeyboardInterrupt signal. + +@item C-c C-z +invokes @kbd{comint-stop-subjob}. Kills the GHCi process. + +@item C-c M-r +invokes @kbd{comint-previous-matching-input-from-input}. If you are familiar +with @kbd{C-r} in bash. This is the same as that. Searches backwards through +input history for match for current input. + +@item C-c M-s +invokes @kbd{comint-next-matching-input-from-input}. Searches forwards through +input history for match for current input. + +@item C-c C-l +invokes @kbd{comint-dynamic-list-input-ring}. Displays a list of recent inputs +entered into the current buffer. + +@item C-c M-o +invokes @kbd{comint-clear-buffer}. Clears the buffer (Only with Emacs 25.X and above) + +@item C-c C-n +invokes @kbd{comint-next-prompt}. Goes to the start of the previous REPL prompt. + +@item C-c C-p +invokes @kbd{comint-previous-prompt}. Goes to the start of the next REPL prompt. + +@item C-c C-o +invokes @kbd{comint-delete-output}. Clears the output of the most recently evaluated +expression. + +@item C-c C-e +invokes @kbd{comint-show-maximum-output}. Moves the point to the end of the buffer. + +@item C-c C-u +invokes @kbd{comint-kill-input}. Kills backward, the line at point. (Use this when you have typed in an expression into the prompt +but you dont want to evaluate it.) + +@item C-c C-w +invokes @kbd{backward-kill-word}. Kills backward, the word at point + +@item C-c C-s +invokes @kbd{comint-write-output}. Write output from interpreter since last +input to FILENAME. Any prompt at the end of the output is not written. +@end table + +@section Relevant defcustoms: + +@multitable @columnfractions .40 .20 .40 +@headitem Interpreter (defcustom) @tab Default Value @tab Possible Values +@item @code{haskell-process-type} @tab @code{'auto} @tab @code{'stack-ghci, 'cabal-repl, 'ghci, 'auto} +@item @code{inferior-haskell-hook} @tab @code{nil} @tab - +@item @code{haskell-process-path-ghci} @tab @code{ghci} @tab - +@item @code{haskell-process-args-ghci} @tab @code{-ferror-spans} @tab - +@item @code{haskell-process-path-cabal} @tab @code{cabal} @tab - +@item @code{haskell-process-args-cabal-repl} @tab @code{--ghc-option=-ferror-spans} @tab - +@item @code{haskell-process-path-stack} @tab @code{stack} @tab - +@item @code{haskell-process-args-stack-ghci} @tab @code{--ghci-options=-ferror-spans --no-build --no-load} @tab - +@end multitable + +@section More on @code{haskell-process-type} + +The Haskell interpreter used by @code{Inf-Haskell} is auto-detected by default, +but is customizable with defcustom @code{haskell-process-type}. The values +recognized by it are (default is 'auto): + +@itemize +@item @code{'stack-ghci} +@item @code{'cabal-repl} +@item @code{'ghci} +@item @code{'auto} +@end itemize + +if the @code{haskell-process-type} is @code{'auto}, the directories are searched for +@code{cabal.sandbox.config} or @code{stack.yaml} or @code{*.cabal} file. +If the file is present, then appropriate process is started. + +When @code{cabal.sandbox.config} is found @code{haskell-process-type} is @code{'cabal-repl}. +Similarly, when @code{stack.yaml} is found @code{haskell-process-type} is @code{'stack-ghci}. +Similarly, when @code{xyz.cabal} is found @code{haskell-process-type} is @code{'cabal-repl}. +When nothing is found @code{haskell-process-type} is @code{'ghci}. When more than one +file such as @code{cabal.sandbox.config} and @code{stack.yaml} are found the following +preference is followed. + +@code{cabal.sandbox.config} > @code{stack.yaml} > @code{*.cabal} + @node Collapsing Haskell code @chapter Collapsing Haskell code diff --git a/haskell-customize.el b/haskell-customize.el index e16d2bb6f..1c6d7a546 100644 --- a/haskell-customize.el +++ b/haskell-customize.el @@ -64,7 +64,11 @@ be located, then stack-ghci will be used. Otherwise if there's a *.cabal file, cabal-repl will be used. If none of the above apply, ghci will be used." - :type '(choice (const auto) (const ghci) (const cabal-repl) (const stack-ghci) (const cabal-new-repl)) + :type '(choice (const auto) + (const ghci) + (const cabal-repl) + (const stack-ghci) + (const cabal-new-repl)) :group 'haskell-interactive) (defcustom haskell-process-wrapper-function @@ -177,6 +181,22 @@ pass additional flags to `ghc'." :group 'haskell-interactive :type '(repeat (string :tag "Argument"))) +(defcustom haskell-process-path-ghc + "ghc" + "Path for The Glorious Glasgow Haskell Compiler") + +(defcustom haskell-process-args-ghc + "--make -ferror-spans -Wall -fforce-recomp" + "Any arguments for starting ghc.") + +(defcustom haskell-process-args-cabal-build + "--ghc-options=\"-ferror-spans -Wall -fforce-recomp\"" + "Arguments while doing cabal build.") + +(defcustom haskell-process-args-stack-build + "--ghc-options=\"-ferror-spans -Wall\"" + "Additional arguments for `stack build' invocation.") + (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. @@ -415,22 +435,43 @@ imports." ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Accessor functions +(defvar inferior-haskell-root-dir nil + "The path which is considered as project root, this is determined by the +presence of a *.cabal file or stack.yaml file or something similar.") + (defun haskell-process-type () - "Return `haskell-process-type', or a guess if that variable is 'auto." - (if (eq 'auto haskell-process-type) - (cond - ;; User has explicitly initialized this project with cabal - ((locate-dominating-file default-directory "cabal.sandbox.config") - 'cabal-repl) - ((and (locate-dominating-file default-directory "stack.yaml") - (executable-find "stack")) - 'stack-ghci) - ((locate-dominating-file - default-directory - (lambda (d) - (cl-find-if (lambda (f) (string-match-p ".\\.cabal\\'" f)) (directory-files d)))) - 'cabal-repl) - (t 'ghci)) - haskell-process-type)) + "Return `haskell-process-type', or a guess if that variable is 'auto. +This function also sets the `inferior-haskell-root-dir'" + (let ((cabal-sandbox (locate-dominating-file default-directory + "cabal.sandbox.config")) + (stack (locate-dominating-file default-directory + "stack.yaml")) + (cabal (locate-dominating-file default-directory + (lambda (d) + (cl-find-if + (lambda (f) + (string-match-p ".\\.cabal\\'" f)) + (directory-files d)))))) + (if (eq 'auto haskell-process-type) + (cond + ;; User has explicitly initialized this project with cabal + ((and cabal-sandbox + (executable-find "cabal")) + (setq inferior-haskell-root-dir cabal-sandbox) + 'cabal-repl) + ((and stack + (executable-find "stack")) + (setq inferior-haskell-root-dir stack) + 'stack-ghci) + ((and cabal + (executable-find "cabal")) + (setq inferior-haskell-root-dir cabal) + 'cabal-repl) + ((executable-find "ghc") + (setq inferior-haskell-root-dir default-directory) + 'ghci) + (t + (error "Could not find any installation of GHC."))) + haskell-process-type))) (provide 'haskell-customize) diff --git a/haskell-doc.el b/haskell-doc.el index 4ac06a1b2..5ac49b8f8 100644 --- a/haskell-doc.el +++ b/haskell-doc.el @@ -1848,6 +1848,14 @@ This function switches to and potentially loads many buffers." ;; return the result doc )))) +(defun inferior-haskell-kind (sym) + "Find the kind of SYM with `:kind' ghci feature." + (inferior-haskell-get-result (format ":kind %s" sym))) + +(defun inferior-haskell-type (sym) + "Find the type of SYM with `:type' ghci feature." + (inferior-haskell-get-result (format ":type (%s)" sym))) + (provide 'haskell-doc) ;;; haskell-doc.el ends here diff --git a/inf-haskell.el b/inf-haskell.el index 9dcb420a3..64430f715 100644 --- a/inf-haskell.el +++ b/inf-haskell.el @@ -1,6 +1,7 @@ ;;; inf-haskell.el --- Interaction with an inferior Haskell process -*- lexical-binding: t -*- ;; Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc. +;; Copyright (C) 2017 Vasantha Ganesh Kanniappan ;; Author: Stefan Monnier ;; Keywords: Haskell @@ -20,8 +21,7 @@ ;;; Commentary: -;; The code is made of 2 parts: a major mode for the buffer that holds the -;; inferior process's session and a minor mode for use in source buffers. +;; A major mode for the buffer that holds the inferior process ;; Todo: @@ -35,12 +35,11 @@ (require 'etags) (require 'haskell-compat) (require 'compile) -(require 'haskell-mode) (require 'haskell-decl-scan) (require 'haskell-cabal) - -;; Dynamically scoped variables. -(defvar find-tag-marker-ring) +(require 'haskell-customize) +(require 'cl-lib) +(require 'haskell-string) ;;;###autoload (defgroup inferior-haskell nil @@ -50,27 +49,29 @@ :prefix "haskell-" :group 'haskell) -;; Here I depart from the inferior-haskell- prefix. -;; Not sure if it's a good idea. -(defcustom haskell-program-name - ;; Arbitrarily give preference to hugs over ghci. - (or (cond - ((executable-find "hugs") "hugs \"+.\"") - ((executable-find "ghci") "ghci")) - "hugs \"+.\"") - "The name of the command to start the inferior Haskell process. -The command can include arguments." - ;; Custom only supports the :options keyword for a few types, e.g. not - ;; for string. - ;; :options '("hugs \"+.\"" "ghci") - :group 'inferior-haskell - :type '(choice string (repeat string))) +(defcustom inferior-haskell-hook nil + "The hook that is called after starting inf-haskell.") + +(defun haskell-program-name-with-args () + "Return the command with the arguments to start the repl based on the +directory structure." + (cl-ecase (haskell-process-type) + ('ghci (cond ((eq system-type 'cygwin) (nconc "ghcii.sh" + haskell-process-args-ghci)) + (t (nconc `(,haskell-process-path-ghci) + haskell-process-args-ghci)))) + ('cabal-repl (nconc `(,haskell-process-path-cabal + "repl") + haskell-process-args-cabal-repl)) + ('stack-ghci (nconc `(,haskell-process-path-stack + "ghci") + haskell-process-args-stack-ghci)))) (defconst inferior-haskell-info-xref-re - "\t-- Defined at \\(.+\\):\\([0-9]+\\):\\([0-9]+\\)\\(?:-\\([0-9]+\\)\\)?$") + "-- Defined at \\(.+\\):\\([0-9]+\\):\\([0-9]+\\)\\(?:-\\([0-9]+\\)\\)?$") (defconst inferior-haskell-module-re - "\t-- Defined in \\(.+\\)$" + "-- Defined in \\(.+\\)$" "Regular expression for matching module names in :info.") (defvar inferior-haskell-multiline-prompt-re @@ -78,9 +79,7 @@ The command can include arguments." "Regular expression for matching multiline prompt (the one inside :{ ... :} blocks).") (defconst inferior-haskell-error-regexp-alist - ;; The format of error messages used by Hugs. - `(("^ERROR \"\\(.+?\\)\"\\(:\\| line \\)\\([0-9]+\\) - " 1 3) - ;; Format of error messages used by GHCi. + `(;; Format of error messages used by GHCi. ("^\\(.+?\\):\\([0-9]+\\):\\(\\([0-9]+\\):\\)?\\( \\|\n *\\)\\([Ww]arning\\)?" 1 2 4 ,@(if (fboundp 'compilation-fake-loc) '((6) nil (5 '(face nil font-lock-multiline t))))) @@ -110,22 +109,30 @@ The command can include arguments." "Regexps for error messages generated by inferior Haskell processes. The format should be the same as for `compilation-error-regexp-alist'.") -(defcustom inferior-haskell-find-project-root t - "If non-nil, try and find the project root directory of this file. -This will either look for a Cabal file or a \"module\" statement in the file." - :group 'inferior-haskell - :type 'boolean) +(defconst haskell-prompt-regexp + ;; Why the backslash in [\\._[:alnum:]]? + "^\\*?[[:upper:]][\\._[:alnum:]]*\\(?: \\*?[[:upper:]][\\._[:alnum:]]*\\)*\\( λ\\)?> \\|^λ?> $") + +;;; TODO +;;; -> Make font lock work for strings, directories, hyperlinks +;;; -> Make font lock work for key words??? + +(defvar inf-haskell-map + (let ((map (make-sparse-keymap))) + (define-key map "\C-c\C-d" 'comint-kill-subjob) + map)) + +(defvaralias 'inferior-haskell-mode-map 'inf-haskell-map) (define-derived-mode inferior-haskell-mode comint-mode "Inf-Haskell" "Major mode for interacting with an inferior Haskell process." :group 'inferior-haskell - (setq-local comint-prompt-regexp - ;; Why the backslash in [\\._[:alnum:]]? - "^\\*?[[:upper:]][\\._[:alnum:]]*\\(?: \\*?[[:upper:]][\\._[:alnum:]]*\\)*\\( λ\\)?> \\|^λ?> $") + (setq-local comint-prompt-regexp haskell-prompt-regexp) + + (setq-local paragraph-start haskell-prompt-regexp) + (setq-local comint-input-autoexpand nil) - (add-hook 'comint-preoutput-filter-functions - 'inferior-haskell-send-decl-post-filter) - (add-hook 'comint-output-filter-functions 'inferior-haskell-spot-prompt nil t) + (setq-local comint-prompt-read-only t) ;; Setup directory tracking. (setq-local shell-cd-regexp ":cd") @@ -153,672 +160,101 @@ This will either look for a Cabal file or a \"module\" statement in the file." ;; Preserve some of the bindings. (define-key map keys (lookup-key compilation-minor-mode-map keys))) (add-to-list 'minor-mode-overriding-map-alist - (cons 'compilation-minor-mode map))))) - -(defun inferior-haskell-string-to-strings (string) - "Split the STRING into a list of strings." - (let ((i (string-match "[\"]" string))) - (if (null i) (split-string string) ; no quoting: easy - (append (unless (eq i 0) (split-string (substring string 0 i))) - (let ((rfs (read-from-string string i))) - (cons (car rfs) - (inferior-haskell-string-to-strings - (substring string (cdr rfs))))))))) - -(defun inferior-haskell-command (arg) - (inferior-haskell-string-to-strings - (if (null arg) haskell-program-name - (read-string "Command to run haskell: " haskell-program-name)))) + (cons 'compilation-minor-mode map)))) + (add-hook 'inferior-haskell-hook 'inferior-haskell-init)) (defvar inferior-haskell-buffer nil "The buffer in which the inferior process is running.") -(defun inferior-haskell-start-process (command) +(defun inferior-haskell-start-process () "Start an inferior haskell process. With universal prefix \\[universal-argument], prompts for a COMMAND, -otherwise uses `haskell-program-name'. +otherwise uses `haskell-program-name-with-args'. It runs the hook `inferior-haskell-hook' after starting the process and setting up the inferior-haskell buffer." - (interactive (list (inferior-haskell-command current-prefix-arg))) - (setq inferior-haskell-buffer - (apply 'make-comint "haskell" (car command) nil (cdr command))) - (with-current-buffer inferior-haskell-buffer - (inferior-haskell-mode) - (run-hooks 'inferior-haskell-hook))) - -(defun inferior-haskell-process (&optional arg) - (or (if (buffer-live-p inferior-haskell-buffer) - (get-buffer-process inferior-haskell-buffer)) - (progn - (let ((current-prefix-arg arg)) - (call-interactively 'inferior-haskell-start-process)) - ;; Try again. - (inferior-haskell-process arg)))) + (let ((command (haskell-program-name-with-args))) + (setq default-directory inferior-haskell-root-dir) + (setq inferior-haskell-buffer + (apply 'make-comint "haskell" (car command) nil (cdr command))) + (with-current-buffer inferior-haskell-buffer + (inferior-haskell-mode) + (run-hooks 'inferior-haskell-hook)))) + +(defun inferior-haskell-process () + "Restart if not present." + (cond ((and (buffer-live-p inferior-haskell-buffer) + (comint-check-proc inferior-haskell-buffer)) + (get-buffer-process inferior-haskell-buffer)) + (t (inferior-haskell-start-process) + (inferior-haskell-process)))) ;;;###autoload (defalias 'run-haskell 'switch-to-haskell) ;;;###autoload -(defun switch-to-haskell (&optional arg) +(defun switch-to-haskell () "Show the inferior-haskell buffer. Start the process if needed." - (interactive "P") - (let ((proc (inferior-haskell-process arg))) - (pop-to-buffer (process-buffer proc)))) - -(defcustom inferior-haskell-wait-and-jump nil - "If non-nil, wait for file loading to terminate and jump to the error." - :type 'boolean - :group 'inferior-haskell) - -(defvar-local inferior-haskell-send-decl-post-filter-on nil) - -(defun inferior-haskell-send-decl-post-filter (string) - (when (and inferior-haskell-send-decl-post-filter-on - (string-match inferior-haskell-multiline-prompt-re string)) - ;; deleting sequence of `%s|' multiline promts - (while (string-match inferior-haskell-multiline-prompt-re string) - (setq string (substring string (match-end 0)))) - ;; deleting regular prompts - (setq string (replace-regexp-in-string comint-prompt-regexp "" string) - ;; turning off this post-filter - inferior-haskell-send-decl-post-filter-on nil)) - string) - -(defvar-local inferior-haskell-seen-prompt nil) - -(defun inferior-haskell-spot-prompt (_string) - (let ((proc (get-buffer-process (current-buffer)))) - (when proc - (save-excursion - (goto-char (process-mark proc)) - (if (re-search-backward comint-prompt-regexp - (line-beginning-position) t) - (setq inferior-haskell-seen-prompt t)))))) - -(defun inferior-haskell-wait-for-prompt (proc &optional timeout) - "Wait until PROC sends us a prompt. -The process PROC should be associated to a comint buffer." - (with-current-buffer (process-buffer proc) - (while (progn - (goto-char comint-last-input-end) - (not (or inferior-haskell-seen-prompt - (setq inferior-haskell-seen-prompt - (re-search-forward comint-prompt-regexp nil t)) - (not (accept-process-output proc timeout)))))) - (unless inferior-haskell-seen-prompt - (error "Can't find the prompt")))) - -(defvar inferior-haskell-cabal-buffer nil) - -(defun inferior-haskell-cabal-of-buf (buf) - (with-current-buffer buf - (or (and (buffer-live-p inferior-haskell-cabal-buffer) - inferior-haskell-cabal-buffer) - (if (local-variable-p 'inferior-haskell-cabal-buffer - ;; XEmacs needs this argument. - (current-buffer)) - inferior-haskell-cabal-buffer - (setq-local inferior-haskell-cabal-buffer (haskell-cabal-find-file)))))) - -(defun inferior-haskell-find-project-root (buf) - (with-current-buffer buf - (let* ( - (cabal-file (inferior-haskell-cabal-of-buf buf)) - (cabal (when cabal-file - (find-file-noselect cabal-file))) - ) - (or (when cabal - (with-current-buffer cabal - (let ((hsd (haskell-cabal--get-field "hs-source-dirs"))) - (if (null hsd) - ;; If there's a Cabal file with no Hs-Source-Dirs, then - ;; just use the Cabal file's directory. - default-directory - ;; If there is an HSD, then check that it's an existing - ;; dir (otherwise, it may be a list of dirs and we don't - ;; know what to do with those). If it doesn't exist, then - ;; give up. - (if (file-directory-p hsd) (expand-file-name hsd)))))) - ;; If there's no Cabal file or it's not helpful, try to look for - ;; a "module" statement and count the number of "." in the - ;; module name. - (save-excursion - (goto-char (point-min)) - (let ((case-fold-search nil)) - (when (re-search-forward - "^module[ \t]+\\(\\(?:\\sw\\|[.]\\)+\\)" nil t) - (let* ((dir default-directory) - (module (match-string 1)) - (pos 0)) - (while (string-match "\\." module pos) - (setq pos (match-end 0)) - (setq dir (expand-file-name ".." dir))) - ;; Let's check that the module name matches the file name, - ;; otherwise the project root is probably not what we think. - (if (eq t (compare-strings - (file-name-sans-extension buffer-file-name) - nil nil - (expand-file-name - (replace-regexp-in-string "\\." "/" module) - dir) - nil nil t)) - dir - ;; If they're not equal, it means the local directory - ;; hierarchy doesn't match the module name. This seems - ;; odd, so let's warn the user about it. May help us - ;; debug this code as well. - (message "Ignoring inconsistent `module' info: %s in %s" - module buffer-file-name) - nil))))))))) - - - -;;;###autoload -(defun inferior-haskell-load-file (&optional reload) - "Pass the current buffer's file to the inferior haskell process. -If prefix arg \\[universal-argument] is given, just reload the previous file." - (interactive "P") - ;; Save first, so we're sure that `buffer-file-name' is non-nil afterward. - (save-buffer) - (let ((buf (current-buffer)) - (file buffer-file-name) - (proc (inferior-haskell-process))) - (if file - (with-current-buffer (process-buffer proc) - (compilation-forget-errors) - (let ((parsing-end (marker-position (process-mark proc))) - root) - ;; Go to the root of the Cabal project, if applicable. - (when (and inferior-haskell-find-project-root - (setq root (inferior-haskell-find-project-root buf))) - ;; Not sure if it's useful/needed and if it actually works. - (unless (equal default-directory root) - (setq default-directory root) - (inferior-haskell-send-command - proc (concat ":cd " default-directory))) - (setq file (file-relative-name file))) - (inferior-haskell-send-command - proc (if reload ":reload" - (concat ":load \"" - ;; Espace the backslashes that may occur in file names. - (replace-regexp-in-string "[\\\"]" "\\\\\&" file) - "\""))) - ;; Move the parsing-end marker *after* sending the command so - ;; that it doesn't point just to the insertion point. - ;; Otherwise insertion may move the marker (if done with - ;; insert-before-markers) and we'd then miss some errors. - (if (boundp 'compilation-parsing-end) - (if (markerp compilation-parsing-end) - (set-marker compilation-parsing-end parsing-end) - (setq compilation-parsing-end parsing-end)))) - (with-selected-window (display-buffer (current-buffer) nil 'visible) - (goto-char (point-max))) - ;; Use compilation-auto-jump-to-first-error if available. - ;; (if (and (boundp 'compilation-auto-jump-to-first-error) - ;; compilation-auto-jump-to-first-error - ;; (boundp 'compilation-auto-jump-to-next)) - ;; (setq compilation-auto-jump-to-next t) - (when inferior-haskell-wait-and-jump - (inferior-haskell-wait-for-prompt proc) - (ignore-errors ;Don't beep if there were no errors. - (next-error)))) - (error "No file associated with buffer")))) - -(defvar inferior-haskell-run-command ":main") - -;;;###autoload -(defun inferior-haskell-load-and-run (command) - "Pass the current buffer's file to haskell and then run a COMMAND." - (interactive - (list - (if (and inferior-haskell-run-command (not current-prefix-arg)) - inferior-haskell-run-command - (read-string "Command to run: " nil nil inferior-haskell-run-command)))) - (setq inferior-haskell-run-command command) - (let* ((inferior-haskell-errors nil) - (neh (lambda () (setq inferior-haskell-errors t)))) - (unwind-protect - (let ((inferior-haskell-wait-and-jump t)) - (add-hook 'next-error-hook neh) - (inferior-haskell-load-file)) - (remove-hook 'next-error-hook neh)) - (unless inferior-haskell-errors - (inferior-haskell-send-command (inferior-haskell-process) command) - (switch-to-haskell)))) - -(defun inferior-haskell-send-command (proc str) - (setq str (concat str "\n")) - (with-current-buffer (process-buffer proc) - (inferior-haskell-wait-for-prompt proc) - (goto-char (process-mark proc)) - (insert-before-markers str) - (move-marker comint-last-input-end (point)) - (setq inferior-haskell-seen-prompt nil) - (comint-send-string proc str))) - -(defun inferior-haskell-reload-file () - "Tell the inferior haskell process to reread the current buffer's file." (interactive) - (inferior-haskell-load-file 'reload)) - -(defun inferior-haskell-wrap-decl (code) - "Wrap declaration code into :{ ... :}." - (setq code (concat code "\n")) - (concat ":{\n" - (if (string-match (concat "^\\s-*" - haskell-ds-start-keywords-re) - code) - ;; non-fun-decl - code - ;; fun-decl, wrapping into let { .. (; ..)* } - (concat "let {\n" - (mapconcat - ;; adding 2 whitespaces to each line - (lambda (decl) - (mapconcat (lambda (s) - (concat " " s)) - (split-string decl "\n") - "\n")) - ;; splitting function case-decls - (let (decls) - (while (string-match "^\\(\\w+\\).*\n*\\(?:\\s-+.*\n+\\)*" code) - (push (match-string 0 code) decls) - (setq code (substring code (match-end 0)))) - (reverse decls)) - "\n;\n") - "\n}")) - "\n:}\n")) - -(defun inferior-haskell-flash-decl (start end &optional timeout) - "Temporarily highlight declaration." - (let ((overlay (make-overlay start end))) - (overlay-put overlay 'face 'secondary-selection) - (run-with-timer (or timeout 0.2) nil 'delete-overlay overlay))) - -;;;###autoload -(defun inferior-haskell-send-decl () - "Send current declaration to inferior-haskell process." - (interactive) - (save-excursion - (goto-char (1+ (point))) - (let* ((proc (inferior-haskell-process)) - (start (or (haskell-ds-backward-decl) (point-min))) - (end (or (haskell-ds-forward-decl) (point-max))) - (raw-decl (buffer-substring start end))) - ;; enter multiline-prompt-cutting-mode - (with-current-buffer (process-buffer proc) - (setq inferior-haskell-send-decl-post-filter-on t)) - ;; flash decl - (inferior-haskell-flash-decl start end) - ;; send decl - (comint-send-string proc (inferior-haskell-wrap-decl raw-decl)) - ;; send preview - (inferior-haskell-send-command - proc - (let* ((str (remove ?\n raw-decl)) - (len (min 15 (length str)))) - (concat "-- evaluating {: " - (substring str 0 len) - (if (= 15 len) ".." "") - " :}")))))) - -(defun inferior-haskell-get-result (inf-expr) - "Submit the expression `inf-expr' to ghci and read the result." (let ((proc (inferior-haskell-process))) - (with-current-buffer (process-buffer proc) - (let ((parsing-end ; Remember previous spot. - (marker-position (process-mark proc)))) - (inferior-haskell-send-command proc inf-expr) - ;; Find new point. - (inferior-haskell-wait-for-prompt proc) - (goto-char (point-max)) - ;; Back up to the previous end-of-line. - (end-of-line 0) - ;; Extract the output - (buffer-substring-no-properties - (save-excursion (goto-char parsing-end) - (line-beginning-position 2)) - (point)))))) + (pop-to-buffer-same-window (process-buffer proc)))) -;;;###autoload -(defun inferior-haskell-type (expr &optional insert-value) - "Query the haskell process for the type of the given expression. -If optional argument `insert-value' is non-nil, insert the type above point -in the buffer. This can be done interactively with the \\[universal-argument] prefix. -The returned info is cached for reuse by `haskell-doc-mode'." - (interactive - (let ((sym (haskell-ident-at-point))) - (list (read-string (if sym - (format "Show type of (default %s): " sym) - "Show type of: ") - nil nil sym) - current-prefix-arg))) - (if (string-match "\\`\\s_+\\'" expr) (setq expr (concat "(" expr ")"))) - (let ((type (inferior-haskell-get-result (concat ":type " expr)))) - (if (not (string-match (concat "^\\(" (regexp-quote expr) - "[ \t\n]+\\(::\\|∷\\)[ \t\n]*\\(.\\|\n\\)*\\)") - type)) - (error "No type info: %s" type) - (progn - (setf type (match-string 1 type)) - ;; Cache for reuse by haskell-doc. - (when (and (boundp 'haskell-doc-mode) haskell-doc-mode - (boundp 'haskell-doc-user-defined-ids) - ;; Haskell-doc only works for idents, not arbitrary expr. - (string-match "\\`(?\\(\\s_+\\|\\(\\sw\\|\\s'\\)+\\)?[ \t]*\\(::\\|∷\\)[ \t]*" - type)) - (let ((sym (match-string 1 type))) - (setq haskell-doc-user-defined-ids - (cons (cons sym (substring type (match-end 0))) - (delq (assoc sym haskell-doc-user-defined-ids) - haskell-doc-user-defined-ids))))) - - (if (called-interactively-p 'any) (message "%s" type)) - (when insert-value - (beginning-of-line) - (insert type "\n")) - type)))) - -;;;###autoload -(defun inferior-haskell-kind (type) - "Query the haskell process for the kind of the given expression." - (interactive - (let ((type (haskell-ident-at-point))) - (list (read-string (if type - (format "Show kind of (default %s): " type) - "Show kind of: ") - nil nil type)))) - (let ((result (inferior-haskell-get-result (concat ":kind " type)))) - (if (called-interactively-p 'any) (message "%s" result)) - result)) - -;;;###autoload -(defun inferior-haskell-info (sym) - "Query the haskell process for the info of the given expression." - (interactive - (let ((sym (haskell-ident-at-point))) - (list (read-string (if sym - (format "Show info of (default %s): " sym) - "Show info of: ") - nil nil sym)))) - (let ((result (inferior-haskell-get-result (concat ":info " sym)))) - (if (called-interactively-p 'any) (message "%s" result)) - result)) - -;;;###autoload -(defun inferior-haskell-find-definition (sym) - "Attempt to locate and jump to the definition of the given expression." - (interactive - (let ((sym (haskell-ident-at-point))) - (list (read-string (if sym - (format "Find definition of (default %s): " sym) - "Find definition of: ") - nil nil sym)))) - (let ((info (inferior-haskell-info sym))) - (if (not (string-match inferior-haskell-info-xref-re info)) - (error "No source information available") - (let ((file (match-string-no-properties 1 info)) - (line (string-to-number - (match-string-no-properties 2 info))) - (col (string-to-number - (match-string-no-properties 3 info)))) - (when file - (with-current-buffer (process-buffer (inferior-haskell-process)) - ;; The file name is relative to the process's cwd. - (setq file (expand-file-name file))) - ;; Push current location marker on the ring used by `find-tag' - (require 'etags) - (xref-push-marker-stack) - (pop-to-buffer (find-file-noselect file)) - (when line - (goto-char (point-min)) - (forward-line (1- line)) - (when col (move-to-column col)))))))) - -;;; Functions to find the documentation of a given function. -;; -;; TODO for this section: -;; -;; * Support fetching of local Haddock docs pulled directly from source files. -;; * Display docs locally? w3m? - -(defcustom inferior-haskell-use-web-docs - 'fallback - "Whether to use the online documentation. Possible values: -`never', meaning always use local documentation, unless the local -file doesn't exist, when do nothing, `fallback', which means only -use the online documentation when the local file doesn't exist, -or `always', meaning always use the online documentation, -regardless of existance of local files. Default is `fallback'." - :group 'inferior-haskell - :type '(choice (const :tag "Never" never) - (const :tag "As fallback" fallback) - (const :tag "Always" always))) - -(defcustom inferior-haskell-web-docs-base - "http://haskell.org/ghc/docs/latest/html/libraries/" - "The base URL of the online libraries documentation. -This will only be used if the value of `inferior-haskell-use-web-docs' -is `always' or `fallback'." - :group 'inferior-haskell - :type 'string) - -(defcustom haskell-package-manager-name "ghc-pkg" - "Name of the program to consult regarding package details." - :group 'inferior-haskell - :type 'string) +(defvar inferior-haskell-result-history nil) -(defcustom haskell-package-conf-file - (condition-case nil - (with-temp-buffer - (call-process "ghc" nil t nil "--print-libdir") - (expand-file-name "package.conf" - (buffer-substring (point-min) (1- (point-max))))) - ;; Don't use `ignore-errors' because this form is not byte-compiled :-( - (error nil)) - "Where the package configuration file for the package manager resides. -By default this is set to `ghc --print-libdir`/package.conf." - :group 'inferior-haskell - :type 'string) - -(defun inferior-haskell-get-module (sym) - "Fetch the module in which SYM is defined." - (let ((info (inferior-haskell-info sym))) - (unless (string-match inferior-haskell-module-re info) - (error - "No documentation information available. Did you forget to C-c C-l?")) - (let* ((module-name (match-string-no-properties 1 info)) - (first-character (substring module-name 0 1))) - ;; Handles GHC 7.4.1+ which quotes module names like - ;; `System.Random', whereas previous GHC did not quote at all. - (if (or (string= "`" first-character) (string= "‘" first-character)) - - (substring module-name 1 (- (length module-name) 1)) - module-name)))) - -(defun inferior-haskell-query-ghc-pkg (&rest args) - "Send ARGS to `haskell-package-manager-name'. -Insert the output into the current buffer." - (apply 'call-process haskell-package-manager-name nil t nil args)) - -(defun inferior-haskell-get-package-list () - "Get the list of packages from `haskell-package-manager-name'." - (with-temp-buffer - (inferior-haskell-query-ghc-pkg "--simple-output" "list") - (split-string (buffer-substring (point-min) (point-max))))) - -(defun inferior-haskell-compute-module-alist () - "Compute a list mapping modules to package names and haddock URLs using ghc-pkg." - (message "Generating module alist...") - (let ((module-alist ())) - (with-temp-buffer - (dolist (package (inferior-haskell-get-package-list)) - (erase-buffer) - (inferior-haskell-query-ghc-pkg "describe" package) - - (let ((package-w/o-version - (replace-regexp-in-string "[-.0-9]*\\'" "" package)) - ;; Find the Haddock documentation URL for this package - (haddock - (progn - (goto-char (point-min)) - (when (re-search-forward "haddock-html:[ \t]+\\(.*[^ \t\n]\\)" - nil t) - (match-string 1))))) - - ;; Fetch the list of exposed modules for this package - (goto-char (point-min)) - (when (re-search-forward "^exposed-modules:\\(.*\\(\n[ \t].*\\)*\\)" - nil t) - (dolist (module (split-string (match-string 1))) - (push (list module package-w/o-version haddock) - module-alist))))) - - (message "Generating module alist... done") - module-alist))) - -(defcustom inferior-haskell-module-alist-file - ;; (expand-file-name "~/.inf-haskell-module-alist") - (expand-file-name (concat "inf-haskell-module-alist-" - (number-to-string (user-uid))) - temporary-file-directory) - "Where to save the module -> package lookup table. -Set this to nil to never cache to a file." - :group 'inferior-haskell - :type '(choice (const :tag "Don't cache to file" nil) string)) - -(defvar inferior-haskell-module-alist nil - "Association list of modules to their packages. -Each element is of the form (MODULE PACKAGE HADDOCK), where -MODULE is the name of a module, -PACKAGE is the package it belongs to, and -HADDOCK is the path to that package's Haddock documentation. - -This is calculated on-demand using `inferior-haskell-compute-module-alist'. -It's also cached in the file `inferior-haskell-module-alist-file', -so that it can be obtained more quickly next time.") - -(defun inferior-haskell-module-alist () - "Get the module alist from cache or ghc-pkg's info." - (or - ;; If we already have computed the alist, use it... - inferior-haskell-module-alist - (setq inferior-haskell-module-alist - (or - ;; ...otherwise try to read it from the cache file... - (and - inferior-haskell-module-alist-file - (file-readable-p inferior-haskell-module-alist-file) - (file-newer-than-file-p inferior-haskell-module-alist-file - haskell-package-conf-file) - (with-temp-buffer - (insert-file-contents inferior-haskell-module-alist-file) - (goto-char (point-min)) - (prog1 (read (current-buffer)) - (message "Read module alist from file cache.")))) - - ;; ...or generate it again and save it in a file for later. - (let ((alist (inferior-haskell-compute-module-alist))) - (when inferior-haskell-module-alist-file - (with-temp-buffer - (print alist (current-buffer)) - ;; Do the write to a temp file first, then rename it. - ;; This makes it more atomic, and suffers from fewer security - ;; holes related to race conditions if the file is in /tmp. - (let ((tmp (make-temp-file inferior-haskell-module-alist-file))) - (write-region (point-min) (point-max) tmp) - (rename-file tmp inferior-haskell-module-alist-file - 'ok-if-already-exists)))) - alist))))) - -(defvar inferior-haskell-ghc-internal-ident-alist - ;; FIXME: Fill this table, ideally semi-automatically. - '(("GHC.Base.return" . "Control.Monad.return") - ("GHC.Base.String" . "Data.String.String") - ("GHC.List" . "Data.List"))) - -(defun inferior-haskell-map-internal-ghc-ident (ident) - "Try to translate some internal GHC identifier to its alter ego in haskell docs." - (let ((head ident) - (tail "") - remapped) - (while (and (not - (setq remapped - (cdr (assoc head - inferior-haskell-ghc-internal-ident-alist)))) - (string-match "\\.[^.]+\\'" head)) - (setq tail (concat (match-string 0 head) tail)) - (setq head (substring head 0 (match-beginning 0)))) - (concat (or remapped head) tail))) +(defvar haskell-next-input "" + "This is a temporary variable to store the intermediate results while +`accecpt-process-output' with `haskell-extract-exp'") -;;;###autoload -(defun inferior-haskell-find-haddock (sym) - "Find and open the Haddock documentation of SYM. -Make sure to load the file into GHCi or Hugs first by using C-c C-l. -Only works for functions in a package installed with ghc-pkg, or -whatever the value of `haskell-package-manager-name' is. - -This function needs to find which package a given module belongs -to. In order to do this, it computes a module-to-package lookup -alist, which is expensive to compute (it takes upwards of five -seconds with more than about thirty installed packages). As a -result, we cache it across sessions using the cache file -referenced by `inferior-haskell-module-alist-file'. We test to -see if this is newer than `haskell-package-conf-file' every time -we load it." - (interactive - (let ((sym (haskell-ident-at-point))) - (list (read-string (if sym - (format "Find documentation of (default %s): " sym) - "Find documentation of: ") - nil nil sym)))) - (let* (;; Find the module and look it up in the alist - (module (inferior-haskell-get-module sym)) - (full-name (inferior-haskell-map-internal-ghc-ident (concat module "." sym))) - (_success (string-match "\\(.*\\)\\.\\(.*\\)" full-name)) - (module (match-string 1 full-name)) - (sym (match-string 2 full-name)) - (alist-record (assoc module (inferior-haskell-module-alist))) - (package (nth 1 alist-record)) - (file-name (concat (subst-char-in-string ?. ?- module) ".html")) - (local-path (concat (nth 2 alist-record) "/" file-name)) - (url (if (or (eq inferior-haskell-use-web-docs 'always) - (and (not (file-exists-p local-path)) - (eq inferior-haskell-use-web-docs 'fallback))) - (concat inferior-haskell-web-docs-base package "/" file-name) - (and (file-exists-p local-path) - (concat "file://" local-path)))) - ;; Jump to the symbol within Haddock. - (url (concat url "#v:" sym))) - (if url (browse-url url) (error "Local file doesn't exist")))) - -(defvar inf-haskell-mode-map - (let ((map (make-sparse-keymap))) - ;; (define-key map (kbd "M-C-x") 'inferior-haskell-send-defun) - ;; (define-key map (kbd "C-x C-e") 'inferior-haskell-send-last-sexp) - ;; (define-key map (kbd "C-c C-r") 'inferior-haskell-send-region) - (define-key map (kbd "C-x C-d") 'inferior-haskell-send-decl) - (define-key map (kbd "C-c C-z") 'switch-to-haskell) - (define-key map (kbd "C-c C-l") 'inferior-haskell-load-file) - ;; I think it makes sense to bind inferior-haskell-load-and-run to C-c - ;; C-r, but since it used to be bound to `reload' until June 2007, I'm - ;; going to leave it out for now. - ;; (define-key map (kbd "C-c C-r") 'inferior-haskell-load-and-run) - (define-key map (kbd "C-c C-b") 'switch-to-haskell) - ;; (define-key map (kbd "C-c C-s") 'inferior-haskell-start-process) - ;; That's what M-; is for. - (define-key map (kbd "C-c C-t") 'inferior-haskell-type) - (define-key map (kbd "C-c C-i") 'inferior-haskell-info) - (define-key map (kbd "C-c M-.") 'inferior-haskell-find-definition) - (define-key map (kbd "C-c C-d") 'inferior-haskell-find-haddock) - (define-key map (kbd "C-c C-v") 'haskell-check) - map) - "Keymap for using `inf-haskell-mode'.") +(defun haskell-extract-exp (str) + (setq haskell-next-input (concat haskell-next-input str)) + (if (with-temp-buffer + (insert haskell-next-input) + (re-search-backward haskell-prompt-regexp nil t 1)) + (progn + (push (substring haskell-next-input + 0 + (1- (with-temp-buffer + (insert haskell-next-input) + (re-search-backward haskell-prompt-regexp nil t 1)))) + inferior-haskell-result-history) + (setq haskell-next-input "")) + "")) + +(defun inferior-haskell-no-result-return (strg) + (let ((proc (inferior-haskell-process))) + (with-local-quit + (progn + (add-to-list 'comint-preoutput-filter-functions + (lambda (output) + (haskell-extract-exp output))) + (process-send-string proc strg) + (accept-process-output proc) + (sit-for 0.1) + (setq comint-preoutput-filter-functions nil))))) -;;;###autoload -(define-minor-mode inf-haskell-mode - "Minor mode for enabling inf-haskell process interaction." - :lighter " Inf-Haskell" - :keymap inf-haskell-mode-map) +(defun inferior-haskell-get-result (inf-expr) + "Submit the expression `inf-expr' to ghci and read the result." + (let* ((times 5)) + (inferior-haskell-no-result-return (concat inf-expr "\n")) + (while (and (> times 0) + (not (stringp (car inferior-haskell-result-history)))) + (setq times (1- times)) + (inferior-haskell-no-result-return (concat inf-expr "\n"))) + (haskell-string-chomp (car inferior-haskell-result-history)))) + +(defun inferior-haskell-init () + "The first thing run while initalizing inferior-haskell-buffer" + (with-local-quit + (with-current-buffer inferior-haskell-buffer + (process-send-string (inferior-haskell-process) "\n") + (accept-process-output (inferior-haskell-process)) + (sit-for 0.1)))) + +(defvar haskell-set+c-p nil + "t if `:set +c` else nil") + +(defun haskell-set+c () + "set `:set +c` is not already set" + (if (not haskell-set+c-p) + (inferior-haskell-get-result ":set +c"))) (provide 'inf-haskell) diff --git a/tests/haskell-customize-tests.el b/tests/haskell-customize-tests.el new file mode 100644 index 000000000..38f73f750 --- /dev/null +++ b/tests/haskell-customize-tests.el @@ -0,0 +1,88 @@ +;;; haskell-customize.el --- Customization settings -*- lexical-binding: t -*- + +;; Copyright (c) 2014 Vasantha Ganesh Kanniappan + +;; 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 this program. If not, see . + +;;; Code: + +(require 'ert) +(require 'haskell-customize) +(require 'haskell-test-utils) + +(defvar dir-structure nil) + +(ert-deftest haskell-process-type-test-1 () + (with-temp-dir-structure + (("README.md" . "Hello world") + ("Main.hs" . "-- Empty file") + ("abc.cabal" . "-- Empty File") + ("stack.yaml" . "# Empty file") + ("src" . (("moduleA.hs" . "-- Empty file") + ("moduleB.hs" . "-- Empty file"))) + ("tests" . (("test1.hs" . "-- Empty file") + ("test2.hs" . "-- Empty file")))) + (progn + (cd "tests") + (should (eq 'stack-ghci (haskell-process-type)))))) + +(ert-deftest haskell-process-type-test-2 () + (with-temp-dir-structure + (("README.md" . "Hello world") + ("Main.hs" . "-- Empty file") + ("stack.yaml" . "# Empty file") + ("src" . (("moduleA.hs" . "-- Empty file") + ("moduleB.hs" . "-- Empty file"))) + ("tests" . (("test1.hs" . "-- Empty file") + ("test2.hs" . "-- Empty file")))) + (progn + (cd "src") + (should (eq 'stack-ghci (haskell-process-type)))))) + +(ert-deftest haskell-process-type-test-3 () + (with-temp-dir-structure + (("README.md" . "Hello world") + ("Main.hs" . "-- Empty file") + ("abc.cabal" . "-- Empty file") + ("src" . (("moduleA.hs" . "-- Empty file") + ("moduleB.hs" . "-- Empty file"))) + ("tests" . (("test1.hs" . "-- Empty file") + ("test2.hs" . "-- Empty file")))) + (progn + (should (eq 'cabal-repl (haskell-process-type)))))) + +(ert-deftest haskell-process-type-test-3 () + (with-temp-dir-structure + (("README.md" . "Hello world") + ("Main.hs" . "-- Empty file") + ("src" . (("moduleA.hs" . "-- Empty file") + ("moduleB.hs" . "-- Empty file"))) + ("tests" . (("test1.hs" . "-- Empty file") + ("test2.hs" . "-- Empty file")))) + (progn + (should (eq 'ghci (haskell-process-type)))))) + +(ert-deftest haskell-process-type-test-4 () + :expected-result :failed + (with-temp-dir-structure + (("README.md" . "Hello world") + ("Main.hs" . "-- Empty file") + ("abc.cabal" . "-- Empty file") + ("cabal.project" . "-- Empty file") + ("src" . (("moduleA.hs" . "-- Empty file") + ("moduleB.hs" . "-- Empty file"))) + ("tests" . (("test1.hs" . "-- Empty file") + ("test2.hs" . "-- Empty file")))) + (progn + (should (eq 'cabal-new-repl (haskell-process-type)))))) diff --git a/tests/inferior-haskell-tests.el b/tests/inferior-haskell-tests.el new file mode 100644 index 000000000..c8640bb22 --- /dev/null +++ b/tests/inferior-haskell-tests.el @@ -0,0 +1,50 @@ +;;; inferior-haskell-tests.el --- tests for collapse module -*- lexical-binding: t -*- +1;4803;0c +;; Copyright © 2017 Vasantha Ganesh K. + +;; 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 this program. If not, see . + + +(require 'ert) +(require 'inf-haskell) +(require 'haskell-string) +(require 'haskell-test-utils) + +(ert-deftest test-run-haskell () + (haskell-unconditional-kill-buffer "*haskell*") + (run-haskell) + (let* ((times 5) + (ans nil)) + (setq ans (inferior-haskell-get-result "1 + 1")) + (while (and (> times 0) + (not (equal ans "2"))) + (setq times (1- times)) + (setq ans (inferior-haskell-get-result "1 + 1"))) + (should (equal ans + "2")))) + +(ert-deftest test-inferior-haskell-buffer () + "Check if the inferior haskell buffer has been started" + (haskell-unconditional-kill-buffer "*haskell*") + (run-haskell) + (should (buffer-live-p inferior-haskell-buffer))) + +(ert-deftest test-inferior-haskell-root-dir () + "Check if the root dir of the loaded file/project is not nil +This way we test is the file is loaded or not" + (haskell-unconditional-kill-buffer "*haskell*") + (run-haskell) + (should (file-directory-p inferior-haskell-root-dir)))