branch: elpa/cider
commit e90868295c38241e57e63644fa1c8c9c3d3d0459
Author: Bozhidar Batsov <[email protected]>
Commit: Bozhidar Batsov <[email protected]>

    Let a stdin prompt send EOF, and document input handling
    
    When evaluated code reads from stdin, RET sends a line but there was no
    way to signal end-of-input: CIDER always appended a newline, so it could
    never send the empty string nREPL turns into EOF. Code reading to EOF
    (`slurp *in*`, a read-line loop) therefore couldn't be satisfied
    interactively - the only escape was to interrupt the whole evaluation.
    
    Add `C-c C-d` at the stdin prompt to send EOF (an empty string), so
    `read-line` returns nil and `slurp` completes. Also document stdin
    handling - the prompt and its RET/EOF/cancel keys - in the Code
    Evaluation manual, where it was previously undocumented.
---
 CHANGELOG.md                                      |  1 +
 doc/modules/ROOT/pages/usage/code_evaluation.adoc | 31 +++++++++++++++++++++++
 lisp/cider-client.el                              | 31 +++++++++++++++++++----
 test/cider-client-tests.el                        | 21 +++++++++++++--
 4 files changed, 77 insertions(+), 7 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0b77f74f54..db21bbd3c3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
 
 ### New features
 
