master b9a311eccec 7/8: Allow sending interactive input to Eshell workers

Jim Porter <[email protected]>
Newsgroups gmane.emacs.diffs
Message-ID <[email protected]>
branch: master
commit b9a311eccecfe421d18bee8dc5008c1a53a29557
Author: Jim Porter <[email protected]>
Commit: Jim Porter <[email protected]>

    Allow sending interactive input to Eshell workers
    
    * lisp/eshell/esh-io.el (eshell-send-eof-to-target)
    (eshell-output-region-to-target): New generic methods.
    
    * lisp/eshell/esh-mode.el (eshell-send-input): Call
    'eshell-output-region-to-target'.
    (eshell-send-eof-to-process): Call 'eshell-send-eof-to-target'.
    
    * lisp/eshell/esh-worker.el (eshell-worker-initialize): New function.
    
    * test/lisp/eshell/esh-worker-tests.el
    (esh-worker-test/map-lines/interactive)
    (esh-worker-test/apply-lines/interactive): New tests.
---
 lisp/eshell/esh-io.el                | 24 ++++++++++++++++++++++++
 lisp/eshell/esh-mode.el              | 10 +++++-----
 lisp/eshell/esh-worker.el            |  7 +++++++
 test/lisp/eshell/esh-worker-tests.el | 31 +++++++++++++++++++++++++++++++
 4 files changed, 67 insertions(+), 5 deletions(-)

diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el
index 395a641aed6..39e456ff9d3 100644
--- a/lisp/eshell/esh-io.el
+++ b/lisp/eshell/esh-io.el
@@ -702,6 +702,15 @@ If status is nil, prompt before killing."
   (when-let* ((close-function (eshell-function-target-close-function target)))
     (funcall close-function status)))
 
+(cl-defgeneric eshell-send-eof-to-target (target)
+  "Send end-of-file to TARGET.
+By default, this is equivalent to `eshell-close-target' with nil status."
+  (eshell-close-target target nil))
+
+(cl-defmethod eshell-send-eof-to-target ((target process))
+  "Send end-of-file to a TARGET process."
+  (process-send-eof target))
+
 (cl-defgeneric eshell-output-object-to-target (object target)
   "Output OBJECT to TARGET.
 Returns what was actually sent, or nil if nothing was sent.")
@@ -766,6 +775,21 @@ command output when `eshell-ensure-newline-p' is non-nil."
   "Return non-nil to indicate that the display is line-oriented."
   t)
 
