]> git.eshelyaron.com Git - emacs.git/commitdiff
Fix detection of directly-invokable commands in Eshell
authorJim Porter <jporterbugs@gmail.com>
Fri, 26 Jan 2024 18:17:19 +0000 (10:17 -0800)
committerEshel Yaron <me@eshelyaron.com>
Fri, 26 Jan 2024 19:03:45 +0000 (20:03 +0100)
I think this regressed partly due to eef32d13da58, so let's add some
regression tests to make sure that doesn't happen again.

* lisp/eshell/em-unix.el (eshell-unix-initialize): Add "compile".

* lisp/eshell/esh-cmd.el (eshell--find-subcommands): Yield the second
element of the subcommand.
(eshell--invoke-command-directly-p): Rename and account for
'eshell-with-copied-handles'.
(eshell-invoke-directly): Rename to...
(eshell-invoke-directly-p): ... this, and use 'pcase' to make the
logic clearer.

* lisp/eshell/esh-mode.el (eshell-send-input): Always queue input if
the process is running; rename some locals to be clearer.

* lisp/eshell/esh-var.el (eshell-var-initialize): Add "env" as a
complex command.

* test/lisp/eshell/esh-cmd-tests.el
(esh-cmd-test--deftest-invoke-directly): New macro.
(no-args, with-args, multiple-cmds, subcmd, complex, complex-subcmd):
New test cases.

(cherry picked from commit 047607f6e611709f89f6c93ae0e2fc97b25bf18f)

lisp/eshell/em-unix.el
lisp/eshell/esh-cmd.el
lisp/eshell/esh-mode.el
lisp/eshell/esh-var.el
test/lisp/eshell/esh-cmd-tests.el

index b066e9eeb8eebd8c0dece96e86c21b56b68172c1..dad02206759bb35413aa09291c8f36ea3c680eac 100644 (file)
@@ -166,9 +166,9 @@ Otherwise, Emacs will attempt to use rsh to invoke du on the remote machine."
     (add-hook 'pcomplete-try-first-hook
              'eshell-complete-host-reference nil t))
   (setq-local eshell-complex-commands
-             (append '("grep" "egrep" "fgrep" "agrep" "rgrep"
-                        "glimpse" "locate" "cat" "time" "cp" "mv"
-                        "make" "du" "diff")
+             (append '("compile" "grep" "egrep" "fgrep" "agrep"
+                        "rgrep" "glimpse" "locate" "cat" "time" "cp"
+                        "mv" "make" "du" "diff")
                      eshell-complex-commands)))
 
 (defalias 'eshell/date     'current-time-string)
index 2746800ea78b06ba0c82bd38cab3cca08e370d16..30494bafb48d9ddc27beb65d3bc9fae4dc4b58b8 100644 (file)
@@ -934,48 +934,52 @@ This yields the SUBCOMMANDs when found in forms like
   (dolist (elem haystack)
     (cond
      ((eq (car-safe elem) 'eshell-as-subcommand)
-      (iter-yield (cdr elem)))
+      (iter-yield (cadr elem)))
      ((listp elem)
       (iter-yield-from (eshell--find-subcommands elem))))))
 
