diff --git a/doc/haskell-mode.texi b/doc/haskell-mode.texi index e47eb9063..42f1a9741 100644 --- a/doc/haskell-mode.texi +++ b/doc/haskell-mode.texi @@ -1754,6 +1754,42 @@ To evaluate expressions, simply type one out and hit `RET`. 123 @end example +@subsection Evaluating multiline expressions + +GHCi features two ways to evaluate multiline expressions. You can use +@code{:set +m} to +enable @uref{https://www.haskell.org/ghc/docs/latest/html/users_guide/ghci.html#multiline-input, +multiline input} for all expressions, or you can wrap your expression in +@code{:@{} and @code{:@}} (they have to be on their own lines). + +The prompt will change to indicate that you're inputting a multiline +expression: + +@example +λ> :@{ +λ| let a = 10 +λ| b = 20 +λ| c = 30 +λ| :@} +@end example + +You can also simulate multiline mode by having your input contain +newline characters. You can input a literal newline character with +@kbd{C-q C-j}, or you can use: + +@example + M-x haskell-interactive-mode-newline-indent +@end example + +which is bound to @kbd{C-j}. This command indents after the newline. You +can simulate the above example like so: + +@example +λ> let a = 10 + b = 20 + c = 30 +@end example + @subsection Type of expressions You can use normal @code{:type} which is part of GHCi to get the type of diff --git a/haskell-commands.el b/haskell-commands.el index 57c60c897..c719de2af 100644 --- a/haskell-commands.el +++ b/haskell-commands.el @@ -95,7 +95,9 @@ You can create new session using function `haskell-session-make'." ;; whole produces only one prompt marker as a response. (haskell-process-send-string process "Prelude.putStrLn \"\"") (haskell-process-send-string process ":set -v1") - (haskell-process-send-string process ":set prompt \"\\4\"")) + (haskell-process-send-string process ":set prompt \"\\4\"") + (haskell-process-send-string process (format ":set prompt2 \"%s\"" + haskell-interactive-prompt2))) :live (lambda (process buffer) (when (haskell-process-consume diff --git a/haskell-customize.el b/haskell-customize.el index 158dc56f4..1ba90fc63 100644 --- a/haskell-customize.el +++ b/haskell-customize.el @@ -310,8 +310,19 @@ ambiguous class constraint." :type 'boolean :group 'haskell-interactive) -(defvar haskell-interactive-prompt "λ> " - "The prompt to use.") +(defcustom haskell-interactive-prompt "λ> " + "The prompt to use." + :type 'string + :group 'haskell-interactive) + +(defcustom haskell-interactive-prompt2 (replace-regexp-in-string + "> $" + "| " + haskell-interactive-prompt) + "The multi-line prompt to use. +The default is `haskell-interactive-prompt' with the last > replaced with |." + :type 'string + :group 'haskell-interactive) (defcustom haskell-interactive-mode-eval-mode nil diff --git a/haskell-interactive-mode.el b/haskell-interactive-mode.el index f0837e64e..03e47d828 100644 --- a/haskell-interactive-mode.el +++ b/haskell-interactive-mode.el @@ -130,6 +130,12 @@ be nil.") "Face for the prompt." :group 'haskell-interactive) +;;;###autoload +(defface haskell-interactive-face-prompt2 + '((t :inherit font-lock-keyword-face)) + "Face for the prompt2 in multi-line mode." + :group 'haskell-interactive) + ;;;###autoload (defface haskell-interactive-face-compile-error '((t :inherit compilation-error)) @@ -161,7 +167,8 @@ be nil.") "Make newline and indent." (interactive) (newline) - (indent-according-to-mode)) + (indent-to (length haskell-interactive-prompt)) + (indent-relative)) (defun haskell-interactive-mode-kill-whole-line () "Kill the whole REPL line." @@ -230,22 +237,6 @@ be nil.") (point-min)) end)))))))) -(defun haskell-interactive-mode-cleanup-response (expr response) - "Ignore the mess that GHCi outputs on multi-line input." - (if (not (string-match "\n" expr)) - response - (let ((i 0) - (out "") - (lines (length (split-string expr "\n")))) - (cl-loop for part in (split-string response "| ") - do (setq out - (concat out - (if (> i lines) - (concat (if (or (= i 0) (= i (1+ lines))) "" "| ") part) - ""))) - do (setq i (1+ i))) - out))) - (defun haskell-interactive-mode-multi-line (expr) "If a multi-line expression EXPR has been entered, then reformat it to be: @@ -254,21 +245,18 @@ do the multi-liner expr :}" - (if (not (string-match "\n" expr)) + (if (not (string-match-p "\n" expr)) expr - (let* ((i 0) - (lines (split-string expr "\n")) - (len (length lines))) - (mapconcat 'identity - (cl-loop for line in lines - collect (cond ((= i 0) - (concat ":{" "\n" line)) - ((= i (1- len)) - (concat line "\n" ":}")) - (t - line)) - do (setq i (1+ i))) - "\n")))) + (let ((len (length haskell-interactive-prompt)) + (lines (split-string expr "\n"))) + (cl-loop for elt on (cdr lines) do + (setcar elt (substring (car elt) len))) + ;; Temporarily set prompt2 to be empty to avoid unwanted output + (concat ":set prompt2 \"\"\n" + ":{\n" + (mapconcat #'identity lines "\n") + "\n:}\n" + (format ":set prompt2 \"%s\"" haskell-interactive-prompt2))))) (defun haskell-interactive-trim (line) "Trim indentation off of LINE in the REPL." @@ -331,21 +319,27 @@ SESSION, otherwise operate on the current buffer." "Insert the result of an eval as plain text." (with-current-buffer (haskell-session-interactive-buffer session) (goto-char (point-max)) - (insert (ansi-color-apply - (propertize text - 'font-lock-face 'haskell-interactive-face-result - 'front-sticky t - 'prompt t - 'read-only t - 'rear-nonsticky t - 'result t))) - (haskell-interactive-mode-handle-h) - (let ((marker (setq-local haskell-interactive-mode-result-end (make-marker)))) - (set-marker marker - (point) - (current-buffer))) - (when haskell-interactive-mode-scroll-to-bottom - (haskell-interactive-mode-scroll-to-bottom)))) + (let ((prop-text (propertize text + 'font-lock-face 'haskell-interactive-face-result + 'front-sticky t + 'prompt t + 'read-only t + 'rear-nonsticky t + 'result t))) + (when (string= text haskell-interactive-prompt2) + (put-text-property 0 + (length haskell-interactive-prompt2) + 'font-lock-face + 'haskell-interactive-face-prompt2 + prop-text)) + (insert (ansi-color-apply prop-text)) + (haskell-interactive-mode-handle-h) + (let ((marker (setq-local haskell-interactive-mode-result-end (make-marker)))) + (set-marker marker + (point) + (current-buffer))) + (when haskell-interactive-mode-scroll-to-bottom + (haskell-interactive-mode-scroll-to-bottom))))) (defun haskell-interactive-mode-scroll-to-bottom () "Scroll to bottom." diff --git a/haskell-repl.el b/haskell-repl.el index 20180b5c2..733309677 100644 --- a/haskell-repl.el +++ b/haskell-repl.el @@ -101,8 +101,7 @@ "Print the result of evaluating the expression." (let ((response (with-temp-buffer - (insert (haskell-interactive-mode-cleanup-response - (cl-caddr state) response)) + (insert response) (haskell-interactive-mode-handle-h) (buffer-string)))) (when haskell-interactive-mode-eval-mode