+(cl-defgeneric eshell-output-region-to-target (start end target)
+  "Output the region from START to END to TARGET."
+  (eshell-output-object-to-target (buffer-substring start end) target))
+
+(cl-defmethod eshell-output-region-to-target (start end (target process))
+  "Output the region from START to END to the process TARGET."
+  (condition-case _
+      (process-send-region target start end)
+    (error
+     ;; NOTE: When running Emacs in batch mode (e.g. during regression
+     ;; tests), Emacs can abort due to SIGPIPE here.  Maybe
+     ;; `process-send-region' should handle SIGPIPE even in batch mode
+     ;; (bug#66186).
+     (signal 'eshell-pipe-broken (list target)))))
+
 (defun eshell-output-object (object &optional handle-index handles)
   "Insert OBJECT, using HANDLE-INDEX specifically.
 If HANDLE-INDEX is nil, output to `eshell-output-handle'.
diff --git a/lisp/eshell/esh-mode.el b/lisp/eshell/esh-mode.el
index d397618544d..9860af9c000 100644
--- a/lisp/eshell/esh-mode.el
+++ b/lisp/eshell/esh-mode.el
@@ -646,10 +646,10 @@ newline."
 	    (if (or eshell-send-direct-to-subprocesses
 		    (= eshell-last-input-start eshell-last-input-end))
 		(unless no-newline
-		  (process-send-string (eshell-head-process) "\n"))
-	      (process-send-region (eshell-head-process)
-				   eshell-last-input-start
-				   eshell-last-input-end)))
+		  (eshell-output-object-to-target "\n" (eshell-head-process)))
+	      (eshell-output-region-to-target eshell-last-input-start
+				              eshell-last-input-end
+                                              (eshell-head-process))))
 	(if (= eshell-last-output-end (point))
 	    (run-hooks 'eshell-post-command-hook)
 	  (let (input)
@@ -686,7 +686,7 @@ newline."
   (interactive)
   (eshell-send-input nil nil t)
   (when (eshell-head-process)
-    (process-send-eof (eshell-head-process))))
+    (eshell-send-eof-to-target (eshell-head-process))))
 
 (defsubst eshell-kill-new ()
   "Add the last input text to the kill ring."
diff --git a/lisp/eshell/esh-worker.el b/lisp/eshell/esh-worker.el
index 9c8f613ef82..aacb4aeed8f 100644
--- a/lisp/eshell/esh-worker.el
+++ b/lisp/eshell/esh-worker.el
@@ -57,6 +57,7 @@
 
 (require 'esh-io)
 
+(defvar eshell-complex-commands)
 (declare-function eshell-set-exit-info "esh-cmd" (status &optional result))
 
 (defgroup eshell-worker nil
@@ -66,6 +67,12 @@ command's output with ordinary Lisp."
   :tag "Worker support"
   :group 'eshell)
 
+(defun eshell-worker-initialize ()   ;Called from `eshell-mode' via intern-soft!
+  "Initialize the Eshell worker code."
+  (setq-local eshell-complex-commands
+              (append '("accumulate" "apply-lines" "map-lines")
+                      eshell-complex-commands)))
+
 (cl-defstruct (eshell-worker
                (:constructor eshell-worker-create)
                (:copier nil))
diff --git a/test/lisp/eshell/esh-worker-tests.el b/test/lisp/eshell/esh-worker-tests.el
index b9b1ea3c256..d51f5319a59 100644
--- a/test/lisp/eshell/esh-worker-tests.el
+++ b/test/lisp/eshell/esh-worker-tests.el
@@ -159,6 +159,21 @@ It should call the mapped function once per line."
      "{echo '10\n1'; echo '5\n20'} | map-lines #'1+"
      "\\`11\n16\n21\n\\'")))
 
+(ert-deftest esh-worker-test/map-lines/interactive ()
+  "Test that `map-lines' works interactively."
+  (with-temp-eshell
+    (let (output-begin)
+      (eshell-insert-command "map-lines #'upcase")
+      (insert "hello")
+      (setq output-begin (1+ (point)))  ; Add one to skip the next newline.
+      (eshell-send-input)
+      (should (string= (buffer-substring output-begin (point)) "HELLO\n"))
+      (insert "goodbye")
+      (setq output-begin (1+ (point)))
+      (eshell-send-input)
+      (should (string= (buffer-substring output-begin (point)) "GOODBYE\n"))
+      (eshell-send-eof-to-process))))
+
 (ert-deftest esh-worker-test/map-lines/error-handling ()
   "Test that `map-lines' catches errors."
   (with-temp-eshell
@@ -221,4 +236,20 @@ It should pass each line as an argument to the applied function."
              "\"hi\"\n\\'"))
     (should (= eshell-last-command-status 1))))
 
+(ert-deftest esh-worker-test/apply-lines/interactive ()
+  "Test that `apply-lines' works interactively."
+  (with-temp-eshell
+    (let (output-begin)
+      (eshell-insert-command "apply-lines #'+")
+      (insert "5")
+      (eshell-send-input)
+      (insert "8")
+      (eshell-send-input)
+      (insert "13")
+      (eshell-send-input)
+      (setq output-begin (point))
+      (eshell-send-eof-to-process)
+      (should (string= (buffer-substring output-begin (eshell-end-of-output))
+                       "26\n")))))
+
 ;;; esh-io-tests.el ends here
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.