+- [#4108](https://github.com/clojure-emacs/cider/pull/4108): Add `C-c C-d` at 
the stdin prompt (shown when evaluated code reads input) to send end-of-input, 
so `read-line` returns `nil` and `(slurp *in*)` completes - code reading to EOF 
can now be satisfied interactively instead of only by interrupting the 
evaluation.
 - [#4094](https://github.com/clojure-emacs/cider/pull/4094): Add 
`cider-doctor`, which checks your Emacs setup (and the active nREPL session, if 
any) for common problems and shows a copy-pasteable report - 
Emacs/CIDER/dependency versions, build tools on the path, stale byte-code, 
leftover obsolete config, and cider-nrepl/nREPL/Clojure compatibility.
 - Render fetched `text/html` content as formatted text (via `shr`) in the REPL 
and the rich-content popup, and make the URL of an external-content result a 
clickable link that opens in the browser (next to its `[show content]` button). 
Remote images in that HTML are not fetched, so rendering a result never makes a 
network request on its own (only inline `data:` images render).
 - Honor content types for interactive evaluations too 
([#2476](https://github.com/clojure-emacs/cider/issues/2476)): a 
`cider-eval-last-sexp` returning an image can now render it, per the new 
`cider-eval-rich-content-destination` - `inline` (the default, in the result 
overlay at point), `repl` (like results of forms typed at the prompt, with the 
`[show content]` button for external references), `popup` (the `*cider-result*` 
buffer) or `nil` (plain values, the previous behavior).
diff --git a/doc/modules/ROOT/pages/usage/code_evaluation.adoc 
b/doc/modules/ROOT/pages/usage/code_evaluation.adoc
index dbcd11385b..daa3bb947f 100644
--- a/doc/modules/ROOT/pages/usage/code_evaluation.adoc
+++ b/doc/modules/ROOT/pages/usage/code_evaluation.adoc
@@ -159,6 +159,37 @@ for sync eval. You can adjust this default if needed:
 
 NOTE: CIDER internally increases the timeout to 30 seconds for the first sync 
eval request it does, as it might require a lot of namespaces and take more 
time to complete. See `cider--prep-interactive-eval` for details.
 
+== Reading Input from Running Code
+
+When code you evaluate reads from standard input - `(read-line)`, `(read)`,
+`(slurp *in*)`, and so on - the evaluation blocks until you supply that input.
+CIDER prompts for it at the minibuffer with a `Stdin` prompt. This happens for
+any evaluation, whether you triggered it from the REPL or from a source buffer,
+and the input is routed back to the exact connection that asked for it.
+
+At the prompt you have three options:
+
+|===
+| Key | Effect
+
+| kbd:[RET]
+| Send the typed text as one line (a trailing newline is added, so `read-line`
+returns it).
+
+| kbd:[C-c C-d]
+| Send *end-of-input* (EOF). `read-line` returns `nil`, `(slurp *in*)`
+completes, and read loops terminate. This is distinct from pressing kbd:[RET]
+on an empty line, which sends an empty line (so `read-line` returns `""` and 
the
+program keeps reading).
+
+| kbd:[C-c C-c]
+| Cancel: interrupt the blocked evaluation altogether.
+|===
+
+TIP: If code reads until end-of-stream (for example `(slurp *in*)` or a
+`(loop [] (when-let [line (read-line)] ... (recur)))`), you need kbd:[C-c C-d]
+to finish it - there is no other way to signal that no more input is coming.
+
 == Configuration
 
 === Enable evaluation interrupts on Java 21 and newer
diff --git a/lisp/cider-client.el b/lisp/cider-client.el
index 7906b80366..b0cca1f09e 100644
--- a/lisp/cider-client.el
+++ b/lisp/cider-client.el
@@ -1195,8 +1195,23 @@ side effects; only the global handlers fire (need-input, 
eval-error, ...)."
 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): "))
+      (format "Stdin for %s (C-c C-d = EOF, C-c C-c = cancel): " (buffer-name 
repl))
+    "Stdin (C-c C-d = EOF, C-c C-c = cancel): "))
+
+(defvar cider-need-input--eof nil
+  "Set by `cider-need-input-send-eof' while a stdin prompt is open.
+`cider-need-input' binds it and checks it after the read to decide whether to
+send end-of-input rather than a line.")
+
+(defun cider-need-input-send-eof ()
+  "End the current stdin read by sending end-of-input to the evaluation.
+Bound in the stdin minibuffer prompt (see `cider-need-input'); the empty
+string CIDER sends is what nREPL turns into EOF, so `read-line' returns nil,
+`slurp' completes, and read loops terminate - which pressing \\`RET' (an empty
+line) does not do."
+  (interactive)
+  (setq cider-need-input--eof t)
+  (exit-minibuffer))
 
 (defun cider--connection-for-session (session)
   "Return the connection buffer whose nREPL SESSION matches, or nil.
@@ -1222,7 +1237,10 @@ RESPONSE is the nREPL need-input response (the message 
whose status is
 \"need-input\").  When given, its `session' selects the exact connection to
 answer (important with several connections) and its `id' lets a cancel
 interrupt just the blocked evaluation.  Without it, both fall back to
-`cider-current-repl' and a connection-wide interrupt."
+`cider-current-repl' and a connection-wide interrupt.
+
+At the prompt, \\`RET' sends the line, `cider-need-input-send-eof' sends
+end-of-input, and cancelling (quit) interrupts the evaluation."
   (with-current-buffer buffer
     (let* ((session (and response (nrepl-dict-get response "session")))
            (id (and response (nrepl-dict-get response "id")))
@@ -1231,9 +1249,12 @@ interrupt just the blocked evaluation.  Without it, both 
fall back to
            (map (make-sparse-keymap)))
       (set-keymap-parent map minibuffer-local-map)
       (define-key map (kbd "C-c C-c") #'abort-recursive-edit)
+      (define-key map (kbd "C-c C-d") #'cider-need-input-send-eof)
       (condition-case nil
-          (let ((input (read-from-minibuffer (cider-need-input--prompt repl) 
nil map)))
-            (nrepl-request:stdin (concat input "\n")
+          (let* ((cider-need-input--eof nil)
+                 (input (read-from-minibuffer (cider-need-input--prompt repl) 
nil map)))
+            ;; An empty string is what nREPL turns into EOF; a line otherwise.
+            (nrepl-request:stdin (if cider-need-input--eof "" (concat input 
"\n"))
                                  (cider-stdin-handler buffer)
                                  repl))
         (quit
diff --git a/test/cider-client-tests.el b/test/cider-client-tests.el
index 65fdea275d..460887f4d5 100644
--- a/test/cider-client-tests.el
+++ b/test/cider-client-tests.el
@@ -564,7 +564,7 @@
   (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): "))
+            :to-equal "Stdin (C-c C-d = EOF, C-c C-c = cancel): "))
 
   (it "names the REPL when the session has several connections"
     (let ((repl (generate-new-buffer " *cider-repl foo*")))
@@ -572,7 +572,7 @@
           (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): "
+                    :to-equal (format "Stdin for %s (C-c C-d = EOF, C-c C-c = 
cancel): "
                                       (buffer-name repl))))
         (kill-buffer repl)))))
 
@@ -595,6 +595,16 @@
     (expect (car (spy-calls-args-for 'nrepl-request:stdin 0)) :to-equal 
"hello\n")
     (expect 'cider-interrupt-repl :not :to-have-been-called))
 
+  (it "sends EOF (an empty string) when end-of-input is requested"
+    ;; simulate C-c C-d: `cider-need-input-send-eof' sets the flag mid-read
+    (spy-on 'read-from-minibuffer :and-call-fake
+            (lambda (&rest _) (setq cider-need-input--eof t) ""))
+    (with-temp-buffer
+      (cider-need-input (current-buffer)))
+    ;; an empty string, not "\n" - that is what nREPL turns into EOF
+    (expect (car (spy-calls-args-for 'nrepl-request:stdin 0)) :to-equal "")
+    (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
@@ -645,3 +655,10 @@
       (cider-need-input (current-buffer) '(dict "session" "s1" "id" "42")))
     (expect 'nrepl-request:interrupt :to-have-been-called-with "42" #'ignore 
conn)
     (expect 'cider-interrupt-repl :not :to-have-been-called)))
+
+(describe "cider-need-input-send-eof"
+  (it "sets the EOF flag"
+    (let ((cider-need-input--eof nil))
+      ;; `exit-minibuffer' errors outside a minibuffer; the flag is set first
+      (ignore-errors (cider-need-input-send-eof))
+      (expect cider-need-input--eof :to-be t))))

Reply via email to