From: Jim Porter <jporterbugs@gmail.com>
To: rms@gnu.org
Cc: michael.albinus@gmx.de, 57556@debbugs.gnu.org
Subject: bug#57556: 28.1; Eshell not finding executables in PATH when tramp-integration loaded
Date: Sun, 16 Oct 2022 16:07:42 -0700 [thread overview]
Message-ID: <2b52eb68-b592-1cc9-2168-2d0dbdbfd114@gmail.com> (raw)
In-Reply-To: <E1okAbY-0002o2-Tr@fencepost.gnu.org>
[-- Attachment #1: Type: text/plain, Size: 970 bytes --]
On 10/16/2022 1:51 PM, Richard Stallman wrote:
> [[[ To any NSA and FBI agents reading my email: please consider ]]]
> [[[ whether defending the US Constitution against all enemies, ]]]
> [[[ foreign or domestic, requires you to follow Snowden's example. ]]]
>
> > My intent was to make that display as all-caps in the HTML documentation
> > as well. In that excerpt, 'NAME' should always be an environment
> > variable, so I used the capitalization conventions that env vars usually
> > use. 'name', on the other hand, could be a Lisp variable or an env var.
>
> It should be @var{name} to express that it is a metasyntactic variable
> that stands for something called ``name''.
Thanks. I've fixed my changes in the Eshell manual to use this
convention now, and also added some further cross-references.
I've attached (hopefully) the final version of these patches, which I'll
merge in the next day or so, unless someone finds any other issues.
[-- Attachment #2: 0001-Remove-over-quoting-of-application-values-in-connect.patch --]
[-- Type: text/plain, Size: 3206 bytes --]
From 046d15f14fcdf5816072c5dcb844f9dd4882051c Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Wed, 12 Oct 2022 11:28:05 -0700
Subject: [PATCH 1/7] ; Remove over-quoting of :application values in
connection-local variables
* test/lisp/files-x-tests.el (files-x-test--application)
(files-x-test--another-application):
* doc/lispref/variables.texi (Connection Local Variables): Remove
extra quotes.
---
doc/lispref/variables.texi | 14 +++++++-------
test/lisp/files-x-tests.el | 4 ++--
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/doc/lispref/variables.texi b/doc/lispref/variables.texi
index 1d891618da..2a06169b21 100644
--- a/doc/lispref/variables.texi
+++ b/doc/lispref/variables.texi
@@ -2311,13 +2311,13 @@ Connection Local Variables
@example
@group
(connection-local-set-profiles
- '(:application 'tramp :protocol "ssh" :machine "localhost")
+ '(:application tramp :protocol "ssh" :machine "localhost")
'remote-bash 'remote-null-device)
@end group
@group
(connection-local-set-profiles
- '(:application 'tramp :protocol "sudo"
+ '(:application tramp :protocol "sudo"
:user "root" :machine "localhost")
'remote-ksh 'remote-null-device)
@end group
@@ -2329,13 +2329,13 @@ Connection Local Variables
@example
@group
(connection-local-set-profiles
- '(:application 'tramp :protocol "ssh" :machine "localhost")
+ '(:application tramp :protocol "ssh" :machine "localhost")
'remote-bash)
@end group
@group
(connection-local-set-profiles
- '(:application 'tramp :protocol "sudo"
+ '(:application tramp :protocol "sudo"
:user "root" :machine "localhost")
'remote-ksh)
@end group
@@ -2365,7 +2365,7 @@ Connection Local Variables
@example
@group
(hack-connection-local-variables
- '(:application 'tramp :protocol "ssh" :machine "localhost"))
+ '(:application tramp :protocol "ssh" :machine "localhost"))
@end group
@group
@@ -2401,7 +2401,7 @@ Connection Local Variables
@group
(connection-local-set-profiles
- '(:application 'tramp :protocol "ssh" :machine "remotehost")
+ '(:application tramp :protocol "ssh" :machine "remotehost")
'remote-perl)
@end group
@@ -2429,7 +2429,7 @@ Connection Local Variables
@group
(connection-local-set-profiles
- '(:application 'my-app :protocol "ssh" :machine "remotehost")
+ '(:application my-app :protocol "ssh" :machine "remotehost")
'my-remote-perl)
@end group
diff --git a/test/lisp/files-x-tests.el b/test/lisp/files-x-tests.el
index 7ee2f0c1a6..2f6d0d4a99 100644
--- a/test/lisp/files-x-tests.el
+++ b/test/lisp/files-x-tests.el
@@ -42,9 +42,9 @@ remote-null-device
(put 'remote-shell-login-switch 'safe-local-variable #'identity)
(put 'remote-null-device 'safe-local-variable #'identity)
-(defconst files-x-test--application '(:application 'my-application))
+(defconst files-x-test--application '(:application my-application))
(defconst files-x-test--another-application
- '(:application 'another-application))
+ '(:application another-application))
(defconst files-x-test--protocol '(:protocol "my-protocol"))
(defconst files-x-test--user '(:user "my-user"))
(defconst files-x-test--machine '(:machine "my-machine"))
--
2.25.1
[-- Attachment #3: 0002-Add-helpers-to-dynamically-assign-connection-local-v.patch --]
[-- Type: text/plain, Size: 24536 bytes --]
From 28c5a764fe9b1042ee2af64f710a4c4fdedcae43 Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Tue, 11 Oct 2022 22:11:04 -0700
Subject: [PATCH 2/7] Add helpers to dynamically assign connection-local values
* lisp/files-x.el (connection-local-criteria)
(connection-local-profile-name-for-setq): New variables.
(with-connection-local-variables-1): ... let-bind them here.
(connection-local-update-profile-variables)
(connection-local-profile-name-for-criteria): New functions.
(with-connection-local-application-variables, setq-connection-local):
New macros.
* test/lisp/files-x-tests.el: Require 'tramp-integration'
(files-x-test--variable5, remote-lazy-var): New variables.
(files-x-test-hack-connection-local-variables-apply): Expand checks.
(files-x-test-with-connection-local-variables): Remove
'hack-connection-local-variables-apply' check (it belongs in the above
test), and expand some other checks.
(files-x-test--get-lazy-var, files-x-test--set-lazy-var): New
functions.
(files-x-test-connection-local-update-profile-variables)
(files-x-test-setq-connection-local): New tests.
* doc/lispref/variables.texi (Connection Local Variables): Split into
two subsections and document the new features.
* etc/NEWS: Announce 'setq-connection-local'.
---
doc/lispref/variables.texi | 98 ++++++++++++++++++------
etc/NEWS | 7 ++
lisp/files-x.el | 103 ++++++++++++++++++++++++--
test/lisp/files-x-tests.el | 148 +++++++++++++++++++++++++++----------
4 files changed, 288 insertions(+), 68 deletions(-)
diff --git a/doc/lispref/variables.texi b/doc/lispref/variables.texi
index 2a06169b21..cbe276b2dc 100644
--- a/doc/lispref/variables.texi
+++ b/doc/lispref/variables.texi
@@ -2239,9 +2239,26 @@ Connection Local Variables
@cindex connection local variables
Connection-local variables provide a general mechanism for different
-variable settings in buffers with a remote connection. They are bound
+variable settings in buffers with a remote connection (@pxref{Remote
+Files,, Remote Files, emacs, The GNU Emacs Manual}). They are bound
and set depending on the remote connection a buffer is dedicated to.
+@menu
+* Connection Local Profiles:: Storing variable settings to
+ apply to connections.
+* Applying Connection Local Variables:: Using connection-local values
+ in your code.
+@end menu
+
+@node Connection Local Profiles
+@subsection Connection Local Profiles
+@cindex connection local profiles
+
+ Emacs uses connection-local profiles to store the variable settings
+to apply to particular connections. You can then associate these with
+remote connections by defining the criteria when they should apply,
+using @code{connection-local-set-profiles}.
+
@defun connection-local-set-profile-variables profile variables
This function defines a set of variable settings for the connection
@var{profile}, which is a symbol. You can later assign the connection
@@ -2356,6 +2373,14 @@ Connection Local Variables
list.
@end deffn
+@node Applying Connection Local Variables
+@subsection Applying Connection Local Variables
+@cindex connection local variables, applying
+
+ When writing connection-aware code, you'll need to collect, and
+possibly apply, any connection-local variables. There are several
+ways to do this, as described below.
+
@defun hack-connection-local-variables criteria
This function collects applicable connection-local variables
associated with @var{criteria} in
@@ -2384,9 +2409,9 @@ Connection Local Variables
@var{criteria}, and immediately applies them in the current buffer.
@end defun
-@defmac with-connection-local-variables &rest body
-All connection-local variables, which are specified by
-@code{default-directory}, are applied.
+@defmac with-connection-local-application-variables application &rest body
+Apply all connection-local variables for @code{application}, which are
+specified by @code{default-directory}.
After that, @var{body} is executed, and the connection-local variables
are unwound. Example:
@@ -2394,20 +2419,20 @@ Connection Local Variables
@example
@group
(connection-local-set-profile-variables
- 'remote-perl
- '((perl-command-name . "/usr/local/bin/perl")
+ 'my-remote-perl
+ '((perl-command-name . "/usr/local/bin/perl5")
(perl-command-switch . "-e %s")))
@end group
@group
(connection-local-set-profiles
- '(:application tramp :protocol "ssh" :machine "remotehost")
- 'remote-perl)
+ '(:application my-app :protocol "ssh" :machine "remotehost")
+ 'my-remote-perl)
@end group
@group
(let ((default-directory "/ssh:remotehost:/working/dir/"))
- (with-connection-local-variables
+ (with-connection-local-application-variables 'my-app
do something useful))
@end group
@end example
@@ -2416,30 +2441,59 @@ Connection Local Variables
@defvar connection-local-default-application
The default application, a symbol, to be applied in
@code{with-connection-local-variables}. It defaults to @code{tramp},
-but in case you want to overwrite Tramp's settings temporarily, you
-could let-bind it like
+but you can let-bind it to change the application temporarily
+(@pxref{Local Variables}).
+
+This variable must not be changed globally.
+@end defvar
+
+@defmac with-connection-local-variables &rest body
+This is equivalent to
+@code{with-connection-local-application-variables}, but uses
+@code{connection-local-default-application} for the application.
+@end defmac
+
+@defmac setq-connection-local [symbol form]@dots{}
+This macro sets each @var{symbol} connection-locally to the result of
+evaluating the corresponding @var{form}, using the connection-local
+profile specified in @code{connection-local-profile-name-for-setq}; if
+the profile name is @code{nil}, this macro will just set the variables
+normally, as with @code{setq} (@pxref{Setting Variables}).
+
+For example, you can use this macro in combination with
+@code{with-connection-local-variables} or
+@code{with-connection-local-application-variables} to lazily
+initialize connection-local settings:
@example
@group
+(defvar my-app-variable nil)
+
(connection-local-set-profile-variables
- 'my-remote-perl
- '((perl-command-name . "/usr/local/bin/perl5")
- (perl-command-switch . "-e %s")))
-@end group
+ 'my-app-connection-default-profile
+ '((my-app-variable . nil)))
-@group
(connection-local-set-profiles
- '(:application my-app :protocol "ssh" :machine "remotehost")
- 'my-remote-perl)
+ '(:application my-app)
+ 'my-app-connection-default-profile)
@end group
@group
-(let ((default-directory "/ssh:remotehost:/working/dir/")
- (connection-local-default-application 'my-app))
- (with-connection-local-variables
- do something useful))
+(defun my-app-get-variable ()
+ (with-connection-local-application-variables 'my-app
+ (or my-app-variable
+ (setq-connection-local my-app-variable
+ do something useful))))
@end group
@end example
+@end defmac
+
+@defvar connection-local-profile-name-for-setq
+The connection-local profile name, a symbol, to use when setting
+variables via @code{setq-connection-local}. This is let-bound in the
+body of @code{with-connection-local-variables}, but you can also
+let-bind it yourself if you'd like to set variables on a different
+profile.
This variable must not be changed globally.
@end defvar
diff --git a/etc/NEWS b/etc/NEWS
index 9641587052..72b2331b81 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -3214,6 +3214,13 @@ TIMEOUT is the idle time after which to deactivate the transient map.
The default timeout value can be defined by the new variable
'set-transient-map-timeout'.
++++
+** New macro 'setq-connection-local'.
+This allows dynamically setting variable values for a particular
+connection within the body of 'with-connection-local-variables'. See
+the "(elisp) Connection Local Variables" node in the Lisp Reference
+manual for more information.
+
+++
** 'plist-get', 'plist-put' and 'plist-member' are no longer limited to 'eq'.
These function now take an optional comparison predicate argument.
diff --git a/lisp/files-x.el b/lisp/files-x.el
index f6d5d6cc27..1ae6586e70 100644
--- a/lisp/files-x.el
+++ b/lisp/files-x.el
@@ -618,6 +618,18 @@ connection-local-criteria-alist
:group 'tramp
:version "29.1")
+(defvar connection-local-criteria nil
+ "The current connection-local criteria, or nil.
+This is set while executing the body of
+`with-connection-local-variables'.")
+
+(defvar connection-local-profile-name-for-setq nil
+ "The current connection-local profile name, or nil.
+This is the name of the profile to use when setting variables via
+`setq-connection-local'. Its value is derived from
+`connection-local-criteria' and is set while executing the body
+of `with-connection-local-variables'.")
+
(defsubst connection-local-normalize-criteria (criteria)
"Normalize plist CRITERIA according to properties.
Return a reordered plist."
@@ -694,6 +706,23 @@ connection-local-set-profile-variables
(customize-set-variable
'connection-local-profile-alist connection-local-profile-alist))
+;;;###autoload
+(defun connection-local-update-profile-variables (profile variables)
+ "Update the variable settings for PROFILE in-place.
+VARIABLES is a list that declares connection-local variables for
+the connection profile. An element in VARIABLES is an alist
+whose elements are of the form (VAR . VALUE).
+
+Unlike `connection-local-set-profile-variables' (which see), this
+function preserves the values of any existing variable
+definitions that aren't listed in VARIABLES."
+ (when-let ((existing-variables
+ (nreverse (connection-local-get-profile-variables profile))))
+ (dolist (var variables)
+ (setf (alist-get (car var) existing-variables) (cdr var)))
+ (setq variables (nreverse existing-variables)))
+ (connection-local-set-profile-variables profile variables))
+
(defun hack-connection-local-variables (criteria)
"Read connection-local variables according to CRITERIA.
Store the connection-local variables in buffer local
@@ -736,6 +765,15 @@ connection-local-criteria-for-default-directory
:user ,(file-remote-p default-directory 'user)
:machine ,(file-remote-p default-directory 'host))))
+(defun connection-local-profile-name-for-criteria (criteria)
+ "Get a connection-local profile name based on CRITERIA."
+ (when criteria
+ (let (print-level print-length)
+ (intern (concat
+ "autogenerated-connection-local-profile/"
+ (prin1-to-string
+ (connection-local-normalize-criteria criteria)))))))
+
;;;###autoload
(defmacro with-connection-local-variables (&rest body)
"Apply connection-local variables according to `default-directory'.
@@ -743,16 +781,28 @@ with-connection-local-variables
(declare (debug t))
`(with-connection-local-variables-1 (lambda () ,@body)))
+;;;###autoload
+(defmacro with-connection-local-application-variables (application &rest body)
+ "Apply connection-local variables for APPLICATION in `default-directory'.
+Execute BODY, and unwind connection-local variables."
+ (declare (debug t) (indent 1))
+ `(let ((connection-local-default-application ,application))
+ (with-connection-local-variables-1 (lambda () ,@body))))
+
;;;###autoload
(defun with-connection-local-variables-1 (body-fun)
"Apply connection-local variables according to `default-directory'.
Call BODY-FUN with no args, and then unwind connection-local variables."
(if (file-remote-p default-directory)
- (let ((enable-connection-local-variables t)
- (old-buffer-local-variables (buffer-local-variables))
- connection-local-variables-alist)
- (hack-connection-local-variables-apply
- (connection-local-criteria-for-default-directory))
+ (let* ((enable-connection-local-variables t)
+ (connection-local-criteria
+ (connection-local-criteria-for-default-directory))
+ (connection-local-profile-name-for-setq
+ (connection-local-profile-name-for-criteria
+ connection-local-criteria))
+ (old-buffer-local-variables (buffer-local-variables))
+ connection-local-variables-alist)
+ (hack-connection-local-variables-apply connection-local-criteria)
(unwind-protect
(funcall body-fun)
;; Cleanup.
@@ -764,6 +814,49 @@ with-connection-local-variables-1
;; No connection-local variables to apply.
(funcall body-fun)))
+;;;###autoload
+(defmacro setq-connection-local (&rest pairs)
+ "Set each VARIABLE connection-locally to VALUE.
+
+When `connection-local-profile-name-for-setq' is set, assign each
+variable's value on that connection profile, and set that profile
+for `connection-local-criteria'. You can use this in combination
+with `with-connection-local-variables', as in
+
+ (with-connection-local-variables
+ (setq-connection-local VARIABLE VALUE))
+
+If there's no connection-local profile to use, just set the
+variables normally, as with `setq'.
+
+The variables are literal symbols and should not be quoted. The
+second VALUE is not computed until after the first VARIABLE is
+set, and so on; each VALUE can use the new value of variables set
+earlier in the `setq-connection-local'. The return value of the
+`setq-connection-local' form is the value of the last VALUE.
+
+\(fn [VARIABLE VALUE]...)"
+ (declare (debug setq))
+ (unless (zerop (mod (length pairs) 2))
+ (error "PAIRS must have an even number of variable/value members"))
+ (let ((set-expr nil)
+ (profile-vars nil))
+ (while pairs
+ (unless (symbolp (car pairs))
+ (error "Attempting to set a non-symbol: %s" (car pairs)))
+ (push `(set ',(car pairs) ,(cadr pairs)) set-expr)
+ (push `(cons ',(car pairs) ,(car pairs)) profile-vars)
+ (setq pairs (cddr pairs)))
+ `(prog1
+ ,(macroexp-progn (nreverse set-expr))
+ (when connection-local-profile-name-for-setq
+ (connection-local-update-profile-variables
+ connection-local-profile-name-for-setq
+ (list ,@(nreverse profile-vars)))
+ (connection-local-set-profiles
+ connection-local-criteria
+ connection-local-profile-name-for-setq)))))
+
;;;###autoload
(defun path-separator ()
"The connection-local value of `path-separator'."
diff --git a/test/lisp/files-x-tests.el b/test/lisp/files-x-tests.el
index 2f6d0d4a99..b1555a0266 100644
--- a/test/lisp/files-x-tests.el
+++ b/test/lisp/files-x-tests.el
@@ -23,6 +23,7 @@
(require 'ert)
(require 'files-x)
+(require 'tramp-integration)
(defconst files-x-test--variables1
'((remote-shell-file-name . "/bin/bash")
@@ -35,7 +36,11 @@ files-x-test--variables3
'((remote-null-device . "/dev/null")))
(defconst files-x-test--variables4
'((remote-null-device . "null")))
+(defconst files-x-test--variables5
+ '((remote-lazy-var . nil)
+ (remote-null-device . "/dev/null")))
(defvar remote-null-device)
+(defvar remote-lazy-var nil)
(put 'remote-shell-file-name 'safe-local-variable #'identity)
(put 'remote-shell-command-switch 'safe-local-variable #'identity)
(put 'remote-shell-interactive-switch 'safe-local-variable #'identity)
@@ -91,6 +96,28 @@ files-x-test-connection-local-set-profile-variables
(connection-local-get-profile-variables 'remote-nullfile)
files-x-test--variables4))))
+(ert-deftest files-x-test-connection-local-update-profile-variables ()
+ "Test updating connection-local profile variables."
+
+ ;; Declare (PROFILE VARIABLES) objects.
+ (let (connection-local-profile-alist connection-local-criteria-alist)
+ (connection-local-set-profile-variables
+ 'remote-bash (copy-alist files-x-test--variables1))
+ (should
+ (equal
+ (connection-local-get-profile-variables 'remote-bash)
+ files-x-test--variables1))
+
+ ;; Updating overwrites only the values specified in this call, but
+ ;; retains all the other values from previous calls.
+ (connection-local-update-profile-variables
+ 'remote-bash files-x-test--variables2)
+ (should
+ (equal
+ (connection-local-get-profile-variables 'remote-bash)
+ (cons (car files-x-test--variables2)
+ (cdr files-x-test--variables1))))))
+
(ert-deftest files-x-test-connection-local-set-profiles ()
"Test setting connection-local profiles."
@@ -233,9 +260,12 @@ files-x-test-hack-connection-local-variables-apply
(nreverse (copy-tree files-x-test--variables2)))))
;; The variables exist also as local variables.
(should (local-variable-p 'remote-shell-file-name))
+ (should (local-variable-p 'remote-null-device))
;; The proper variable value is set.
(should
- (string-equal (symbol-value 'remote-shell-file-name) "/bin/ksh"))))
+ (string-equal (symbol-value 'remote-shell-file-name) "/bin/ksh"))
+ (should
+ (string-equal (symbol-value 'remote-null-device) "/dev/null"))))
;; The third test case. Both criteria `files-x-test--criteria1'
;; and `files-x-test--criteria2' apply, but there are no double
@@ -274,13 +304,11 @@ files-x-test-hack-connection-local-variables-apply
(should-not (local-variable-p 'remote-shell-file-name))
(should-not (boundp 'remote-shell-file-name))))))
-(defvar tramp-connection-local-default-shell-variables)
-(defvar tramp-connection-local-default-system-variables)
-
(ert-deftest files-x-test-with-connection-local-variables ()
"Test setting connection-local variables."
- (let (connection-local-profile-alist connection-local-criteria-alist)
+ (let ((connection-local-profile-alist connection-local-profile-alist)
+ (connection-local-criteria-alist connection-local-criteria-alist))
(connection-local-set-profile-variables
'remote-bash files-x-test--variables1)
(connection-local-set-profile-variables
@@ -291,29 +319,6 @@ files-x-test-with-connection-local-variables
(connection-local-set-profiles
nil 'remote-ksh 'remote-nullfile)
- (with-temp-buffer
- (let ((enable-connection-local-variables t))
- (hack-connection-local-variables-apply nil)
-
- ;; All connection-local variables are set. They apply in
- ;; reverse order in `connection-local-variables-alist'.
- (should
- (equal connection-local-variables-alist
- (append
- (nreverse (copy-tree files-x-test--variables3))
- (nreverse (copy-tree files-x-test--variables2)))))
- ;; The variables exist also as local variables.
- (should (local-variable-p 'remote-shell-file-name))
- (should (local-variable-p 'remote-null-device))
- ;; The proper variable values are set.
- (should
- (string-equal (symbol-value 'remote-shell-file-name) "/bin/ksh"))
- (should
- (string-equal (symbol-value 'remote-null-device) "/dev/null"))
-
- ;; A candidate connection-local variable is not bound yet.
- (should-not (local-variable-p 'remote-shell-command-switch))))
-
(with-temp-buffer
;; Use the macro. We need a remote `default-directory'.
(let ((enable-connection-local-variables t)
@@ -331,18 +336,18 @@ files-x-test-with-connection-local-variables
(with-connection-local-variables
;; All connection-local variables are set. They apply in
;; reverse order in `connection-local-variables-alist'.
- ;; Since we ha a remote default directory, Tramp's settings
+ ;; Since we have a remote default directory, Tramp's settings
;; are appended as well.
(should
(equal
connection-local-variables-alist
(append
- (nreverse (copy-tree files-x-test--variables3))
- (nreverse (copy-tree files-x-test--variables2))
(nreverse
(copy-tree tramp-connection-local-default-shell-variables))
(nreverse
- (copy-tree tramp-connection-local-default-system-variables)))))
+ (copy-tree tramp-connection-local-default-system-variables))
+ (nreverse (copy-tree files-x-test--variables3))
+ (nreverse (copy-tree files-x-test--variables2)))))
;; The variables exist also as local variables.
(should (local-variable-p 'remote-shell-file-name))
(should (local-variable-p 'remote-null-device))
@@ -352,15 +357,21 @@ files-x-test-with-connection-local-variables
(should
(string-equal (symbol-value 'remote-null-device) "/dev/null"))
- ;; Run another instance of `with-connection-local-variables'
- ;; with a different application.
- (let ((connection-local-default-application (cadr files-x-test--application)))
- (with-connection-local-variables
- ;; The proper variable values are set.
- (should
- (string-equal (symbol-value 'remote-shell-file-name) "/bin/bash"))
- (should
- (string-equal (symbol-value 'remote-null-device) "/dev/null"))))
+ ;; Run `with-connection-local-application-variables' to use a
+ ;; different application.
+ (with-connection-local-application-variables
+ (cadr files-x-test--application)
+ (should
+ (equal
+ connection-local-variables-alist
+ (append
+ (nreverse (copy-tree files-x-test--variables3))
+ (nreverse (copy-tree files-x-test--variables1)))))
+ ;; The proper variable values are set.
+ (should
+ (string-equal (symbol-value 'remote-shell-file-name) "/bin/bash"))
+ (should
+ (string-equal (symbol-value 'remote-null-device) "/dev/null")))
;; The variable values are reset.
(should
(string-equal (symbol-value 'remote-shell-file-name) "/bin/ksh"))
@@ -376,5 +387,60 @@ files-x-test-with-connection-local-variables
(should-not (boundp 'remote-shell-file-name))
(should (string-equal (symbol-value 'remote-null-device) "null"))))))
+(defun files-x-test--get-lazy-var ()
+ "Get the connection-local value of `remote-lazy-var'.
+If it's not initialized yet, initialize it."
+ (with-connection-local-application-variables
+ (cadr files-x-test--application)
+ (or remote-lazy-var
+ (setq-connection-local remote-lazy-var
+ (or (file-remote-p default-directory 'host)
+ "local")))))
+
+(defun files-x-test--set-lazy-var (value)
+ "Set the connection-local value of `remote-lazy-var'"
+ (with-connection-local-application-variables
+ (cadr files-x-test--application)
+ (setq-connection-local remote-lazy-var value)))
+
+(ert-deftest files-x-test-setq-connection-local ()
+ "Test dynamically setting connection local variables."
+ (let (connection-local-profile-alist connection-local-criteria-alist)
+ (connection-local-set-profile-variables
+ 'remote-lazy files-x-test--variables5)
+ (connection-local-set-profiles
+ files-x-test--application
+ 'remote-lazy)
+
+ ;; Test the initial local value.
+ (should (equal (files-x-test--get-lazy-var) "local"))
+
+ ;; Set the local value and make sure it retains the value we set.
+ (should (equal (files-x-test--set-lazy-var "here") "here"))
+ (should (equal (files-x-test--get-lazy-var) "here"))
+
+ (let ((default-directory "/method:host:"))
+ ;; Test the initial remote value.
+ (should (equal (files-x-test--get-lazy-var) "host"))
+
+ ;; Set the remote value and make sure it retains the value we set.
+ (should (equal (files-x-test--set-lazy-var "there") "there"))
+ (should (equal (files-x-test--get-lazy-var) "there"))
+ ;; Set another connection-local variable.
+ (with-connection-local-application-variables
+ (cadr files-x-test--application)
+ (setq-connection-local remote-null-device "null")))
+
+ ;; Make sure we get the local value we set above.
+ (should (equal (files-x-test--get-lazy-var) "here"))
+ (should-not (boundp 'remote-null-device))
+
+ ;; Make sure we get the remote values we set above.
+ (let ((default-directory "/method:host:"))
+ (should (equal (files-x-test--get-lazy-var) "there"))
+ (with-connection-local-application-variables
+ (cadr files-x-test--application)
+ (should (equal remote-null-device "null"))))))
+
(provide 'files-x-tests)
;;; files-x-tests.el ends here
--
2.25.1
[-- Attachment #4: 0003-Allow-ignoring-errors-when-calling-eshell-match-comm.patch --]
[-- Type: text/plain, Size: 3827 bytes --]
From e0224cb5c9277b7059e546a3b93fb52ad7b183d2 Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Sat, 24 Sep 2022 18:13:03 -0700
Subject: [PATCH 3/7] ; Allow ignoring errors when calling
'eshell-match-command-output'
* test/lisp/eshell/eshell-tests-helpers.el
(eshell-match-command-output): New argument IGNORE-ERRORS.
* test/lisp/eshell/esh-var-tests.el
(esh-var-test/last-status-var-lisp-command)
(esh-var-test/last-status-var-lisp-form)
(esh-var-test/last-status-var-lisp-form-2): Ignore errors when calling
'eshell-match-command-output'.
---
test/lisp/eshell/esh-var-tests.el | 15 ++++++---------
test/lisp/eshell/eshell-tests-helpers.el | 13 ++++++++++---
2 files changed, 16 insertions(+), 12 deletions(-)
diff --git a/test/lisp/eshell/esh-var-tests.el b/test/lisp/eshell/esh-var-tests.el
index cb5b1766bb..ad695e45d7 100644
--- a/test/lisp/eshell/esh-var-tests.el
+++ b/test/lisp/eshell/esh-var-tests.el
@@ -472,9 +472,8 @@ esh-var-test/last-status-var-lisp-command
"t\n0\n")
(eshell-match-command-output "zerop 1; echo $?"
"0\n")
- (let ((debug-on-error nil))
- (eshell-match-command-output "zerop foo; echo $?"
- "1\n"))))
+ (eshell-match-command-output "zerop foo; echo $?"
+ "1\n" nil t)))
(ert-deftest esh-var-test/last-status-var-lisp-form ()
"Test using the \"last exit status\" ($?) variable with a Lisp form"
@@ -484,9 +483,8 @@ esh-var-test/last-status-var-lisp-form
"t\n0\n")
(eshell-match-command-output "(zerop 1); echo $?"
"2\n")
- (let ((debug-on-error nil))
- (eshell-match-command-output "(zerop \"foo\"); echo $?"
- "1\n")))))
+ (eshell-match-command-output "(zerop \"foo\"); echo $?"
+ "1\n" nil t))))
(ert-deftest esh-var-test/last-status-var-lisp-form-2 ()
"Test using the \"last exit status\" ($?) variable with a Lisp form.
@@ -497,9 +495,8 @@ esh-var-test/last-status-var-lisp-form-2
"0\n")
(eshell-match-command-output "(zerop 0); echo $?"
"0\n")
- (let ((debug-on-error nil))
- (eshell-match-command-output "(zerop \"foo\"); echo $?"
- "1\n")))))
+ (eshell-match-command-output "(zerop \"foo\"); echo $?"
+ "1\n" nil t))))
(ert-deftest esh-var-test/last-status-var-ext-cmd ()
"Test using the \"last exit status\" ($?) variable with an external command"
diff --git a/test/lisp/eshell/eshell-tests-helpers.el b/test/lisp/eshell/eshell-tests-helpers.el
index 73abfcbb55..e713e162ad 100644
--- a/test/lisp/eshell/eshell-tests-helpers.el
+++ b/test/lisp/eshell/eshell-tests-helpers.el
@@ -100,9 +100,16 @@ eshell-match-output--explainer
(put 'eshell-match-output 'ert-explainer #'eshell-match-output--explainer)
-(defun eshell-match-command-output (command regexp &optional func)
- "Insert a COMMAND at the end of the buffer and match the output with REGEXP."
- (eshell-insert-command command func)
+(defun eshell-match-command-output (command regexp &optional func
+ ignore-errors)
+ "Insert a COMMAND at the end of the buffer and match the output with REGEXP.
+FUNC is the function to call after inserting the text (see
+`eshell-insert-command').
+
+If IGNORE-ERRORS is non-nil, ignore any errors signaled when
+inserting the command."
+ (let ((debug-on-error (and (not ignore-errors) debug-on-error)))
+ (eshell-insert-command command func))
(eshell-wait-for-subprocess)
(should (eshell-match-output regexp)))
--
2.25.1
[-- Attachment #5: 0004-Obsolete-eshell-define.patch --]
[-- Type: text/plain, Size: 1674 bytes --]
From fbf9191ad694c9995a3985175d8b277392400757 Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Wed, 28 Sep 2022 09:34:38 -0700
Subject: [PATCH 4/7] ; Obsolete 'eshell/define'
* lisp/eshell/esh-var.el (eshell/define): Make obsolete, and explain
its current state.
* doc/misc/eshell.texi (Built-ins): Remove 'define'.
---
doc/misc/eshell.texi | 5 -----
lisp/eshell/esh-var.el | 5 +++++
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi
index 0ee33f2c2a..8036bbd83a 100644
--- a/doc/misc/eshell.texi
+++ b/doc/misc/eshell.texi
@@ -439,11 +439,6 @@ Built-ins
is similar to, but slightly different from, the GNU Coreutils
@command{date} command.
-@item define
-@cmindex define
-Define a variable alias.
-@xref{Variable Aliases, , , elisp, The Emacs Lisp Reference Manual}.
-
@item diff
@cmindex diff
Compare files using Emacs's internal @code{diff} (not to be confused
diff --git a/lisp/eshell/esh-var.el b/lisp/eshell/esh-var.el
index 36e59cd5a4..3c09fc52fb 100644
--- a/lisp/eshell/esh-var.el
+++ b/lisp/eshell/esh-var.el
@@ -302,6 +302,11 @@ eshell-interpolate-variable
(defun eshell/define (var-alias definition)
"Define a VAR-ALIAS using DEFINITION."
+ ;; FIXME: This function doesn't work (it produces variable aliases
+ ;; in a form not recognized by other parts of the code), and likely
+ ;; hasn't worked since before its introduction into Emacs. It
+ ;; should either be removed or fixed up.
+ (declare (obsolete nil "29.1"))
(if (not definition)
(setq eshell-variable-aliases-list
(delq (assoc var-alias eshell-variable-aliases-list)
--
2.25.1
[-- Attachment #6: 0005-Allow-setting-the-values-of-variable-aliases-in-Eshe.patch --]
[-- Type: text/plain, Size: 22178 bytes --]
From f535cc568bac5d8a90b030547aa1761f6378db9f Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Sun, 25 Sep 2022 21:47:26 -0700
Subject: [PATCH 5/7] Allow setting the values of variable aliases in Eshell
This makes commands like "COLUMNS=40 some-command" work as expected.
* lisp/eshell/esh-cmd.el (eshell-subcommand-bindings): Remove
'process-environment' from here...
* lisp/eshell/esh-var.el (eshell-var-initialize): ... and add to here,
along with 'eshell-variable-aliases-list'.
(eshell-inside-emacs): Convert to a 'defvar-local' to make it settable
in a particular Eshell buffer.
(eshell-variable-aliases-list): Make $?, $$, and $* read-only and
update docstring.
(eshell-set-variable): New function...
(eshell-handle-local-variables, eshell/export, eshell/unset): ... use
it.
(eshell/set, pcomplete/eshell-mode/set): New functions.
(eshell-get-variable): Get the variable alias's getter function when
appropriate and use a safer method for checking function arity.
* test/lisp/eshell/esh-var-tests.el (esh-var-test/set/env-var)
(esh-var-test/set/symbol, esh-var-test/unset/env-var)
(esh-var-test/unset/symbol, esh-var-test/setq, esh-var-test/export)
(esh-var-test/local-variables, esh-var-test/alias/function)
(esh-var-test/alias/function-pair, esh-var-test/alias/string)
(esh-var-test/alias/string/prefer-lisp, esh-var-test/alias/symbol)
(esh-var-test/alias/symbol-pair, esh-var-test/alias/export)
(esh-var-test/alias/local-variables): New tests.
* doc/misc/eshell.texi (Built-ins): Add 'set' and update 'unset'
documentation.
(Variables): Expand documentation of how to get/set variables.
---
doc/misc/eshell.texi | 49 ++++++++--
lisp/eshell/esh-cmd.el | 4 +-
lisp/eshell/esh-var.el | 141 +++++++++++++++++++++--------
test/lisp/eshell/esh-var-tests.el | 145 ++++++++++++++++++++++++++++++
4 files changed, 293 insertions(+), 46 deletions(-)
diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi
index 8036bbd83a..21c1671a21 100644
--- a/doc/misc/eshell.texi
+++ b/doc/misc/eshell.texi
@@ -694,10 +694,18 @@ Built-ins
This command can be loaded as part of the eshell-xtra module, which is
disabled by default.
+@item set
+@cmindex set
+Set variable values, using the function @code{set} like a command
+(@pxref{Setting Variables,,, elisp, GNU Emacs Lisp Reference Manual}).
+A variable name can be a symbol, in which case it refers to a Lisp
+variable, or a string, referring to an environment variable
+(@pxref{Arguments}).
+
@item setq
@cmindex setq
-Set variable values, using the function @code{setq} like a command.
-@xref{Setting Variables,,, elisp, GNU Emacs Lisp Reference Manual}.
+Set variable values, using the function @code{setq} like a command
+(@pxref{Setting Variables,,, elisp, GNU Emacs Lisp Reference Manual}).
@item source
@cmindex source
@@ -743,7 +751,9 @@ Built-ins
@item unset
@cmindex unset
-Unset an environment variable.
+Unset one or more variables. As with @command{set}, a variable name
+can be a symbol, in which case it refers to a Lisp variable, or a
+string, referring to an environment variable.
@item wait
@cmindex wait
@@ -881,12 +891,35 @@ Built-ins
@node Variables
@section Variables
-Since Eshell is just an Emacs @acronym{REPL}@footnote{
+@vindex eshell-prefer-lisp-variables
+Since Eshell is a combination of an Emacs @acronym{REPL}@footnote{
Short for ``Read-Eval-Print Loop''.
-}
-, it does not have its own scope, and simply stores variables the same
-you would in an Elisp program. Eshell provides a command version of
-@code{setq} for convenience.
+} and a command shell, it can refer to variables from two different
+sources: ordinary Emacs Lisp variables, as well as environment
+variables. By default, when using a variable in Eshell, it will first
+look in the list of built-in variables, then in the list of
+environment variables, and finally in the list of Lisp variables. If
+you would prefer to use Lisp variables over environment variables, you
+can set @code{eshell-prefer-lisp-variables} to @code{t}.
+
+You can set variables in a few different ways. To set a Lisp
+variable, you can use the command @samp{setq @var{name} @var{value}},
+which works much like its Lisp counterpart (@pxref{Setting Variables,
+, , elisp, The Emacs Lisp Reference Manual}). To set an environment
+variable, use @samp{export @var{name}=@var{value}}. You can also use
+@samp{set @var{variable} @var{value}}, which sets a Lisp variable if
+@var{variable} is a symbol, or an environment variable if it's a
+string (@pxref{Arguments}). Finally, you can temporarily set
+environment variables for a single command with
+@samp{@var{name}=@var{value} @var{command} @dots{}}. This is
+equivalent to:
+
+@example
+@{
+ export @var{name}=@var{value}
+ @var{command} @dots{}
+@}
+@end example
@subsection Built-in variables
Eshell knows a few built-in variables:
diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el
index 3f3a1616ee..c5ceb3ffd1 100644
--- a/lisp/eshell/esh-cmd.el
+++ b/lisp/eshell/esh-cmd.el
@@ -261,9 +261,9 @@ eshell-deferrable-commands
(defcustom eshell-subcommand-bindings
'((eshell-in-subcommand-p t)
(eshell-in-pipeline-p nil)
- (default-directory default-directory)
- (process-environment (eshell-copy-environment)))
+ (default-directory default-directory))
"A list of `let' bindings for subcommand environments."
+ :version "29.1" ; removed `process-environment'
:type 'sexp
:risky t)
diff --git a/lisp/eshell/esh-var.el b/lisp/eshell/esh-var.el
index 3c09fc52fb..caf143e1a1 100644
--- a/lisp/eshell/esh-var.el
+++ b/lisp/eshell/esh-var.el
@@ -113,7 +113,7 @@
(require 'pcomplete)
(require 'ring)
-(defconst eshell-inside-emacs (format "%s,eshell" emacs-version)
+(defvar-local eshell-inside-emacs (format "%s,eshell" emacs-version)
"Value for the `INSIDE_EMACS' environment variable.")
(defgroup eshell-var nil
@@ -162,8 +162,8 @@ eshell-variable-aliases-list
(car (last eshell-last-arguments))
(eshell-apply-indices eshell-last-arguments
indices quoted))))
- ("?" eshell-last-command-status)
- ("$" eshell-last-command-result)
+ ("?" (eshell-last-command-status . nil))
+ ("$" (eshell-last-command-result . nil))
;; for em-alias.el and em-script.el
("0" eshell-command-name)
@@ -176,7 +176,7 @@ eshell-variable-aliases-list
("7" ,(lambda () (nth 6 eshell-command-arguments)) nil t)
("8" ,(lambda () (nth 7 eshell-command-arguments)) nil t)
("9" ,(lambda () (nth 8 eshell-command-arguments)) nil t)
- ("*" eshell-command-arguments))
+ ("*" (eshell-command-arguments . nil)))
"This list provides aliasing for variable references.
Each member is of the following form:
@@ -186,6 +186,11 @@ eshell-variable-aliases-list
compute the string value that will be returned when the variable is
accessed via the syntax `$NAME'.
+If VALUE is a cons (GET . SET), then variable references to NAME
+will use GET to get the value, and SET to set it. GET and SET
+can be one of the forms described below. If SET is nil, the
+variable is read-only.
+
If VALUE is a function, its behavior depends on the value of
SIMPLE-FUNCTION. If SIMPLE-FUNCTION is nil, call VALUE with two
arguments: the list of the indices that were used in the reference,
@@ -193,23 +198,30 @@ eshell-variable-aliases-list
quoted with double quotes. For example, if `NAME' were aliased
to a function, a reference of `$NAME[10][20]' would result in that
function being called with the arguments `((\"10\") (\"20\"))' and
-nil.
-If SIMPLE-FUNCTION is non-nil, call the function with no arguments
-and then pass its return value to `eshell-apply-indices'.
+nil. If SIMPLE-FUNCTION is non-nil, call the function with no
+arguments and then pass its return value to `eshell-apply-indices'.
+
+When VALUE is a function, it's read-only by default. To make it
+writeable, use the (GET . SET) form described above. If SET is a
+function, it takes two arguments: a list of indices (currently
+always nil, but reserved for future enhancement), and the new
+value to set.
-If VALUE is a string, return the value for the variable with that
-name in the current environment. If no variable with that name exists
-in the environment, but if a symbol with that same name exists and has
-a value bound to it, return that symbol's value instead. You can
-prefer symbol values over environment values by setting the value
-of `eshell-prefer-lisp-variables' to t.
+If VALUE is a string, get/set the value for the variable with
+that name in the current environment. When getting the value, if
+no variable with that name exists in the environment, but if a
+symbol with that same name exists and has a value bound to it,
+return that symbol's value instead. You can prefer symbol values
+over environment values by setting the value of
+`eshell-prefer-lisp-variables' to t.
-If VALUE is a symbol, return the value bound to it.
+If VALUE is a symbol, get/set the value bound to it.
If VALUE has any other type, signal an error.
Additionally, if COPY-TO-ENVIRONMENT is non-nil, the alias should be
copied (a.k.a. \"exported\") to the environment of created subprocesses."
+ :version "29.1"
:type '(repeat (list string sexp
(choice (const :tag "Copy to environment" t)
(const :tag "Use only in Eshell" nil))
@@ -234,6 +246,11 @@ eshell-var-initialize
;; changing a variable will affect all of Emacs.
(unless eshell-modify-global-environment
(setq-local process-environment (eshell-copy-environment)))
+ (setq-local eshell-subcommand-bindings
+ (append
+ '((process-environment (eshell-copy-environment))
+ (eshell-variable-aliases-list eshell-variable-aliases-list))
+ eshell-subcommand-bindings))
(setq-local eshell-special-chars-inside-quoting
(append eshell-special-chars-inside-quoting '(?$)))
@@ -282,9 +299,9 @@ eshell-handle-local-variables
(while (string-match setvar command)
(nconc
l (list
- (list 'setenv (match-string 1 command)
- (match-string 2 command)
- (= (length (match-string 2 command)) 0))))
+ (list 'eshell-set-variable
+ (match-string 1 command)
+ (match-string 2 command))))
(setq command (eshell-stringify (car args))
args (cdr args)))
(cdr l))
@@ -328,12 +345,11 @@ eshell/define
(defun eshell/export (&rest sets)
"This alias allows the `export' command to act as bash users expect."
- (while sets
- (if (and (stringp (car sets))
- (string-match "^\\([^=]+\\)=\\(.*\\)" (car sets)))
- (setenv (match-string 1 (car sets))
- (match-string 2 (car sets))))
- (setq sets (cdr sets))))
+ (dolist (set sets)
+ (when (and (stringp set)
+ (string-match "^\\([^=]+\\)=\\(.*\\)" set))
+ (eshell-set-variable (match-string 1 set)
+ (match-string 2 set)))))
(defun pcomplete/eshell-mode/export ()
"Completion function for Eshell's `export'."
@@ -343,16 +359,28 @@ pcomplete/eshell-mode/export
(eshell-envvar-names)))))
(defun eshell/unset (&rest args)
- "Unset an environment variable."
- (while args
- (if (stringp (car args))
- (setenv (car args) nil t))
- (setq args (cdr args))))
+ "Unset one or more variables.
+This is equivalent to calling `eshell/set' for all of ARGS with
+the values of nil for each."
+ (dolist (arg args)
+ (eshell-set-variable arg nil)))
(defun pcomplete/eshell-mode/unset ()
"Completion function for Eshell's `unset'."
(while (pcomplete-here (eshell-envvar-names))))
+(defun eshell/set (&rest args)
+ "Allow command-ish use of `set'."
+ (let (last-value)
+ (while args
+ (setq last-value (eshell-set-variable (car args) (cadr args))
+ args (cddr args)))
+ last-value))
+
+(defun pcomplete/eshell-mode/set ()
+ "Completion function for Eshell's `set'."
+ (while (pcomplete-here (eshell-envvar-names))))
+
(defun eshell/setq (&rest args)
"Allow command-ish use of `setq'."
(let (last-value)
@@ -566,18 +594,21 @@ eshell-get-variable
If QUOTED is non-nil, this was invoked inside double-quotes."
(if-let ((alias (assoc name eshell-variable-aliases-list)))
(let ((target (nth 1 alias)))
+ (when (and (not (functionp target))
+ (consp target))
+ (setq target (car target)))
(cond
((functionp target)
(if (nth 3 alias)
(eshell-apply-indices (funcall target) indices quoted)
- (condition-case nil
- (funcall target indices quoted)
- (wrong-number-of-arguments
- (display-warning
- :warning (concat "Function for `eshell-variable-aliases-list' "
- "entry should accept two arguments: INDICES "
- "and QUOTED.'"))
- (funcall target indices)))))
+ (let ((max-arity (cdr (func-arity target))))
+ (if (or (eq max-arity 'many) (>= max-arity 2))
+ (funcall target indices quoted)
+ (display-warning
+ :warning (concat "Function for `eshell-variable-aliases-list' "
+ "entry should accept two arguments: INDICES "
+ "and QUOTED.'"))
+ (funcall target indices)))))
((symbolp target)
(eshell-apply-indices (symbol-value target) indices quoted))
(t
@@ -594,6 +625,44 @@ eshell-get-variable
(getenv name)))
indices quoted)))
+(defun eshell-set-variable (name value)
+ "Set the variable named NAME to VALUE.
+NAME can be a string (in which case it refers to an environment
+variable or variable alias) or a symbol (in which case it refers
+to a Lisp variable)."
+ (if-let ((alias (assoc name eshell-variable-aliases-list)))
+ (let ((target (nth 1 alias)))
+ (cond
+ ((functionp target)
+ (setq target nil))
+ ((consp target)
+ (setq target (cdr target))))
+ (cond
+ ((functionp target)
+ (funcall target nil value))
+ ((null target)
+ (unless eshell-in-subcommand-p
+ (error "Variable `%s' is not settable" (eshell-stringify name)))
+ (push `(,name ,(lambda () value) t t)
+ eshell-variable-aliases-list)
+ value)
+ ;; Since getting a variable alias with a string target and
+ ;; `eshell-prefer-lisp-variables' non-nil gets the
+ ;; corresponding Lisp variable, make sure setting does the
+ ;; same.
+ ((and eshell-prefer-lisp-variables
+ (stringp target))
+ (eshell-set-variable (intern target) value))
+ (t
+ (eshell-set-variable target value))))
+ (cond
+ ((stringp name)
+ (setenv name value))
+ ((symbolp name)
+ (set name value))
+ (t
+ (error "Unknown variable `%s'" (eshell-stringify name))))))
+
(defun eshell-apply-indices (value indices &optional quoted)
"Apply to VALUE all of the given INDICES, returning the sub-result.
The format of INDICES is:
diff --git a/test/lisp/eshell/esh-var-tests.el b/test/lisp/eshell/esh-var-tests.el
index ad695e45d7..a7ac52ed24 100644
--- a/test/lisp/eshell/esh-var-tests.el
+++ b/test/lisp/eshell/esh-var-tests.el
@@ -25,6 +25,7 @@
(require 'ert)
(require 'esh-mode)
+(require 'esh-var)
(require 'eshell)
(require 'eshell-tests-helpers
@@ -439,6 +440,150 @@ esh-var-test/quoted-interp-convert-cmd-split-indices
(eshell-command-result-equal "echo \"${echo \\\"000 010 020\\\"}[0]\""
"000"))
+\f
+;; Variable-related commands
+
+(ert-deftest esh-var-test/set/env-var ()
+ "Test that `set' with a string variable name sets an environment variable."
+ (with-temp-eshell
+ (eshell-match-command-output "set VAR hello" "hello\n")
+ (should (equal (getenv "VAR") "hello")))
+ (should-not (equal (getenv "VAR") "hello")))
+
+(ert-deftest esh-var-test/set/symbol ()
+ "Test that `set' with a symbol variable name sets a Lisp variable."
+ (let (eshell-test-value)
+ (eshell-command-result-equal "set #'eshell-test-value hello"
+ "hello")
+ (should (equal eshell-test-value "hello"))))
+
+(ert-deftest esh-var-test/unset/env-var ()
+ "Test that `unset' with a string variable name unsets an env var."
+ (let ((process-environment (cons "VAR=value" process-environment)))
+ (with-temp-eshell
+ (eshell-match-command-output "unset VAR" "\\`\\'")
+ (should (equal (getenv "VAR") nil)))
+ (should (equal (getenv "VAR") "value"))))
+
+(ert-deftest esh-var-test/unset/symbol ()
+ "Test that `unset' with a symbol variable name unsets a Lisp variable."
+ (let ((eshell-test-value "value"))
+ (eshell-command-result-equal "unset #'eshell-test-value" nil)
+ (should (equal eshell-test-value nil))))
+
+(ert-deftest esh-var-test/setq ()
+ "Test that `setq' sets Lisp variables."
+ (let (eshell-test-value)
+ (eshell-command-result-equal "setq eshell-test-value hello"
+ "hello")
+ (should (equal eshell-test-value "hello"))))
+
+(ert-deftest esh-var-test/export ()
+ "Test that `export' sets environment variables."
+ (with-temp-eshell
+ (eshell-match-command-output "export VAR=hello" "\\`\\'")
+ (should (equal (getenv "VAR") "hello"))))
+
+(ert-deftest esh-var-test/local-variables ()
+ "Test that \"VAR=value command\" temporarily sets variables."
+ (with-temp-eshell
+ (push "VAR=value" process-environment)
+ (eshell-match-command-output "VAR=hello env" "VAR=hello\n")
+ (should (equal (getenv "VAR") "value"))))
+
+\f
+;; Variable aliases
+
+(ert-deftest esh-var-test/alias/function ()
+ "Test using a variable alias defined as a function."
+ (with-temp-eshell
+ (push `("ALIAS" ,(lambda () "value") nil t) eshell-variable-aliases-list)
+ (eshell-match-command-output "echo $ALIAS" "value\n")
+ (eshell-match-command-output "set ALIAS hello"
+ "Variable `ALIAS' is not settable\n"
+ nil t)))
+
+(ert-deftest esh-var-test/alias/function-pair ()
+ "Test using a variable alias defined as a pair of getter/setter functions."
+ (with-temp-eshell
+ (let ((eshell-test-value "value"))
+ (push `("ALIAS" (,(lambda () eshell-test-value)
+ . (lambda (_ value)
+ (setq eshell-test-value (upcase value))))
+ nil t)
+ eshell-variable-aliases-list)
+ (eshell-match-command-output "echo $ALIAS" "value\n")
+ (eshell-match-command-output "set ALIAS hello" "HELLO\n")
+ (should (equal eshell-test-value "HELLO")))))
+
+(ert-deftest esh-var-test/alias/string ()
+ "Test using a variable alias defined as a string.
+This should get/set the aliased environment variable."
+ (with-temp-eshell
+ (let ((eshell-test-value "lisp-value"))
+ (push "eshell-test-value=env-value" process-environment)
+ (push `("ALIAS" "eshell-test-value") eshell-variable-aliases-list)
+ (eshell-match-command-output "echo $ALIAS" "env-value\n")
+ (eshell-match-command-output "set ALIAS hello" "hello\n")
+ (should (equal (getenv "eshell-test-value") "hello"))
+ (should (equal eshell-test-value "lisp-value")))))
+
+(ert-deftest esh-var-test/alias/string/prefer-lisp ()
+ "Test using a variable alias defined as a string.
+This sets `eshell-prefer-lisp-variables' to t and should get/set
+the aliased Lisp variable."
+ (with-temp-eshell
+ (let ((eshell-test-value "lisp-value")
+ (eshell-prefer-lisp-variables t))
+ (push "eshell-test-value=env-value" process-environment)
+ (push `("ALIAS" "eshell-test-value") eshell-variable-aliases-list)
+ (eshell-match-command-output "echo $ALIAS" "lisp-value\n")
+ (eshell-match-command-output "set ALIAS hello" "hello\n")
+ (should (equal (car process-environment) "eshell-test-value=env-value"))
+ (should (equal eshell-test-value "hello")))))
+
+(ert-deftest esh-var-test/alias/symbol ()
+ "Test using a variable alias defined as a symbol.
+This should get/set the value bound to the symbol."
+ (with-temp-eshell
+ (let ((eshell-test-value "value"))
+ (push '("ALIAS" eshell-test-value) eshell-variable-aliases-list)
+ (eshell-match-command-output "echo $ALIAS" "value\n")
+ (eshell-match-command-output "set ALIAS hello" "hello\n")
+ (should (equal eshell-test-value "hello")))))
+
+(ert-deftest esh-var-test/alias/symbol-pair ()
+ "Test using a variable alias defined as a pair of symbols.
+This should get the value bound to the symbol, but fail to set
+it, since the setter is nil."
+ (with-temp-eshell
+ (let ((eshell-test-value "value"))
+ (push '("ALIAS" (eshell-test-value . nil)) eshell-variable-aliases-list)
+ (eshell-match-command-output "echo $ALIAS" "value\n")
+ (eshell-match-command-output "set ALIAS hello"
+ "Variable `ALIAS' is not settable\n"
+ nil t))))
+
+(ert-deftest esh-var-test/alias/export ()
+ "Test that `export' properly sets variable aliases."
+ (with-temp-eshell
+ (let ((eshell-test-value "value"))
+ (push `("ALIAS" (,(lambda () eshell-test-value)
+ . (lambda (_ value) (setq eshell-test-value value)))
+ nil t)
+ eshell-variable-aliases-list)
+ (eshell-match-command-output "export ALIAS=hello" "\\`\\'")
+ (should (equal eshell-test-value "hello")))))
+
+(ert-deftest esh-var-test/alias/local-variables ()
+ "Test that \"VAR=value cmd\" temporarily sets read-only variable aliases."
+ (with-temp-eshell
+ (let ((eshell-test-value "value"))
+ (push `("ALIAS" ,(lambda () eshell-test-value) t t)
+ eshell-variable-aliases-list)
+ (eshell-match-command-output "ALIAS=hello env" "ALIAS=hello\n")
+ (should (equal eshell-test-value "value")))))
+
\f
;; Built-in variables
--
2.25.1
[-- Attachment #7: 0006-Improve-handling-of-PATH-in-Eshell-for-remote-direct.patch --]
[-- Type: text/plain, Size: 19675 bytes --]
From 5b915a21708332d1b25bd78d3742ae90d93473b2 Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Thu, 15 Sep 2022 12:24:37 -0700
Subject: [PATCH 6/7] Improve handling of $PATH in Eshell for remote
directories
* lisp/eshell/esh-util.el (eshell-path-env, eshell-parse-colon-path):
Make obsolete.
(eshell-path-env-list): New variable.
(eshell-connection-default-profile): New connection-local profile.
(eshell-get-path): Reimplement using 'eshell-path-env-list'.
(eshell-set-path): New function.
* lisp/eshell/esh-var.el (eshell-variable-aliases-list): Add entry for
$PATH.
(eshell-var-initialize): Add 'eshell-path-env-list' to
'eshell-subcommand-bindings'.
* lisp/eshell/esh-ext.el (eshell-search-path): Use 'file-name-concat'
instead of 'concat'.
(eshell/addpath): Use 'eshell-get-path' and 'eshell-set-path'.
* lisp/net/tramp-integration.el: Only apply Eshell hooks when
'eshell-path-env-list' is unbound.
* test/lisp/eshell/esh-var-tests.el
(esh-var-test/path-var/local-directory)
(esh-var-test/path-var/remote-directory, esh-var-test/path-var/set)
(esh-var-test/path-var/set-locally)
(esh-var-test/path-var-preserve-across-hosts): New tests.
* test/lisp/eshell/esh-ext-tests.el: New file.
* test/lisp/eshell/eshell-tests-helpers.el
(with-temp-eshell): Set 'eshell-last-dir-ring-file-name' to nil.
(eshell-tests-remote-accessible-p, eshell-last-input)
(eshell-last-output): New functions.
(eshell-match-output, eshell-match-output--explainer): Use
'eshell-last-input' and 'eshell-last-output'.
* doc/misc/eshell.texi (Variables): Document $PATH.
* etc/NEWS: Announce this change (bug#57556).
---
doc/misc/eshell.texi | 10 ++++
etc/NEWS | 5 ++
lisp/eshell/esh-ext.el | 23 ++++---
lisp/eshell/esh-util.el | 53 +++++++++++++++--
lisp/eshell/esh-var.el | 12 +++-
lisp/net/tramp-integration.el | 21 +++----
test/lisp/eshell/esh-ext-tests.el | 76 ++++++++++++++++++++++++
test/lisp/eshell/esh-var-tests.el | 60 +++++++++++++++++++
test/lisp/eshell/eshell-tests-helpers.el | 32 +++++++---
9 files changed, 255 insertions(+), 37 deletions(-)
create mode 100644 test/lisp/eshell/esh-ext-tests.el
diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi
index 21c1671a21..d518eafd72 100644
--- a/doc/misc/eshell.texi
+++ b/doc/misc/eshell.texi
@@ -942,6 +942,16 @@ Variables
directory ring via subscripting, e.g.@: @samp{$-[1]} refers to the
working directory @emph{before} the previous one.
+@vindex $PATH
+@item $PATH
+This specifies the directories to search for executable programs. Its
+value is a string, separated by @code{":"} for Unix and GNU systems,
+and @code{";"} for MS systems. This variable is connection-aware, so
+whenever you change the current directory to a different host
+(@pxref{Remote Files, , , emacs, The GNU Emacs Manual}),
+the value will automatically update to reflect the search path on that
+host.
+
@vindex $_
@item $_
This refers to the last argument of the last command. With a
diff --git a/etc/NEWS b/etc/NEWS
index 72b2331b81..871060148d 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -356,6 +356,11 @@ previous 'C-x ='.
** Eshell
+*** Eshell's PATH is now derived from 'exec-path'.
+For consistency with remote connections, Eshell now uses 'exec-path'
+to determine the execution path on the local system, instead of using
+the PATH environment variable directly.
+
---
*** 'source' and '.' no longer accept the '--help' option.
This is for compatibility with the shell versions of these commands,
diff --git a/lisp/eshell/esh-ext.el b/lisp/eshell/esh-ext.el
index 98902fc6f2..d513d750d9 100644
--- a/lisp/eshell/esh-ext.el
+++ b/lisp/eshell/esh-ext.el
@@ -77,7 +77,7 @@ eshell-search-path
(let ((list (eshell-get-path))
suffixes n1 n2 file)
(while list
- (setq n1 (concat (car list) name))
+ (setq n1 (file-name-concat (car list) name))
(setq suffixes eshell-binary-suffixes)
(while suffixes
(setq n2 (concat n1 (car suffixes)))
@@ -239,17 +239,16 @@ eshell/addpath
(?h "help" nil nil "display this usage message")
:usage "[-b] PATH
Adds the given PATH to $PATH.")
- (if args
- (progn
- (setq eshell-path-env (getenv "PATH")
- args (mapconcat #'identity args path-separator)
- eshell-path-env
- (if prepend
- (concat args path-separator eshell-path-env)
- (concat eshell-path-env path-separator args)))
- (setenv "PATH" eshell-path-env))
- (dolist (dir (parse-colon-path (getenv "PATH")))
- (eshell-printn dir)))))
+ (let ((path (eshell-get-path t)))
+ (if args
+ (progn
+ (setq path (if prepend
+ (append args path)
+ (append path args)))
+ (eshell-set-path path)
+ (string-join path (path-separator)))
+ (dolist (dir path)
+ (eshell-printn dir))))))
(put 'eshell/addpath 'eshell-no-numeric-conversions t)
(put 'eshell/addpath 'eshell-filename-arguments t)
diff --git a/lisp/eshell/esh-util.el b/lisp/eshell/esh-util.el
index 9258ca5e40..55983b1feb 100644
--- a/lisp/eshell/esh-util.el
+++ b/lisp/eshell/esh-util.el
@@ -249,17 +249,58 @@ eshell-path-env
It might be different from \(getenv \"PATH\"), when
`default-directory' points to a remote host.")
-(defun eshell-get-path ()
+(make-obsolete-variable 'eshell-path-env 'eshell-get-path "29.1")
+
+(defvar-local eshell-path-env-list nil)
+
+(connection-local-set-profile-variables
+ 'eshell-connection-default-profile
+ '((eshell-path-env-list . nil)))
+
+(connection-local-set-profiles
+ '(:application eshell)
+ 'eshell-connection-default-profile)
+
+(defun eshell-get-path (&optional local-part)
"Return $PATH as a list.
-Add the current directory on MS-Windows."
- (eshell-parse-colon-path
- (if (eshell-under-windows-p)
- (concat "." path-separator eshell-path-env)
- eshell-path-env)))
+If LOCAL-PART is non-nil, only return the local part of the path.
+Otherwise, return the full, possibly-remote path.
+
+On MS-Windows, add the current directory as the first directory
+in the path."
+ (with-connection-local-application-variables 'eshell
+ (let ((remote (file-remote-p default-directory))
+ (path
+ (or eshell-path-env-list
+ ;; If not already cached, get the path from
+ ;; `exec-path', removing the last element, which is
+ ;; `exec-directory'.
+ (setq-connection-local eshell-path-env-list
+ (butlast (exec-path))))))
+ (when (and (eshell-under-windows-p)
+ (not remote))
+ (push "." path))
+ (if (and remote (not local-part))
+ (mapcar (lambda (x) (file-name-concat remote x)) path)
+ path))))
+
+(defun eshell-set-path (path)
+ "Set the Eshell $PATH to PATH.
+PATH can be either a list of directories or a string of
+directories separated by `path-separator'."
+ (with-connection-local-application-variables 'eshell
+ (setq-connection-local
+ eshell-path-env-list
+ (if (listp path)
+ path
+ ;; Don't use `parse-colon-path' here, since we don't want
+ ;; the additonal translations it does on each element.
+ (split-string path (path-separator))))))
(defun eshell-parse-colon-path (path-env)
"Split string with `parse-colon-path'.
Prepend remote identification of `default-directory', if any."
+ (declare (obsolete nil "29.1"))
(let ((remote (file-remote-p default-directory)))
(if remote
(mapcar
diff --git a/lisp/eshell/esh-var.el b/lisp/eshell/esh-var.el
index caf143e1a1..57ea42f493 100644
--- a/lisp/eshell/esh-var.el
+++ b/lisp/eshell/esh-var.el
@@ -156,7 +156,14 @@ eshell-variable-aliases-list
("LINES" ,(lambda () (window-body-height nil 'remap)) t t)
("INSIDE_EMACS" eshell-inside-emacs t)
- ;; for eshell-cmd.el
+ ;; for esh-ext.el
+ ("PATH" (,(lambda () (string-join (eshell-get-path t) (path-separator)))
+ . ,(lambda (_ value)
+ (eshell-set-path value)
+ value))
+ t t)
+
+ ;; for esh-cmd.el
("_" ,(lambda (indices quoted)
(if (not indices)
(car (last eshell-last-arguments))
@@ -249,7 +256,8 @@ eshell-var-initialize
(setq-local eshell-subcommand-bindings
(append
'((process-environment (eshell-copy-environment))
- (eshell-variable-aliases-list eshell-variable-aliases-list))
+ (eshell-variable-aliases-list eshell-variable-aliases-list)
+ (eshell-path-env-list eshell-path-env-list))
eshell-subcommand-bindings))
(setq-local eshell-special-chars-inside-quoting
diff --git a/lisp/net/tramp-integration.el b/lisp/net/tramp-integration.el
index 35c0636b1c..4be019edd9 100644
--- a/lisp/net/tramp-integration.el
+++ b/lisp/net/tramp-integration.el
@@ -136,16 +136,17 @@ tramp-eshell-directory-change
(getenv "PATH"))))
(with-eval-after-load 'esh-util
- (add-hook 'eshell-mode-hook
- #'tramp-eshell-directory-change)
- (add-hook 'eshell-directory-change-hook
- #'tramp-eshell-directory-change)
- (add-hook 'tramp-integration-unload-hook
- (lambda ()
- (remove-hook 'eshell-mode-hook
- #'tramp-eshell-directory-change)
- (remove-hook 'eshell-directory-change-hook
- #'tramp-eshell-directory-change))))
+ (unless (boundp 'eshell-path-env-list)
+ (add-hook 'eshell-mode-hook
+ #'tramp-eshell-directory-change)
+ (add-hook 'eshell-directory-change-hook
+ #'tramp-eshell-directory-change)
+ (add-hook 'tramp-integration-unload-hook
+ (lambda ()
+ (remove-hook 'eshell-mode-hook
+ #'tramp-eshell-directory-change)
+ (remove-hook 'eshell-directory-change-hook
+ #'tramp-eshell-directory-change)))))
;;; Integration of recentf.el:
diff --git a/test/lisp/eshell/esh-ext-tests.el b/test/lisp/eshell/esh-ext-tests.el
new file mode 100644
index 0000000000..54191e9409
--- /dev/null
+++ b/test/lisp/eshell/esh-ext-tests.el
@@ -0,0 +1,76 @@
+;;; esh-ext-tests.el --- esh-ext test suite -*- lexical-binding:t -*-
+
+;; Copyright (C) 2022 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Tests for Eshell's external command handling.
+
+;;; Code:
+
+(require 'ert)
+(require 'esh-mode)
+(require 'esh-ext)
+(require 'eshell)
+
+(require 'eshell-tests-helpers
+ (expand-file-name "eshell-tests-helpers"
+ (file-name-directory (or load-file-name
+ default-directory))))
+
+;;; Tests:
+
+(ert-deftest esh-ext-test/addpath/end ()
+ "Test that \"addpath\" adds paths to the end of $PATH."
+ (with-temp-eshell
+ (let ((eshell-path-env-list '("/some/path" "/other/path"))
+ (expected-path (string-join '("/some/path" "/other/path" "/new/path"
+ "/new/path2")
+ (path-separator))))
+ (eshell-match-command-output "addpath /new/path /new/path2"
+ (concat expected-path "\n"))
+ (eshell-match-command-output "echo $PATH"
+ (concat expected-path "\n")))))
+
+(ert-deftest esh-ext-test/addpath/begin ()
+ "Test that \"addpath -b\" adds paths to the beginning of $PATH."
+ (with-temp-eshell
+ (let ((eshell-path-env-list '("/some/path" "/other/path"))
+ (expected-path (string-join '("/new/path" "/new/path2" "/some/path"
+ "/other/path")
+ (path-separator))))
+ (eshell-match-command-output "addpath -b /new/path /new/path2"
+ (concat expected-path "\n"))
+ (eshell-match-command-output "echo $PATH"
+ (concat expected-path "\n")))))
+
+(ert-deftest esh-ext-test/addpath/set-locally ()
+ "Test adding to the path temporarily in a subcommand."
+ (let* ((eshell-path-env-list '("/some/path" "/other/path"))
+ (original-path (string-join eshell-path-env-list (path-separator)))
+ (local-path (string-join (append eshell-path-env-list '("/new/path"))
+ (path-separator))))
+ (with-temp-eshell
+ (eshell-match-command-output
+ "{ addpath /new/path; env }"
+ (format "PATH=%s\n" (regexp-quote local-path)))
+ ;; After the last command, the previous $PATH value should be restored.
+ (eshell-match-command-output "echo $PATH"
+ (concat original-path "\n")))))
+
+;; esh-ext-tests.el ends here
diff --git a/test/lisp/eshell/esh-var-tests.el b/test/lisp/eshell/esh-var-tests.el
index a7ac52ed24..31b01c5605 100644
--- a/test/lisp/eshell/esh-var-tests.el
+++ b/test/lisp/eshell/esh-var-tests.el
@@ -23,6 +23,7 @@
;;; Code:
+(require 'tramp)
(require 'ert)
(require 'esh-mode)
(require 'esh-var)
@@ -610,6 +611,65 @@ esh-var-test/inside-emacs-var-split-indices
(eshell-match-command-output "echo $INSIDE_EMACS[, 1]"
"eshell")))
+(ert-deftest esh-var-test/path-var/local-directory ()
+ "Test using $PATH in a local directory."
+ (let ((expected-path (string-join (eshell-get-path t) (path-separator))))
+ (with-temp-eshell
+ (eshell-match-command-output "echo $PATH" (regexp-quote expected-path)))))
+
+(ert-deftest esh-var-test/path-var/remote-directory ()
+ "Test using $PATH in a remote directory."
+ (skip-unless (eshell-tests-remote-accessible-p))
+ (let* ((default-directory ert-remote-temporary-file-directory)
+ (expected-path (string-join (eshell-get-path t) (path-separator))))
+ (with-temp-eshell
+ (eshell-match-command-output "echo $PATH" (regexp-quote expected-path)))))
+
+(ert-deftest esh-var-test/path-var/set ()
+ "Test setting $PATH."
+ (let* ((path-to-set-list '("/some/path" "/other/path"))
+ (path-to-set (string-join path-to-set-list (path-separator))))
+ (with-temp-eshell
+ (eshell-match-command-output (concat "set PATH " path-to-set)
+ (concat path-to-set "\n"))
+ (eshell-match-command-output "echo $PATH" (concat path-to-set "\n"))
+ (should (equal (eshell-get-path) path-to-set-list)))))
+
+(ert-deftest esh-var-test/path-var/set-locally ()
+ "Test setting $PATH temporarily for a single command."
+ (let* ((path-to-set-list '("/some/path" "/other/path"))
+ (path-to-set (string-join path-to-set-list (path-separator))))
+ (with-temp-eshell
+ (eshell-match-command-output (concat "set PATH " path-to-set)
+ (concat path-to-set "\n"))
+ (eshell-match-command-output "PATH=/local/path env"
+ "PATH=/local/path\n")
+ ;; After the last command, the previous $PATH value should be restored.
+ (eshell-match-command-output "echo $PATH" (concat path-to-set "\n"))
+ (should (equal (eshell-get-path) path-to-set-list)))))
+
+(ert-deftest esh-var-test/path-var/preserve-across-hosts ()
+ "Test that $PATH can be set independently on multiple hosts."
+ (let ((local-directory default-directory)
+ local-path remote-path)
+ (with-temp-eshell
+ ;; Set the $PATH on localhost.
+ (eshell-insert-command "set PATH /local/path")
+ (setq local-path (eshell-last-output))
+ ;; `cd' to a remote host and set the $PATH there too.
+ (eshell-insert-command
+ (format "cd %s" ert-remote-temporary-file-directory))
+ (eshell-insert-command "set PATH /remote/path")
+ (setq remote-path (eshell-last-output))
+ ;; Return to localhost and check that $PATH is the value we set
+ ;; originally.
+ (eshell-insert-command (format "cd %s" local-directory))
+ (eshell-match-command-output "echo $PATH" (regexp-quote local-path))
+ ;; ... and do the same for the remote host.
+ (eshell-insert-command
+ (format "cd %s" ert-remote-temporary-file-directory))
+ (eshell-match-command-output "echo $PATH" (regexp-quote remote-path)))))
+
(ert-deftest esh-var-test/last-status-var-lisp-command ()
"Test using the \"last exit status\" ($?) variable with a Lisp command"
(with-temp-eshell
diff --git a/test/lisp/eshell/eshell-tests-helpers.el b/test/lisp/eshell/eshell-tests-helpers.el
index e713e162ad..1d9674070c 100644
--- a/test/lisp/eshell/eshell-tests-helpers.el
+++ b/test/lisp/eshell/eshell-tests-helpers.el
@@ -31,11 +31,22 @@
(require 'eshell)
(defvar eshell-history-file-name nil)
+(defvar eshell-last-dir-ring-file-name nil)
(defvar eshell-test--max-subprocess-time 5
"The maximum amount of time to wait for a subprocess to finish, in seconds.
See `eshell-wait-for-subprocess'.")
+(defun eshell-tests-remote-accessible-p ()
+ "Return if a test involving remote files can proceed.
+If using this function, be sure to load `tramp' near the
+beginning of the test file."
+ (ignore-errors
+ (and
+ (file-remote-p ert-remote-temporary-file-directory)
+ (file-directory-p ert-remote-temporary-file-directory)
+ (file-writable-p ert-remote-temporary-file-directory))))
+
(defmacro with-temp-eshell (&rest body)
"Evaluate BODY in a temporary Eshell buffer."
`(save-current-buffer
@@ -44,6 +55,7 @@ with-temp-eshell
;; back on $HISTFILE.
(process-environment (cons "HISTFILE" process-environment))
(eshell-history-file-name nil)
+ (eshell-last-dir-ring-file-name nil)
(eshell-buffer (eshell t)))
(unwind-protect
(with-current-buffer eshell-buffer
@@ -83,19 +95,25 @@ eshell-insert-command
(insert-and-inherit command)
(funcall (or func 'eshell-send-input)))
+(defun eshell-last-input ()
+ "Return the input of the last Eshell command."
+ (buffer-substring-no-properties
+ eshell-last-input-start eshell-last-input-end))
+
+(defun eshell-last-output ()
+ "Return the output of the last Eshell command."
+ (buffer-substring-no-properties
+ (eshell-beginning-of-output) (eshell-end-of-output)))
+
(defun eshell-match-output (regexp)
"Test whether the output of the last command matches REGEXP."
- (string-match-p
- regexp (buffer-substring-no-properties
- (eshell-beginning-of-output) (eshell-end-of-output))))
+ (string-match-p regexp (eshell-last-output)))
(defun eshell-match-output--explainer (regexp)
"Explain the result of `eshell-match-output'."
`(mismatched-output
- (command ,(buffer-substring-no-properties
- eshell-last-input-start eshell-last-input-end))
- (output ,(buffer-substring-no-properties
- (eshell-beginning-of-output) (eshell-end-of-output)))
+ (command ,(eshell-last-input))
+ (output ,(eshell-last-output))
(regexp ,regexp)))
(put 'eshell-match-output 'ert-explainer #'eshell-match-output--explainer)
--
2.25.1
[-- Attachment #8: 0007-Print-the-correct-PATH-when-Eshell-s-which-fails-to-.patch --]
[-- Type: text/plain, Size: 1079 bytes --]
From 95f021dede1be4b53cd789704543bbfa945442ee Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Thu, 15 Sep 2022 12:32:02 -0700
Subject: [PATCH 7/7] Print the correct $PATH when Eshell's 'which' fails to
find a command
* lisp/eshell/esh-cmd.el (eshell/which): Use 'eshell-get-path'
(bug#20008).
---
lisp/eshell/esh-cmd.el | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el
index c5ceb3ffd1..4a41bbe8fa 100644
--- a/lisp/eshell/esh-cmd.el
+++ b/lisp/eshell/esh-cmd.el
@@ -1274,8 +1274,9 @@ eshell/which
name)
(eshell-search-path name)))))
(if (not program)
- (eshell-error (format "which: no %s in (%s)\n"
- name (getenv "PATH")))
+ (eshell-error (format "which: no %s in (%s)\n"
+ name (string-join (eshell-get-path t)
+ (path-separator))))
(eshell-printn program)))))
(put 'eshell/which 'eshell-no-numeric-conversions t)
--
2.25.1
next prev parent reply other threads:[~2022-10-16 23:07 UTC|newest]
Thread overview: 32+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-09-03 5:03 bug#57556: 28.1; Eshell not finding executables in PATH when tramp-integration loaded Colton Lewis via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-09-03 12:26 ` Lars Ingebrigtsen
2022-09-18 11:18 ` Michael Albinus
2022-09-18 18:54 ` Jim Porter
2022-09-18 19:07 ` Michael Albinus
2022-09-22 17:23 ` Colton Lewis via Bug reports for GNU Emacs, the Swiss army knife of text editors
2022-09-22 17:55 ` Michael Albinus
2022-09-30 3:54 ` Jim Porter
2022-10-01 20:25 ` Michael Albinus
2022-10-01 22:02 ` Jim Porter
2022-10-02 5:34 ` Jim Porter
2022-10-02 8:48 ` Michael Albinus
2022-10-07 3:19 ` Jim Porter
2022-10-07 18:28 ` Michael Albinus
2022-10-08 22:09 ` Jim Porter
2022-10-09 18:01 ` Michael Albinus
2022-10-13 4:11 ` Jim Porter
2022-10-13 6:35 ` Eli Zaretskii
2022-10-14 1:29 ` Jim Porter
2022-10-14 6:17 ` Eli Zaretskii
2022-10-14 12:28 ` Michael Albinus
2022-10-14 12:27 ` Michael Albinus
2022-10-14 20:53 ` Jim Porter
2022-10-15 10:38 ` Michael Albinus
2022-10-15 23:33 ` Jim Porter
2022-10-16 17:00 ` Michael Albinus
2022-10-16 23:01 ` Jim Porter
2022-10-16 20:51 ` Richard Stallman
2022-10-16 23:07 ` Jim Porter [this message]
2022-10-18 1:51 ` Jim Porter
2022-10-10 9:15 ` Michael Albinus
2022-10-02 8:55 ` Michael Albinus
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.gnu.org/software/emacs/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=2b52eb68-b592-1cc9-2168-2d0dbdbfd114@gmail.com \
--to=jporterbugs@gmail.com \
--cc=57556@debbugs.gnu.org \
--cc=michael.albinus@gmx.de \
--cc=rms@gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).