From 7d385585f48c9c7093ca1a4ff0448028f9fade46 Mon Sep 17 00:00:00 2001 From: Jim Porter Date: Wed, 22 May 2024 21:58:34 -0700 Subject: [PATCH 3/4] Rework how 'eshell-ensure-newline-p' adds newlines This allows for other output targets (see the following commit) to be "line-oriented". * lisp/eshell/esh-io.el (eshell-virtual-targets): Update docstring. (eshell-ensure-newline-p): Move from esh-cmd.el. (eshell-generic-target): New struct... (eshell-function-target): ... use it, and rename from 'eshell-virtual-target'. (eshell-target-line-oriented-p, eshell--output-maybe-n) (eshell-print-maybe-n, eshell-error-maybe-n) (eshell-maybe-output-newline): New functions. * lisp/eshell/esh-cmd.el (eshell-lisp-command): Don't print a newline in this function directly; instead, use 'eshell-print-maybe-n' and 'eshell-error-maybe-n'. (eshell-ensure-newline-p): Move to esh-io.el. * lisp/eshell/em-unix.el (eshell/cat): Remove now-unnecessary override of 'eshell-ensure-newline-p'. * doc/misc/eshell.texi (Redirection): Correct documentation about virtual targets. --- doc/misc/eshell.texi | 4 +- lisp/eshell/em-unix.el | 4 +- lisp/eshell/esh-cmd.el | 20 +++------ lisp/eshell/esh-io.el | 100 ++++++++++++++++++++++++++++++++--------- 4 files changed, 89 insertions(+), 39 deletions(-) diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi index 57ee3bf3e9f..bdf19bb6bd9 100644 --- a/doc/misc/eshell.texi +++ b/doc/misc/eshell.texi @@ -2402,8 +2402,8 @@ Redirection @code{append} for @code{>>}, or @code{insert} for @code{>>>}--and the function is expected to return the output function. -The output function is called once on each line of output until -@code{nil} is passed, indicating end of output. +When using the virtual target, Eshell will call the output function with +each object to output in turn. @node Pipelines @section Pipelines diff --git a/lisp/eshell/em-unix.el b/lisp/eshell/em-unix.el index 7f976d22681..4137c05fa41 100644 --- a/lisp/eshell/em-unix.el +++ b/lisp/eshell/em-unix.el @@ -683,9 +683,7 @@ eshell/cat (with-current-buffer curbuf (eshell-buffered-print str))) (forward-line))))) - (eshell-flush) - ;; if the file does not end in a newline, do not emit one - (setq eshell-ensure-newline-p nil)))) + (eshell-flush)))) (put 'eshell/cat 'eshell-no-numeric-conversions t) (put 'eshell/cat 'eshell-filename-arguments t) diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el index dae1a77552f..f77f910e63f 100644 --- a/lisp/eshell/esh-cmd.el +++ b/lisp/eshell/esh-cmd.el @@ -254,11 +254,6 @@ eshell-subcommand-bindings :type 'sexp :risky t) -(defvar eshell-ensure-newline-p nil - "If non-nil, ensure that a newline is emitted after a Lisp form. -This can be changed by Lisp forms that are evaluated from the Eshell -command line.") - ;;; Internal Variables: ;; These variables have been merged into `eshell-foreground-command'. @@ -1497,7 +1492,7 @@ eshell-lisp-command (catch 'eshell-external ; deferred to an external command (setq eshell-last-command-status 0 eshell-last-arguments args) - (let* ((eshell-ensure-newline-p (eshell-interactive-output-p)) + (let* ((eshell-ensure-newline-p t) (command-form-p (functionp object)) (result (if command-form-p @@ -1524,14 +1519,13 @@ eshell-lisp-command (setq args (cdr args)))) (setq eshell-last-command-name (concat "#")) - (eshell-apply object eshell-last-arguments)) + (eshell-apply* #'eshell-print-maybe-n + #'eshell-error-maybe-n + object eshell-last-arguments)) (setq eshell-last-command-name "#") - (eshell-eval object)))) - (if (and eshell-ensure-newline-p - (save-excursion - (goto-char eshell-last-output-end) - (not (bolp)))) - (eshell-print "\n")) + (eshell-eval* #'eshell-print-maybe-n + #'eshell-error-maybe-n + object)))) (eshell-close-handles ;; If `eshell-lisp-form-nil-is-failure' is non-nil, Lisp forms ;; that succeeded but have a nil result should have an exit diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el index 4487389bf26..83b869e307b 100644 --- a/lisp/eshell/esh-io.el +++ b/lisp/eshell/esh-io.el @@ -144,9 +144,8 @@ eshell-virtual-targets output function. Otherwise, the second element itself is the output function. -The output function is then called repeatedly with single strings, -which represents successive pieces of the output of the command, until nil -is passed, meaning EOF." +When using the virtual target, Eshell calls the output function +repeatedly with each object to output." :version "30.1" :type '(repeat (list (string :tag "Target") @@ -158,6 +157,12 @@ eshell-virtual-targets (define-error 'eshell-pipe-broken "Pipe broken") +(defvar eshell-ensure-newline-p nil + "If non-nil, ensure that a newline is emitted after a Lisp form. +This can be changed by Lisp forms that are evaluated from the +Eshell command line. This behavior only applies to line-oriented +output targets (see `eshell-target-line-oriented-p'.") + ;;; Internal Variables: (defconst eshell-redirection-operators-alist @@ -489,20 +494,48 @@ eshell-error "Output OBJECT to the standard error handle." (eshell-output-object object eshell-error-handle)) -(defsubst eshell-errorn (object) - "Output OBJECT followed by a newline to the standard error handle." - (eshell-error object) - (eshell-error "\n")) - (defsubst eshell-printn (object) "Output OBJECT followed by a newline to the standard output handle." (eshell-print object) (eshell-print "\n")) -(cl-defstruct (eshell-virtual-target +(defsubst eshell-errorn (object) + "Output OBJECT followed by a newline to the standard error handle." + (eshell-error object) + (eshell-error "\n")) + +(defun eshell--output-maybe-n (object handle) + "Output OBJECT to HANDLE. +For any line-oriented output targets on HANDLE, ensure the output +ends in a newline." + (eshell-output-object object handle) + (when (and eshell-ensure-newline-p + (not (and (stringp object) + (string-suffix-p object "\n")))) + (eshell-maybe-output-newline handle))) + +(defsubst eshell-print-maybe-n (object) + "Output OBJECT to the standard output handle. +For any line-oriented output targets, ensure the output ends in a +newline." + (eshell--output-maybe-n object eshell-output-handle)) + +(defsubst eshell-error-maybe-n (object) + "Output OBJECT to the standard error handle. +For any line-oriented output targets, ensure the output ends in a +newline." + (eshell--output-maybe-n object eshell-error-handle)) + +(cl-defstruct (eshell-generic-target (:constructor nil)) + "An Eshell target. +This is mainly useful for creating virtual targets (see +`eshell-virtual-targets').") + +(cl-defstruct (eshell-function-target + (:include eshell-generic-target) (:constructor nil) - (:constructor eshell-virtual-target-create (output-function))) - "A virtual target (see `eshell-virtual-targets')." + (:constructor eshell-function-target-create (output-function))) + "An Eshell target that calls an output function." output-function) (cl-defgeneric eshell-get-target (raw-target &optional _mode) @@ -514,14 +547,16 @@ eshell-get-target (cl-defmethod eshell-get-target ((raw-target string) &optional mode) "Convert a string RAW-TARGET into a valid output target using MODE. If TARGET is a virtual target (see `eshell-virtual-targets'), -return an `eshell-virtual-target' instance; otherwise, return a +return an `eshell-generic-target' instance; otherwise, return a marker for a file named TARGET." (setq mode (or mode 'insert)) (if-let ((redir (assoc raw-target eshell-virtual-targets))) - (eshell-virtual-target-create - (if (nth 2 redir) - (funcall (nth 1 redir) mode) - (nth 1 redir))) + (let ((target (if (nth 2 redir) + (funcall (nth 1 redir) mode) + (nth 1 redir)))) + (unless (eshell-generic-target-p target) + (setq target (eshell-function-target-create target))) + target) (let ((exists (get-file-buffer raw-target)) (buf (find-file-noselect raw-target t))) (with-current-buffer buf @@ -602,8 +637,8 @@ eshell-close-target (throw 'done nil)) (process-send-eof target)))) -(cl-defmethod eshell-close-target ((_target eshell-virtual-target) _status) - "Close a virtual TARGET." +(cl-defmethod eshell-close-target ((_target eshell-function-target) _status) + "Close an Eshell function TARGET." nil) (cl-defgeneric eshell-output-object-to-target (object target) @@ -660,9 +695,19 @@ eshell-output-object-to-target object) (cl-defmethod eshell-output-object-to-target (object - (target eshell-virtual-target)) - "Output OBJECT to the virtual TARGET." - (funcall (eshell-virtual-target-output-function target) object)) + (target eshell-function-target)) + "Output OBJECT to the Eshell function TARGET." + (funcall (eshell-function-target-output-function target) object)) + +(cl-defgeneric eshell-target-line-oriented-p (_target) + "Return non-nil if the specified TARGET is line-oriented. +Line-oriented targets are those that expect a newline after +command output when `eshell-ensure-newline-p' is non-nil." + nil) + +(cl-defmethod eshell-target-line-oriented-p ((_target (eql t))) + "Return non-nil to indicate that the display is line-oriented." + t) (defun eshell-output-object (object &optional handle-index handles) "Insert OBJECT, using HANDLE-INDEX specifically. @@ -674,5 +719,18 @@ eshell-output-object (dolist (target targets) (eshell-output-object-to-target object target)))) +(defun eshell-maybe-output-newline (&optional handle-index handles) + "Maybe insert a newline, using HANDLE-INDEX specifically. +This inserts a newline for all line-oriented output targets. + +If HANDLE-INDEX is nil, output to `eshell-output-handle'. +HANDLES is the set of file handles to use; if nil, use +`eshell-current-handles'." + (let ((targets (caar (aref (or handles eshell-current-handles) + (or handle-index eshell-output-handle))))) + (dolist (target targets) + (when (eshell-target-line-oriented-p target) + (eshell-output-object-to-target "\n" target))))) + (provide 'esh-io) ;;; esh-io.el ends here -- 2.25.1