Improve handling of local variable settings in Eshell
This ensures that these commands work the same as normal commands, aside from making environment variable settings local to that command. Among other things, this means that "VAR=value cd dir/" now changes the directory correctly. * lisp/eshell/esh-var.el (eshell-in-local-scope-p) (eshell-local-variable-bindings): New variables. (eshell-var-initialize, eshell-set-variable): Use 'eshell-local-variable-bindings'. (eshell-handle-local-variables): Don't use 'eshell-as-subcommand'. * test/lisp/eshell/esh-var-tests.el (esh-var-test/local-variables/cd): New test.
This commit is contained in:
parent
4834be0949
commit
65829b27ca
2 changed files with 39 additions and 26 deletions
|
@ -255,6 +255,20 @@ copied (a.k.a. \"exported\") to the environment of created subprocesses."
|
|||
(defvar-keymap eshell-var-mode-map
|
||||
"C-c M-v" #'eshell-insert-envvar)
|
||||
|
||||
;;; Internal Variables:
|
||||
|
||||
(defvar eshell-in-local-scope-p nil
|
||||
"Non-nil if the current command has a local variable scope.
|
||||
This is set to t in `eshell-local-variable-bindings' (which see).")
|
||||
|
||||
(defvar eshell-local-variable-bindings
|
||||
'((eshell-in-local-scope-p t)
|
||||
(process-environment (eshell-copy-environment))
|
||||
(eshell-variable-aliases-list eshell-variable-aliases-list)
|
||||
(eshell-path-env-list eshell-path-env-list)
|
||||
(comint-pager comint-pager))
|
||||
"A list of `let' bindings for local variable (and subcommand) environments.")
|
||||
|
||||
;;; Functions:
|
||||
|
||||
(define-minor-mode eshell-var-mode
|
||||
|
@ -271,12 +285,8 @@ copied (a.k.a. \"exported\") to the environment of created subprocesses."
|
|||
(setq-local process-environment (eshell-copy-environment)))
|
||||
(make-local-variable 'comint-pager)
|
||||
(setq-local eshell-subcommand-bindings
|
||||
(append
|
||||
'((process-environment (eshell-copy-environment))
|
||||
(eshell-variable-aliases-list eshell-variable-aliases-list)
|
||||
(eshell-path-env-list eshell-path-env-list)
|
||||
(comint-pager comint-pager))
|
||||
eshell-subcommand-bindings))
|
||||
(append eshell-local-variable-bindings
|
||||
eshell-subcommand-bindings))
|
||||
|
||||
(setq-local eshell-special-chars-inside-quoting
|
||||
(append eshell-special-chars-inside-quoting '(?$)))
|
||||
|
@ -296,30 +306,25 @@ copied (a.k.a. \"exported\") to the environment of created subprocesses."
|
|||
|
||||
(defun eshell-handle-local-variables ()
|
||||
"Allow for the syntax `VAR=val <command> <args>'."
|
||||
;; Eshell handles local variable settings (e.g. 'CFLAGS=-O2 make')
|
||||
;; by making the whole command into a subcommand, and calling
|
||||
;; `eshell-set-variable' immediately before the command is invoked.
|
||||
;; This means that 'FOO=x cd bar' won't work exactly as expected,
|
||||
;; but that is by no means a typical use of local environment
|
||||
;; variables.
|
||||
;; Handle local variable settings by let-binding the entries in
|
||||
;; `eshell-local-variable-bindings' and calling `eshell-set-variable'
|
||||
;; for each variable before the command is invoked.
|
||||
(let ((setvar "\\`\\([A-Za-z_][A-Za-z0-9_]*\\)=\\(.*\\)\\'")
|
||||
(command eshell-last-command-name)
|
||||
(args eshell-last-arguments))
|
||||
(when (and (stringp command) (string-match setvar command))
|
||||
(throw 'eshell-replace-command
|
||||
`(eshell-as-subcommand
|
||||
(progn
|
||||
,@(let (locals)
|
||||
(while (and (stringp command)
|
||||
(string-match setvar command))
|
||||
(push `(eshell-set-variable
|
||||
,(match-string 1 command)
|
||||
,(match-string 2 command))
|
||||
locals)
|
||||
(setq command (pop args)))
|
||||
(nreverse locals))
|
||||
(eshell-named-command ,command ,(list 'quote args)))
|
||||
)))))
|
||||
`(let ,eshell-local-variable-bindings
|
||||
,@(let (locals)
|
||||
(while (and (stringp command)
|
||||
(string-match setvar command))
|
||||
(push `(eshell-set-variable
|
||||
,(match-string 1 command)
|
||||
,(match-string 2 command))
|
||||
locals)
|
||||
(setq command (pop args)))
|
||||
(nreverse locals))
|
||||
(eshell-named-command ,command ,(list 'quote args)))))))
|
||||
|
||||
(defun eshell-interpolate-variable ()
|
||||
"Parse a variable interpolation.
|
||||
|
@ -709,7 +714,7 @@ to a Lisp variable)."
|
|||
((functionp target)
|
||||
(funcall target nil value))
|
||||
((null target)
|
||||
(unless eshell-in-subcommand-p
|
||||
(unless eshell-in-local-scope-p
|
||||
(error "Variable `%s' is not settable" (eshell-stringify name)))
|
||||
(push `(,name ,(lambda () value) t t)
|
||||
eshell-variable-aliases-list)
|
||||
|
|
|
@ -653,6 +653,14 @@ nil, use FUNCTION instead."
|
|||
"VAR=hello\n")
|
||||
(should (equal (getenv "VAR") "value"))))
|
||||
|
||||
(ert-deftest esh-var-test/local-variables/cd ()
|
||||
"Test that \"VAR=value cd DIR\" properly changes the directory."
|
||||
(let ((parent-directory (file-name-directory
|
||||
(directory-file-name default-directory))))
|
||||
(with-temp-eshell
|
||||
(eshell-insert-command "VAR=hello cd ..")
|
||||
(should (equal default-directory parent-directory)))))
|
||||
|
||||
|
||||
;; Variable aliases
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue