branch: elpa/cider
commit e4a9ca617cb9712c770aa8f11041ef557f838f2e
Author: Bozhidar Batsov <[email protected]>
Commit: Bozhidar Batsov <[email protected]>
Interrupt the evaluation when cancelling a stdin prompt
When evaluated code reads from stdin, CIDER prompts for input at the
minibuffer. Cancelling that prompt sent `nil' as the stdin value, which
bencodes to an empty list rather than a string - a malformed `stdin' op,
so the blocked read never unblocked cleanly. Cancel now interrupts the
pending evaluation instead, which is what the user actually wants.
Also clarify the prompt: it mentions that C-c C-c cancels, and names the
REPL when the session has more than one connection so it's clear which
evaluation is waiting.
---
CHANGELOG.md | 1 +
lisp/cider-client.el | 31 +++++++++++++++++++++++--------
test/cider-client-tests.el | 42 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 66 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9fcad15031..8ba728b0d3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -47,6 +47,7 @@
### Bugs fixed
+- [#4106](https://github.com/clojure-emacs/cider/pull/4106): Cancelling a
stdin prompt (when evaluated code reads input) now interrupts the evaluation
instead of sending a malformed `stdin` op that left the read hung. The prompt
also names the REPL when the session has more than one connection.
- [#4093](https://github.com/clojure-emacs/cider/pull/4093): Fix a crash
(`wrong-type-argument stringp nil`) when a REPL result is truncated by the
print middleware.
- [#4089](https://github.com/clojure-emacs/cider/issues/4089): Fix
`cider-macroexpand-undo` failing with a read-only error in the macroexpansion
buffer.
- [#4085](https://github.com/clojure-emacs/cider/pull/4085): Resolve
unqualified ClojureScript core vars (e.g. their indentation metadata) against
`cljs.core` in a cljs REPL, instead of always falling back to `clojure.core`.
diff --git a/lisp/cider-client.el b/lisp/cider-client.el
index 6c5d08d257..ebc6a652d0 100644
--- a/lisp/cider-client.el
+++ b/lisp/cider-client.el
@@ -1190,18 +1190,33 @@ Intentionally swallows value/out/err so the response is
consumed without
side effects; only the global handlers fire (need-input, eval-error, ...)."
(cider-make-eval-handler :buffer (current-buffer)))
+(defun cider-need-input--prompt (repl)
+ "Return the stdin minibuffer prompt for REPL.
+When the session has more than one connection, name REPL so it's clear which
+evaluation is waiting for input."
+ (if (and (bufferp repl) (cdr (cider-repls)))
+ (format "Stdin for %s (C-c C-c to cancel): " (buffer-name repl))
+ "Stdin (C-c C-c to cancel): "))
+
(defun cider-need-input (buffer)
- "Handle a need-input request from BUFFER."
+ "Handle a need-input request from BUFFER.
+Read a line at the minibuffer and send it to the blocked evaluation.
+Cancelling the prompt interrupts that evaluation instead: the previous
+behavior sent nil, which bencodes to an empty list rather than a string, so
+the `stdin' op was malformed and the read never unblocked cleanly."
(with-current-buffer buffer
- (let ((map (make-sparse-keymap)))
+ (let ((repl (cider-current-repl))
+ (map (make-sparse-keymap)))
(set-keymap-parent map minibuffer-local-map)
(define-key map (kbd "C-c C-c") #'abort-recursive-edit)
- (let ((stdin (condition-case nil
- (concat (read-from-minibuffer "Stdin: " nil map) "\n")
- (quit nil))))
- (nrepl-request:stdin stdin
- (cider-stdin-handler buffer)
- (cider-current-repl))))))
+ (condition-case nil
+ (let ((input (read-from-minibuffer (cider-need-input--prompt repl)
nil map)))
+ (nrepl-request:stdin (concat input "\n")
+ (cider-stdin-handler buffer)
+ repl))
+ (quit
+ (when (buffer-live-p repl)
+ (cider-interrupt-repl repl)))))))
(provide 'cider-client)
diff --git a/test/cider-client-tests.el b/test/cider-client-tests.el
index 5f1d5f5f8a..b0d5747f48 100644
--- a/test/cider-client-tests.el
+++ b/test/cider-client-tests.el
@@ -559,3 +559,45 @@
(cider-nrepl-send-eval-request "(+ 1 1)" #'ignore :connection repl)
(expect 'cider-spinner-start :not :to-have-been-called))
(kill-buffer repl)))))
+
+(describe "cider-need-input--prompt"
+ (it "uses a plain prompt with a single connection"
+ (spy-on 'cider-repls :and-return-value (list 'repl-a))
+ (expect (cider-need-input--prompt (generate-new-buffer " *repl*"))
+ :to-equal "Stdin (C-c C-c to cancel): "))
+
+ (it "names the REPL when the session has several connections"
+ (let ((repl (generate-new-buffer " *cider-repl foo*")))
+ (unwind-protect
+ (progn
+ (spy-on 'cider-repls :and-return-value (list repl 'repl-b))
+ (expect (cider-need-input--prompt repl)
+ :to-equal (format "Stdin for %s (C-c C-c to cancel): "
+ (buffer-name repl))))
+ (kill-buffer repl)))))
+
+(describe "cider-need-input"
+ :var (repl)
+ (before-each
+ (setq repl (generate-new-buffer " *repl*"))
+ (spy-on 'cider-current-repl :and-return-value repl)
+ (spy-on 'cider-repls :and-return-value (list repl))
+ (spy-on 'nrepl-request:stdin)
+ (spy-on 'cider-interrupt-repl))
+ (after-each
+ (when (buffer-live-p repl) (kill-buffer repl)))
+
+ (it "sends the entered line, newline-terminated, to the blocked REPL"
+ (spy-on 'read-from-minibuffer :and-return-value "hello")
+ (with-temp-buffer
+ (cider-need-input (current-buffer)))
+ (expect 'nrepl-request:stdin :to-have-been-called)
+ (expect (car (spy-calls-args-for 'nrepl-request:stdin 0)) :to-equal
"hello\n")
+ (expect 'cider-interrupt-repl :not :to-have-been-called))
+
+ (it "interrupts the evaluation when the prompt is cancelled"
+ (spy-on 'read-from-minibuffer :and-call-fake (lambda (&rest _) (signal
'quit nil)))
+ (with-temp-buffer
+ (cider-need-input (current-buffer)))
+ (expect 'cider-interrupt-repl :to-have-been-called-with repl)
+ (expect 'nrepl-request:stdin :not :to-have-been-called)))