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)))

Reply via email to