From afe81dc609802c9a3d3d64037d9d9df9a100cd0f Mon Sep 17 00:00:00 2001 From: Jim Porter Date: Tue, 20 Dec 2022 13:47:20 -0800 Subject: [PATCH 3/3] Simplify handling of /dev/null redirection in Eshell This also fixes an issue where "echo hi > foo > /dev/null" didn't write to the file "foo". * lisp/eshell/esh-io.el (eshell-virtual-targets): Add "/dev/null". (eshell-set-output-handle): Handle 'eshell-null-device'. (eshell-get-target): Map 'null-device' to "/dev/null". * test/lisp/eshell/esh-io-tests.el (esh-io-test/redirect-subcommands/dev-null) (esh-io-test/virtual/dev-null, esh-io-test/virtual/dev-null/multiple): New tests. --- lisp/eshell/esh-io.el | 56 +++++++++++++++++--------------- test/lisp/eshell/esh-io-tests.el | 33 +++++++++++++++++-- 2 files changed, 60 insertions(+), 29 deletions(-) diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el index 58084db28a8..a1dfef07458 100644 --- a/lisp/eshell/esh-io.el +++ b/lisp/eshell/esh-io.el @@ -116,16 +116,20 @@ eshell-print-queue-size :group 'eshell-io) (defcustom eshell-virtual-targets - '(("/dev/eshell" eshell-interactive-print nil) + '(;; This should be the literal string "/dev/null", not `null-device'. + ("/dev/null" (lambda (mode) (throw 'eshell-null-device t)) t) + ("/dev/eshell" eshell-interactive-print nil) ("/dev/kill" (lambda (mode) - (if (eq mode 'overwrite) - (kill-new "")) - 'eshell-kill-append) t) + (when (eq mode 'overwrite) + (kill-new "")) + #'eshell-kill-append) + t) ("/dev/clip" (lambda (mode) - (if (eq mode 'overwrite) - (let ((select-enable-clipboard t)) - (kill-new ""))) - 'eshell-clipboard-append) t)) + (when (eq mode 'overwrite) + (let ((select-enable-clipboard t)) + (kill-new ""))) + #'eshell-clipboard-append) + t)) "Map virtual devices name to Emacs Lisp functions. If the user specifies any of the filenames above as a redirection target, the function in the second element will be called. @@ -138,10 +142,7 @@ eshell-virtual-targets 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. - -NOTE: /dev/null is handled specially as a virtual target, and should -not be added to this variable." +is passed, meaning EOF." :type '(repeat (list (string :tag "Target") function @@ -357,21 +358,17 @@ eshell-set-output-handle "Set handle INDEX for the current HANDLES to point to TARGET using MODE. If HANDLES is nil, use `eshell-current-handles'." (when target - (let ((handles (or handles eshell-current-handles))) - (if (and (stringp target) - (string= target (null-device))) - (aset handles index nil) - (let* ((where (eshell-get-target target mode)) - (handle (or (aref handles index) - (aset handles index (list nil nil 1)))) - (current (car handle)) - (defaultp (cadr handle))) - (if (not defaultp) - (unless (member where current) - (setq current (append current (list where)))) - (setq current (list where))) - (setcar handle current) - (setcar (cdr handle) nil)))))) + (let* ((handles (or handles eshell-current-handles)) + (handle (or (aref handles index) + (aset handles index (list nil nil 1)))) + (defaultp (cadr handle)) + (current (unless defaultp (car handle)))) + (catch 'eshell-null-device + (let ((where (eshell-get-target target mode))) + (unless (member where current) + (setq current (append current (list where)))))) + (setcar handle current) + (setcar (cdr handle) nil)))) (defun eshell-copy-output-handle (index index-to-copy &optional handles) "Copy the handle INDEX-TO-COPY to INDEX for the current HANDLES. @@ -458,6 +455,11 @@ eshell-get-target (setq mode (or mode 'insert)) (cond ((stringp target) + ;; Always treat the `null-device' as the virtual target + ;; "/dev/null". This way, systems that call their null device + ;; something else can use either form. + (when (string= target (null-device)) + (setq target "/dev/null")) (let ((redir (assoc target eshell-virtual-targets))) (if redir (if (nth 2 redir) diff --git a/test/lisp/eshell/esh-io-tests.el b/test/lisp/eshell/esh-io-tests.el index ccf8ac1b9a1..9a3c14f365f 100644 --- a/test/lisp/eshell/esh-io-tests.el +++ b/test/lisp/eshell/esh-io-tests.el @@ -166,6 +166,17 @@ esh-io-test/redirect-subcommands/override (should (equal (buffer-string) "bar"))) (should (equal (buffer-string) "foobaz")))) +(ert-deftest esh-io-test/redirect-subcommands/dev-null () + "Check that redirecting subcommands applies to all subcommands. +Include a redirect to /dev/null to ensure it only applies to its +statement." + (eshell-with-temp-buffer bufname "old" + (with-temp-eshell + (eshell-insert-command + (format "{echo foo; echo bar > /dev/null; echo baz} > #<%s>" + bufname))) + (should (equal (buffer-string) "foobaz")))) + (ert-deftest esh-io-test/redirect-subcommands/interpolated () "Check that redirecting interpolated subcommands applies to all subcommands." (eshell-with-temp-buffer bufname "old" @@ -302,12 +313,30 @@ esh-io-test/redirect-pipe ;; Virtual targets -(ert-deftest esh-io-test/virtual-dev-eshell () +(ert-deftest esh-io-test/virtual/dev-null () + "Check that redirecting to /dev/null works." + (with-temp-eshell + (eshell-match-command-output "echo hi > /dev/null" "\\`\\'"))) + +(ert-deftest esh-io-test/virtual/dev-null/multiple () + "Check that redirecting to /dev/null works alongside other redirections." + (eshell-with-temp-buffer bufname "old" + (with-temp-eshell + (eshell-match-command-output + (format "echo new > /dev/null > #<%s>" bufname) "\\`\\'")) + (should (equal (buffer-string) "new"))) + (eshell-with-temp-buffer bufname "old" + (with-temp-eshell + (eshell-match-command-output + (format "echo new > #<%s> > /dev/null" bufname) "\\`\\'")) + (should (equal (buffer-string) "new")))) + +(ert-deftest esh-io-test/virtual/dev-eshell () "Check that redirecting to /dev/eshell works." (with-temp-eshell (eshell-match-command-output "echo hi > /dev/eshell" "hi"))) -(ert-deftest esh-io-test/virtual-dev-kill () +(ert-deftest esh-io-test/virtual/dev-kill () "Check that redirecting to /dev/kill works." (with-temp-eshell (eshell-insert-command "echo one > /dev/kill") -- 2.25.1