diff options
| author | Jim Porter | 2024-05-23 14:52:07 -0700 |
|---|---|---|
| committer | Jim Porter | 2024-05-29 12:09:05 -0700 |
| commit | 9daf1085a9b11e056079edce8dccca92d69bf891 (patch) | |
| tree | 955af8fdd760efca90732a9742e04d4d2f5a6712 | |
| parent | 4c924a53334035dc4089b24174012b54c020631b (diff) | |
| download | emacs-9daf1085a9b11e056079edce8dccca92d69bf891.tar.gz emacs-9daf1085a9b11e056079edce8dccca92d69bf891.zip | |
Add ability for Eshell virtual targets to handle closing the target
This was documented to work by calling the output function with 'nil',
but that was never actually implemented. Instead, for compatibility,
we now support a new (optional) close function.
* lisp/eshell/esh-io.el (eshell-virtual-targets): Update docstring.
(eshell-generic-target): New struct...
(eshell-function-target): ... inherit from it, and rename from
'eshell-virtual-target'.
(eshell-get-target): Handle already-created 'eshell-generic-target'.
(eshell-close-target): Call the target's close function if present.
* test/lisp/eshell/esh-io-tests.el (esh-io-test/virtual/device-close):
New test.
* doc/misc/eshell.texi (Redirection): Document the new behavior.
| -rw-r--r-- | doc/misc/eshell.texi | 41 | ||||
| -rw-r--r-- | lisp/eshell/esh-io.el | 68 | ||||
| -rw-r--r-- | test/lisp/eshell/esh-io-tests.el | 15 |
3 files changed, 85 insertions, 39 deletions
diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi index 57ee3bf3e9f..2da132e01eb 100644 --- a/doc/misc/eshell.texi +++ b/doc/misc/eshell.texi | |||
| @@ -2392,18 +2392,35 @@ Adds the text passed to it to the clipboard. | |||
| 2392 | @end table | 2392 | @end table |
| 2393 | 2393 | ||
| 2394 | @vindex eshell-virtual-targets | 2394 | @vindex eshell-virtual-targets |
| 2395 | You can, of course, define your own virtual targets. They are defined | 2395 | @vindex eshell-generic-target |
| 2396 | by adding a list of the form @samp{("/dev/name" @var{function} | 2396 | @findex eshell-output-object-to-target |
| 2397 | @var{mode})} to @code{eshell-virtual-targets}. The first element is | 2397 | @findex eshell-close-target |
| 2398 | the device name; @var{function} may be either a lambda or a function | 2398 | You can, of course, define your own virtual targets. These are entries |
| 2399 | name. If @var{mode} is @code{nil}, then the function is the output | 2399 | in @code{eshell-virtual-targets} with the form @samp{(@var{filename} |
| 2400 | function; if it is non-@code{nil}, then the function is passed the | 2400 | @var{output-function} @var{pass-mode})}. The first element, |
| 2401 | redirection mode as a symbol--@code{overwrite} for @code{>}, | 2401 | @var{filename}, is the device name, usually of the form |
| 2402 | @code{append} for @code{>>}, or @code{insert} for @code{>>>}--and the | 2402 | @samp{"/dev/@var{name}"}. The second, @var{output-function}, should be a |
| 2403 | function is expected to return the output function. | 2403 | function: Eshell will repeatedly call it with the redirected output. |
| 2404 | 2404 | This argument can also be an @code{eshell-generic-target} instance. In | |
| 2405 | The output function is called once on each line of output until | 2405 | this case, Eshell will repeatedly call the generic function |
| 2406 | @code{nil} is passed, indicating end of output. | 2406 | @code{eshell-output-object-to-target} with the output; once the |
| 2407 | redirection has completed, Eshell will then call the generic function | ||
| 2408 | @code{eshell-close-target}, passing non-@code{nil} if the redirected | ||
| 2409 | command succeeded. | ||
| 2410 | |||
| 2411 | If @var{pass-mode} is non-@code{nil}, then Eshell will pass the | ||
| 2412 | redirection mode as an argument to @code{output-function} as a | ||
| 2413 | symbol: @code{overwrite} for @code{>}, @code{append} for @code{>>}, or | ||
| 2414 | @code{insert} for @code{>>>}. In this case, @code{output-function} | ||
| 2415 | should return the real output function (either an ordinary function or | ||
| 2416 | an @code{eshell-generic-target} as described above). | ||
| 2417 | |||
| 2418 | @defun eshell-function-target-create output-function &optional close-function | ||
| 2419 | Create a new virtual target for Eshell that repeatedly calls | ||
| 2420 | @var{output-function} with the redirected output, as described above. | ||
| 2421 | If @var{close-function} is non-nil, Eshell will call it when closing the | ||
| 2422 | target, passing non-@code{nil} if the redirected command succeeded. | ||
| 2423 | @end defun | ||
| 2407 | 2424 | ||
| 2408 | @node Pipelines | 2425 | @node Pipelines |
| 2409 | @section Pipelines | 2426 | @section Pipelines |
diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el index 4487389bf26..9b35125cd28 100644 --- a/lisp/eshell/esh-io.el +++ b/lisp/eshell/esh-io.el | |||
| @@ -135,18 +135,22 @@ from executing while Emacs is redisplaying." | |||
| 135 | #'eshell-clipboard-append) | 135 | #'eshell-clipboard-append) |
| 136 | t)) | 136 | t)) |
| 137 | "Map virtual devices name to Emacs Lisp functions. | 137 | "Map virtual devices name to Emacs Lisp functions. |
| 138 | If the user specifies any of the filenames above as a redirection | 138 | Each member is of the following form: |
| 139 | target, the function in the second element will be called. | 139 | |
| 140 | 140 | (FILENAME OUTPUT-FUNCTION [PASS-MODE]) | |
| 141 | If the third element is non-nil, the redirection mode is passed as an | 141 | |
| 142 | argument (which is the symbol `overwrite', `append' or `insert'), and | 142 | When the user specifies FILENAME as a redirection target, Eshell will |
| 143 | the function is expected to return another function -- which is the | 143 | repeatedly call the OUTPUT-FUNCTION with the redirected output as |
| 144 | output function. Otherwise, the second element itself is the output | 144 | strings. OUTPUT-FUNCTION can also be an `eshell-generic-target' |
| 145 | function. | 145 | instance. In this case, Eshell will repeatedly call the function in the |
| 146 | 146 | `output-function' slot with the string output; once the redirection has | |
| 147 | The output function is then called repeatedly with single strings, | 147 | completed, Eshell will then call the function in the `close-function' |
| 148 | which represents successive pieces of the output of the command, until nil | 148 | slot, passing the exit status of the redirected command. |
| 149 | is passed, meaning EOF." | 149 | |
| 150 | If PASS-MODE is non-nil, Eshell will pass the redirection mode as an | ||
| 151 | argument (which is the symbol `overwrite', `append' or `insert') to | ||
| 152 | OUTPUT-FUNCTION, which should return the real output function (either an | ||
| 153 | ordinary function or `eshell-generic-target' as desribed above)." | ||
| 150 | :version "30.1" | 154 | :version "30.1" |
| 151 | :type '(repeat | 155 | :type '(repeat |
| 152 | (list (string :tag "Target") | 156 | (list (string :tag "Target") |
| @@ -499,11 +503,18 @@ after all printing is over with no argument." | |||
| 499 | (eshell-print object) | 503 | (eshell-print object) |
| 500 | (eshell-print "\n")) | 504 | (eshell-print "\n")) |
| 501 | 505 | ||
| 502 | (cl-defstruct (eshell-virtual-target | 506 | (cl-defstruct (eshell-generic-target (:constructor nil)) |
| 507 | "An Eshell target. | ||
| 508 | This is mainly useful for creating virtual targets (see | ||
| 509 | `eshell-virtual-targets').") | ||
| 510 | |||
| 511 | (cl-defstruct (eshell-function-target | ||
| 512 | (:include eshell-generic-target) | ||
| 503 | (:constructor nil) | 513 | (:constructor nil) |
| 504 | (:constructor eshell-virtual-target-create (output-function))) | 514 | (:constructor eshell-function-target-create |
| 505 | "A virtual target (see `eshell-virtual-targets')." | 515 | (output-function &optional close-function))) |
| 506 | output-function) | 516 | "An Eshell target that calls an OUTPUT-FUNCTION." |
| 517 | output-function close-function) | ||
| 507 | 518 | ||
| 508 | (cl-defgeneric eshell-get-target (raw-target &optional _mode) | 519 | (cl-defgeneric eshell-get-target (raw-target &optional _mode) |
| 509 | "Convert RAW-TARGET, which is a raw argument, into a valid output target. | 520 | "Convert RAW-TARGET, which is a raw argument, into a valid output target. |
| @@ -514,14 +525,16 @@ it defaults to `insert'." | |||
| 514 | (cl-defmethod eshell-get-target ((raw-target string) &optional mode) | 525 | (cl-defmethod eshell-get-target ((raw-target string) &optional mode) |
| 515 | "Convert a string RAW-TARGET into a valid output target using MODE. | 526 | "Convert a string RAW-TARGET into a valid output target using MODE. |
| 516 | If TARGET is a virtual target (see `eshell-virtual-targets'), | 527 | If TARGET is a virtual target (see `eshell-virtual-targets'), |
| 517 | return an `eshell-virtual-target' instance; otherwise, return a | 528 | return an `eshell-generic-target' instance; otherwise, return a |
| 518 | marker for a file named TARGET." | 529 | marker for a file named TARGET." |
| 519 | (setq mode (or mode 'insert)) | 530 | (setq mode (or mode 'insert)) |
| 520 | (if-let ((redir (assoc raw-target eshell-virtual-targets))) | 531 | (if-let ((redir (assoc raw-target eshell-virtual-targets))) |
| 521 | (eshell-virtual-target-create | 532 | (let ((target (if (nth 2 redir) |
| 522 | (if (nth 2 redir) | 533 | (funcall (nth 1 redir) mode) |
| 523 | (funcall (nth 1 redir) mode) | 534 | (nth 1 redir)))) |
| 524 | (nth 1 redir))) | 535 | (unless (eshell-generic-target-p target) |
| 536 | (setq target (eshell-function-target-create target))) | ||
| 537 | target) | ||
| 525 | (let ((exists (get-file-buffer raw-target)) | 538 | (let ((exists (get-file-buffer raw-target)) |
| 526 | (buf (find-file-noselect raw-target t))) | 539 | (buf (find-file-noselect raw-target t))) |
| 527 | (with-current-buffer buf | 540 | (with-current-buffer buf |
| @@ -602,9 +615,10 @@ If status is nil, prompt before killing." | |||
| 602 | (throw 'done nil)) | 615 | (throw 'done nil)) |
| 603 | (process-send-eof target)))) | 616 | (process-send-eof target)))) |
| 604 | 617 | ||
| 605 | (cl-defmethod eshell-close-target ((_target eshell-virtual-target) _status) | 618 | (cl-defmethod eshell-close-target ((target eshell-function-target) status) |
| 606 | "Close a virtual TARGET." | 619 | "Close an Eshell function TARGET." |
| 607 | nil) | 620 | (when-let ((close-function (eshell-function-target-close-function target))) |
| 621 | (funcall close-function status))) | ||
| 608 | 622 | ||
| 609 | (cl-defgeneric eshell-output-object-to-target (object target) | 623 | (cl-defgeneric eshell-output-object-to-target (object target) |
| 610 | "Output OBJECT to TARGET. | 624 | "Output OBJECT to TARGET. |
| @@ -660,9 +674,9 @@ Returns what was actually sent, or nil if nothing was sent.") | |||
| 660 | object) | 674 | object) |
| 661 | 675 | ||
| 662 | (cl-defmethod eshell-output-object-to-target (object | 676 | (cl-defmethod eshell-output-object-to-target (object |
| 663 | (target eshell-virtual-target)) | 677 | (target eshell-function-target)) |
| 664 | "Output OBJECT to the virtual TARGET." | 678 | "Output OBJECT to the Eshell function TARGET." |
| 665 | (funcall (eshell-virtual-target-output-function target) object)) | 679 | (funcall (eshell-function-target-output-function target) object)) |
| 666 | 680 | ||
| 667 | (defun eshell-output-object (object &optional handle-index handles) | 681 | (defun eshell-output-object (object &optional handle-index handles) |
| 668 | "Insert OBJECT, using HANDLE-INDEX specifically. | 682 | "Insert OBJECT, using HANDLE-INDEX specifically. |
diff --git a/test/lisp/eshell/esh-io-tests.el b/test/lisp/eshell/esh-io-tests.el index 188570161c7..b4e8c0b4a9a 100644 --- a/test/lisp/eshell/esh-io-tests.el +++ b/test/lisp/eshell/esh-io-tests.el | |||
| @@ -381,4 +381,19 @@ stdout originally pointed (the terminal)." | |||
| 381 | (eshell-insert-command "echo three >> /dev/kill") | 381 | (eshell-insert-command "echo three >> /dev/kill") |
| 382 | (should (equal (car kill-ring) "twothree")))) | 382 | (should (equal (car kill-ring) "twothree")))) |
| 383 | 383 | ||
| 384 | (ert-deftest esh-io-test/virtual/device-close () | ||
| 385 | "Check that the close function for `eshell-function-target' works." | ||
| 386 | (let* ((data nil) | ||
| 387 | (status nil) | ||
| 388 | (eshell-virtual-targets | ||
| 389 | `(("/dev/virtual" | ||
| 390 | ,(eshell-function-target-create | ||
| 391 | (lambda (d) (setq data d)) | ||
| 392 | (lambda (s) (setq status s))) | ||
| 393 | nil)))) | ||
| 394 | (with-temp-eshell | ||
| 395 | (eshell-insert-command "echo hello > /dev/virtual") | ||
| 396 | (should (equal data "hello")) | ||
| 397 | (should (equal status t))))) | ||
| 398 | |||
| 384 | ;;; esh-io-tests.el ends here | 399 | ;;; esh-io-tests.el ends here |