-(defun eshell--invoke-command-directly (command)
+(defun eshell--invoke-command-directly-p (command)
   "Determine whether the given COMMAND can be invoked directly.
 COMMAND should be a non-top-level Eshell command in parsed form.
 
 A command can be invoked directly if all of the following are true:
 
 * The command is of the form
-  \"(eshell-trap-errors (eshell-named-command NAME ARGS))\",
-  where ARGS is optional.
+  (eshell-with-copied-handles
+   (eshell-trap-errors (eshell-named-command NAME [ARGS])) _).
 
 * NAME is a string referring to an alias function and isn't a
   complex command (see `eshell-complex-commands').
 
 * Any subcommands in ARGS can also be invoked directly."
-  (when (and (eq (car command) 'eshell-trap-errors)
-             (eq (car (cadr command)) 'eshell-named-command))
-    (let ((name (cadr (cadr command)))
-          (args (cdr-safe (nth 2 (cadr command)))))
-      (and name (stringp name)
-          (not (member name eshell-complex-commands))
-          (catch 'simple
-            (dolist (pred eshell-complex-commands t)
-              (when (and (functionp pred)
-                         (funcall pred name))
-                (throw 'simple nil))))
-          (eshell-find-alias-function name)
-           (catch 'indirect-subcommand
-             (iter-do (subcommand (eshell--find-subcommands args))
-               (unless (eshell--invoke-command-directly subcommand)
-                 (throw 'indirect-subcommand nil)))
-             t)))))
-
-(defun eshell-invoke-directly (command)
+  (pcase command
+    (`(eshell-with-copied-handles
+       (eshell-trap-errors (eshell-named-command ,name . ,args))
+       ,_)
+     (and name (stringp name)
+         (not (member name eshell-complex-commands))
+         (catch 'simple
+           (dolist (pred eshell-complex-commands t)
+             (when (and (functionp pred)
+                        (funcall pred name))
+               (throw 'simple nil))))
+         (eshell-find-alias-function name)
+          (catch 'indirect-subcommand
+            (iter-do (subcommand (eshell--find-subcommands (car args)))
+              (unless (eshell--invoke-command-directly-p subcommand)
+                (throw 'indirect-subcommand nil)))
+            t)))))
+
+(defun eshell-invoke-directly-p (command)
   "Determine whether the given COMMAND can be invoked directly.
 COMMAND should be a top-level Eshell command in parsed form, as
 produced by `eshell-parse-command'."
-  (let ((base (cadr (nth 2 (nth 2 (cadr command))))))
-    (eshell--invoke-command-directly base)))
+  (pcase command
+    (`(eshell-commands (progn ,_ (unwind-protect (progn ,base) . ,_)))
+     (eshell--invoke-command-directly-p base))))
+
+(define-obsolete-function-alias 'eshell-invoke-directly
+  'eshell-invoke-directly-p "30.1")
 
 (defun eshell-eval-argument (argument)
   "Evaluate a single Eshell ARGUMENT and return the result."
index 21e3f00086f542c28761e77272d06f5fc400d8ec..fd279f61673f2f4f956ae33705f75f28d5ad6de5 100644 (file)
@@ -619,14 +619,14 @@ If NO-NEWLINE is non-nil, the input is sent without an implied final
 newline."
   (interactive "P")
   ;; Note that the input string does not include its terminal newline.
-  (let ((proc-running-p (and (eshell-head-process)
-                            (not queue-p)))
-       (inhibit-modification-hooks t))
-    (unless (and proc-running-p
+  (let* ((proc-running-p (eshell-head-process))
+         (send-to-process-p (and proc-running-p (not queue-p)))
+         (inhibit-modification-hooks t))
+    (unless (and send-to-process-p
                 (not (eq (process-status
                           (eshell-head-process))
                           'run)))
-      (if (or proc-running-p
+      (if (or send-to-process-p
              (>= (point) eshell-last-output-end))
          (goto-char (point-max))
        (let ((copy (eshell-get-old-input use-region)))
@@ -634,7 +634,7 @@ newline."
          (insert-and-inherit copy)))
       (unless (or no-newline
                  (and eshell-send-direct-to-subprocesses
-                      proc-running-p))
+                      send-to-process-p))
        (insert-before-markers-and-inherit ?\n))
       ;; Delete and reinsert input.  This seems like a no-op, except
       ;; for the resulting entries in the undo list: undoing this
@@ -644,7 +644,7 @@ newline."
             (inhibit-read-only t))
         (delete-region eshell-last-output-end (point))
         (insert text))
-      (if proc-running-p
+      (if send-to-process-p
          (progn
            (eshell-update-markers eshell-last-output-end)
            (if (or eshell-send-direct-to-subprocesses
@@ -673,7 +673,8 @@ newline."
                      (run-hooks 'eshell-input-filter-functions)
                      (and (catch 'eshell-terminal
                             (ignore
-                             (if (eshell-invoke-directly cmd)
+                             (if (and (not proc-running-p)
+                                       (eshell-invoke-directly-p cmd))
                                  (eval cmd)
                                (eshell-eval-command cmd input))))
                           (eshell-life-is-too-much)))))
index 627cbb17797543eac9bf836863a188c0e4e6f29a..537bc4b06410b80180b6f5d7dd5351bcf371d9f2 100644 (file)
@@ -287,6 +287,8 @@ This is set to t in `eshell-local-variable-bindings' (which see).")
   (setq-local eshell-subcommand-bindings
               (append eshell-local-variable-bindings
                       eshell-subcommand-bindings))
+  (setq-local eshell-complex-commands
+             (append '("env") eshell-complex-commands))
 
   (setq-local eshell-special-chars-inside-quoting
        (append eshell-special-chars-inside-quoting '(?$)))
index be31681267bac34a6f752f67baa9926387689b21..c37e6d14187ed1a58964f07254738f4f71a88ef5 100644 (file)
@@ -468,6 +468,26 @@ This tests when `eshell-lisp-form-nil-is-failure' is nil."
   (eshell-command-result-equal "unless {[ foo = bar ]} {echo no} {echo yes}"
                                "no"))
 
+\f
+;; Direct invocation
+
+(defmacro esh-cmd-test--deftest-invoke-directly (name command expected)
+  "FIXME"
+  (declare (indent 2))
+  `(ert-deftest ,(intern (concat "esh-cmd-test/invoke-directly/"
+                                 (symbol-name name))) ()
+     (with-temp-eshell
+      (should (equal (eshell-invoke-directly
+                      (eshell-parse-command ,command nil t))
+                     ,expected)))))
+
+(esh-cmd-test--deftest-invoke-directly no-args "echo" t)
+(esh-cmd-test--deftest-invoke-directly with-args "echo hi" t)
+(esh-cmd-test--deftest-invoke-directly multiple-cmds "echo hi; echo bye" nil)
+(esh-cmd-test--deftest-invoke-directly subcmd "echo ${echo hi}" t)
+(esh-cmd-test--deftest-invoke-directly complex "ls ." nil)
+(esh-cmd-test--deftest-invoke-directly complex-subcmd "echo {ls .}" nil)
+
 \f
 ;; Error handling