summaryrefslogtreecommitdiff
path: root/lisp/eshell
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/eshell')
-rw-r--r--lisp/eshell/em-basic.el24
-rw-r--r--lisp/eshell/em-dirs.el3
-rw-r--r--lisp/eshell/em-glob.el36
-rw-r--r--lisp/eshell/em-tramp.el22
-rw-r--r--lisp/eshell/em-unix.el26
-rw-r--r--lisp/eshell/esh-arg.el7
-rw-r--r--lisp/eshell/esh-cmd.el54
-rw-r--r--lisp/eshell/esh-ext.el6
-rw-r--r--lisp/eshell/esh-mode.el35
-rw-r--r--lisp/eshell/esh-opt.el62
-rw-r--r--lisp/eshell/esh-proc.el2
-rw-r--r--lisp/eshell/esh-util.el51
-rw-r--r--lisp/eshell/esh-var.el97
13 files changed, 241 insertions, 184 deletions
diff --git a/lisp/eshell/em-basic.el b/lisp/eshell/em-basic.el
index 8f68a750bd7..6ec53ef9412 100644
--- a/lisp/eshell/em-basic.el
+++ b/lisp/eshell/em-basic.el
@@ -160,6 +160,18 @@ or `eshell-printn' for display."
:preserve-args
:usage "[-S] [mode]")
(cond
+ (args
+ (let* ((mask (car args))
+ (modes
+ (if (stringp mask)
+ (if (string-match (rx bos (+ (any "0-7")) eos) mask)
+ (- #o777 (string-to-number mask 8))
+ (file-modes-symbolic-to-number
+ mask (default-file-modes)))
+ (- #o777 mask))))
+ (set-default-file-modes modes)
+ (eshell-print
+ "Warning: umask changed for all new files created by Emacs.\n")))
(symbolic-p
(let ((mode (default-file-modes)))
(eshell-printn
@@ -173,17 +185,9 @@ or `eshell-printn' for display."
(concat (and (= (logand mode 1) 1) "r")
(and (= (logand mode 2) 2) "w")
(and (= (logand mode 4) 4) "x"))))))
- ((not args)
- (eshell-printn (format "%03o" (logand (lognot (default-file-modes))
- #o777))))
(t
- (when (stringp (car args))
- (if (string-match "^[0-7]+$" (car args))
- (setcar args (string-to-number (car args) 8))
- (error "Setting umask symbolically is not yet implemented")))
- (set-default-file-modes (- #o777 (car args)))
- (eshell-print
- "Warning: umask changed for all new files created by Emacs.\n")))
+ (eshell-printn (format "%03o" (logand (lognot (default-file-modes))
+ #o777)))))
nil))
(put 'eshell/umask 'eshell-no-numeric-conversions t)
diff --git a/lisp/eshell/em-dirs.el b/lisp/eshell/em-dirs.el
index cf90a8bb230..07063afc286 100644
--- a/lisp/eshell/em-dirs.el
+++ b/lisp/eshell/em-dirs.el
@@ -262,6 +262,7 @@ Thus, this does not include the current directory.")
(defun eshell-parse-user-reference ()
"An argument beginning with ~ is a filename to be expanded."
(when (and (not eshell-current-argument)
+ (not eshell-current-quoted)
(eq (char-after) ?~))
;; Apply this modifier fairly early so it happens before things
;; like glob expansion.
@@ -316,7 +317,7 @@ Thus, this does not include the current directory.")
(`(boundaries . ,suffix)
`(boundaries 0 . ,(string-search "/" suffix))))))))))
-(defun eshell/pwd (&rest _args)
+(defun eshell/pwd ()
"Change output from `pwd' to be cleaner."
(let* ((path default-directory)
(len (length path)))
diff --git a/lisp/eshell/em-glob.el b/lisp/eshell/em-glob.el
index b0c3e6e7a11..7fc6958a00f 100644
--- a/lisp/eshell/em-glob.el
+++ b/lisp/eshell/em-glob.el
@@ -190,6 +190,12 @@ interpretation."
'(("**/" . recurse)
("***/" . recurse-symlink)))
+(defsubst eshell-glob-chars-regexp ()
+ "Return the lazily-created value for `eshell-glob-chars-regexp'."
+ (or eshell-glob-chars-regexp
+ (setq-local eshell-glob-chars-regexp
+ (format "[%s]+" (apply 'string eshell-glob-chars-list)))))
+
(defun eshell-glob-regexp (pattern)
"Convert glob-pattern PATTERN to a regular expression.
The basic syntax is:
@@ -210,11 +216,8 @@ set to true, then these characters will match themselves in the
resulting regular expression."
(let ((matched-in-pattern 0) ; How much of PATTERN handled
regexp)
- (while (string-match
- (or eshell-glob-chars-regexp
- (setq-local eshell-glob-chars-regexp
- (format "[%s]+" (apply 'string eshell-glob-chars-list))))
- pattern matched-in-pattern)
+ (while (string-match (eshell-glob-chars-regexp)
+ pattern matched-in-pattern)
(let* ((op-begin (match-beginning 0))
(op-char (aref pattern op-begin)))
(setq regexp
@@ -239,6 +242,10 @@ resulting regular expression."
(regexp-quote (substring pattern matched-in-pattern))
"\\'")))
+(defun eshell-glob-p (pattern)
+ "Return non-nil if PATTERN has any special glob characters."
+ (string-match (eshell-glob-chars-regexp) pattern))
+
(defun eshell-glob-convert-1 (glob &optional last)
"Convert a GLOB matching a single element of a file name to regexps.
If LAST is non-nil, this glob is the last element of a file name.
@@ -291,14 +298,13 @@ The result is a list of three elements:
symlinks.
3. A boolean indicating whether to match directories only."
- (let ((globs (eshell-split-path glob))
- (isdir (eq (aref glob (1- (length glob))) ?/))
+ (let ((globs (eshell-split-filename glob))
+ (isdir (string-suffix-p "/" glob))
start-dir result last-saw-recursion)
(if (and (cdr globs)
(file-name-absolute-p (car globs)))
- (setq start-dir (car globs)
- globs (cdr globs))
- (setq start-dir "."))
+ (setq start-dir (pop globs))
+ (setq start-dir (file-name-as-directory ".")))
(while globs
(if-let ((recurse (cdr (assoc (car globs)
eshell-glob-recursive-alist))))
@@ -306,11 +312,15 @@ The result is a list of three elements:
(setcar result recurse)
(push recurse result)
(setq last-saw-recursion t))
- (push (eshell-glob-convert-1 (car globs) (null (cdr globs)))
- result)
+ (if (or result (eshell-glob-p (car globs)))
+ (push (eshell-glob-convert-1 (car globs) (null (cdr globs)))
+ result)
+ ;; We haven't seen a glob yet, so instead append to the start
+ ;; directory.
+ (setq start-dir (file-name-concat start-dir (car globs))))
(setq last-saw-recursion nil))
(setq globs (cdr globs)))
- (list (file-name-as-directory start-dir)
+ (list start-dir
(nreverse result)
isdir)))
diff --git a/lisp/eshell/em-tramp.el b/lisp/eshell/em-tramp.el
index 90f9c6cf78d..efb37225651 100644
--- a/lisp/eshell/em-tramp.el
+++ b/lisp/eshell/em-tramp.el
@@ -121,12 +121,11 @@ Uses the system sudo through Tramp's sudo method."
:usage "[(-u | --user) USER] (-s | --shell) | COMMAND
Execute a COMMAND as the superuser or another USER.")
(let ((dir (eshell--method-wrap-directory default-directory "sudo" user)))
- (if shell
- (throw 'eshell-replace-command
- (eshell-parse-command "cd" (list dir)))
- (throw 'eshell-external
- (let ((default-directory dir))
- (eshell-named-command (car args) (cdr args))))))))
+ (throw 'eshell-replace-command
+ (if shell
+ (eshell-parse-command "cd" (list dir))
+ `(let ((default-directory ,dir))
+ (eshell-named-command ',(car args) ',(cdr args))))))))
(put 'eshell/sudo 'eshell-no-numeric-conversions t)
@@ -144,12 +143,11 @@ Uses the system doas through Tramp's doas method."
:usage "[(-u | --user) USER] (-s | --shell) | COMMAND
Execute a COMMAND as the superuser or another USER.")
(let ((dir (eshell--method-wrap-directory default-directory "doas" user)))
- (if shell
- (throw 'eshell-replace-command
- (eshell-parse-command "cd" (list dir)))
- (throw 'eshell-external
- (let ((default-directory dir))
- (eshell-named-command (car args) (cdr args))))))))
+ (throw 'eshell-replace-command
+ (if shell
+ (eshell-parse-command "cd" (list dir))
+ `(let ((default-directory ,dir))
+ (eshell-named-command ',(car args) ',(cdr args))))))))
(put 'eshell/doas 'eshell-no-numeric-conversions t)
diff --git a/lisp/eshell/em-unix.el b/lisp/eshell/em-unix.el
index 75afaf1c104..751f13cc715 100644
--- a/lisp/eshell/em-unix.el
+++ b/lisp/eshell/em-unix.el
@@ -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)
@@ -590,7 +590,7 @@ Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.
:external "cp"
:show-usage
:usage "[OPTION]... SOURCE DEST
- or: cp [OPTION]... SOURCE... DIRECTORY
+ or: cp [OPTION]... SOURCE... DIRECTORY
Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.")
(if archive
(setq preserve t no-dereference t em-recursive t))
@@ -618,11 +618,11 @@ Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.")
:preserve-args
:external "ln"
:show-usage
- :usage "[OPTION]... TARGET [LINK_NAME]
- or: ln [OPTION]... TARGET... DIRECTORY
-Create a link to the specified TARGET with optional LINK_NAME. If there is
-more than one TARGET, the last argument must be a directory; create links
-in DIRECTORY to each TARGET. Create hard links by default, symbolic links
+ :usage "[OPTION]... TARGET LINK_NAME
+ or: ln [OPTION]... TARGET... DIRECTORY
+Create a link to the specified TARGET with LINK_NAME. If there is more
+than one TARGET, the last argument must be a directory; create links in
+DIRECTORY to each TARGET. Create hard links by default, symbolic links
with `--symbolic'. When creating hard links, each TARGET must exist.")
(let ((no-dereference t))
(eshell-mvcpln-template "ln" "linking"
@@ -741,7 +741,7 @@ Fallback to standard make when called synchronously."
(eshell-compile "make" args
;; Use plain output unless we're executing in the
;; background.
- (not eshell-current-subjob-p)))
+ (unless eshell-current-subjob-p 'plain)))
(put 'eshell/make 'eshell-no-numeric-conversions t)
@@ -789,7 +789,7 @@ available..."
(ignore-errors
(occur (car args))))
(if (get-buffer "*Occur*")
- (with-current-buffer (get-buffer "*Occur*")
+ (with-current-buffer "*Occur*"
(setq string (buffer-string))
(kill-buffer (current-buffer)))))
(if string (insert string))
@@ -940,7 +940,7 @@ external command."
"display data only this many levels of data")
(?h "human-readable" 1024 human-readable
"print sizes in human readable format")
- (?H "is" 1000 human-readable
+ (?H "si" 1000 human-readable
"likewise, but use powers of 1000 not 1024")
(?k "kilobytes" 1024 block-size
"like --block-size 1024")
@@ -1018,7 +1018,7 @@ Show wall-clock time elapsed during execution of COMMAND.")
(eshell-stringify-list
(flatten-tree (cdr time-args))))))))
-(defun eshell/whoami (&rest _args)
+(defun eshell/whoami ()
"Make \"whoami\" Tramp aware."
(eshell-user-login-name))
diff --git a/lisp/eshell/esh-arg.el b/lisp/eshell/esh-arg.el
index 1880cc03885..78cf28d785a 100644
--- a/lisp/eshell/esh-arg.el
+++ b/lisp/eshell/esh-arg.el
@@ -285,7 +285,7 @@ QUOTED is passed to `eshell-concat' (which see) and, if non-nil,
allows values to be converted to numbers where appropriate.
ARGS should be a list of lists of arguments, such as that
-produced by `eshell-prepare-slice'. \"Adjacent\" values of
+produced by `eshell-prepare-splice'. \"Adjacent\" values of
consecutive arguments will be passed to `eshell-concat'. For
example, if ARGS is
@@ -440,6 +440,7 @@ Point is left at the end of the arguments."
(defsubst eshell-looking-at-backslash-return (pos)
"Test whether a backslash-return sequence occurs at POS."
+ (declare (obsolete nil "30.1"))
(and (eq (char-after pos) ?\\)
(or (= (1+ pos) (point-max))
(and (eq (char-after (1+ pos)) ?\n)
@@ -464,8 +465,8 @@ backslash is ignored and the character after is returned. If the
backslash is in a quoted string, the backslash and the character
after are both returned."
(when (eq (char-after) ?\\)
- (when (eshell-looking-at-backslash-return (point))
- (throw 'eshell-incomplete "\\"))
+ (when (= (1+ (point)) (point-max))
+ (throw 'eshell-incomplete "\\"))
(forward-char 2) ; Move one char past the backslash.
(let ((special-chars (if eshell-current-quoted
eshell-special-chars-inside-quoting
diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el
index 2746800ea78..30494bafb48 100644
--- a/lisp/eshell/esh-cmd.el
+++ b/lisp/eshell/esh-cmd.el
@@ -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."
diff --git a/lisp/eshell/esh-ext.el b/lisp/eshell/esh-ext.el
index dc2b93e574b..44861c222b8 100644
--- a/lisp/eshell/esh-ext.el
+++ b/lisp/eshell/esh-ext.el
@@ -253,10 +253,10 @@ An external command simply means external to Emacs."
"Add a set of paths to PATH."
(eshell-eval-using-options
"addpath" args
- '((?b "begin" nil prepend "add path element at beginning")
+ '((?b "begin" nil prepend "add to beginning of $PATH")
(?h "help" nil nil "display this usage message")
- :usage "[-b] PATH
-Adds the given PATH to $PATH.")
+ :usage "[-b] DIR...
+Adds the given DIR to $PATH.")
(let ((path (eshell-get-path t)))
(if args
(progn
diff --git a/lisp/eshell/esh-mode.el b/lisp/eshell/esh-mode.el
index 21e3f00086f..b15f99a0359 100644
--- a/lisp/eshell/esh-mode.el
+++ b/lisp/eshell/esh-mode.el
@@ -290,7 +290,7 @@ non-interactive sessions, such as when using `eshell-command'.")
"C-e" #'eshell-show-maximum-output
"C-f" #'eshell-forward-argument
"C-m" #'eshell-copy-old-input
- "C-o" #'eshell-kill-output
+ "C-o" #'eshell-delete-output
"C-r" #'eshell-show-output
"C-t" #'eshell-truncate-buffer
"C-u" #'eshell-kill-input
@@ -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)))))
@@ -831,15 +832,23 @@ This function should be in the list `eshell-output-filter-functions'."
eshell-last-output-start
eshell-last-output-end))
-(defun eshell-kill-output ()
- "Kill all output from interpreter since last input.
-Does not delete the prompt."
- (interactive)
+(defun eshell-delete-output (&optional kill)
+ "Delete all output from interpreter since last input.
+If KILL is non-nil (interactively, the prefix), save the killed text in
+the kill ring.
+
+This command does not delete the prompt."
+ (interactive "P")
(save-excursion
(goto-char (eshell-beginning-of-output))
(insert "*** output flushed ***\n")
+ (when kill
+ (copy-region-as-kill (point) (eshell-end-of-output)))
(delete-region (point) (eshell-end-of-output))))
+(define-obsolete-function-alias 'eshell-kill-output
+ #'eshell-delete-output "30.1")
+
(defun eshell-show-output (&optional arg)
"Display start of this batch of interpreter output at top of window.
Sets mark to the value of point when this command is run.
diff --git a/lisp/eshell/esh-opt.el b/lisp/eshell/esh-opt.el
index d01e3569d57..e6f5fc9629a 100644
--- a/lisp/eshell/esh-opt.el
+++ b/lisp/eshell/esh-opt.el
@@ -100,29 +100,37 @@ the new process for its value.
Lastly, any remaining arguments will be available in the locally
let-bound variable `args'."
(declare (debug (form form sexp body)))
- `(let* ((temp-args
- ,(if (memq ':preserve-args (cadr options))
- (list 'copy-tree macro-args)
- (list 'eshell-stringify-list
- (list 'flatten-tree macro-args))))
- (processed-args (eshell--do-opts ,name ,options temp-args ,macro-args))
- ,@(delete-dups
- (delq nil (mapcar (lambda (opt)
- (and (listp opt) (nth 3 opt)
- `(,(nth 3 opt) (pop processed-args))))
- ;; `options' is of the form (quote OPTS).
- (cadr options))))
- (args processed-args))
- ;; Silence unused lexical variable warning if body does not use `args'.
- (ignore args)
- ,@body-forms))
+ (let ((option-syms (eshell--get-option-symbols
+ ;; `options' is of the form (quote OPTS).
+ (cadr options))))
+ `(let* ((temp-args
+ ,(if (memq ':preserve-args (cadr options))
+ (list 'copy-tree macro-args)
+ (list 'eshell-stringify-list
+ (list 'flatten-tree macro-args))))
+ (args (eshell--do-opts ,name temp-args ,macro-args
+ ,options ',option-syms))
+ ;; Bind all the option variables. When done, `args' will
+ ;; contain any remaining positional arguments.
+ ,@(mapcar (lambda (sym) `(,sym (pop args))) option-syms))
+ ;; Silence unused lexical variable warning if body does not use `args'.
+ (ignore args)
+ ,@body-forms)))
;;; Internal Functions:
;; Documented part of the interface; see eshell-eval-using-options.
(defvar eshell--args)
-(defun eshell--do-opts (name options args orig-args)
+(defun eshell--get-option-symbols (options)
+ "Get a list of symbols for the specified OPTIONS.
+OPTIONS is a list of command-line options from
+`eshell-eval-using-options' (which see)."
+ (delete-dups
+ (delq nil (mapcar (lambda (opt) (and (listp opt) (nth 3 opt)))
+ options))))
+
+(defun eshell--do-opts (name args orig-args options option-syms)
"Helper function for `eshell-eval-using-options'.
This code doesn't really need to be macro expanded everywhere."
(require 'esh-ext)
@@ -134,7 +142,8 @@ This code doesn't really need to be macro expanded everywhere."
(if (and (= (length args) 0)
(memq ':show-usage options))
(eshell-show-usage name options)
- (setq args (eshell--process-args name args options))
+ (setq args (eshell--process-args name args options
+ option-syms))
nil))))
(when usage-msg
(user-error "%s" usage-msg))))))
@@ -269,16 +278,13 @@ triggered to say that the switch is unrecognized."
"%s: unrecognized option --%s")
name (car switch)))))))
-(defun eshell--process-args (name args options)
- "Process the given ARGS using OPTIONS."
- (let* ((seen ())
- (opt-vals (delq nil (mapcar (lambda (opt)
- (when (listp opt)
- (let ((sym (nth 3 opt)))
- (when (and sym (not (memq sym seen)))
- (push sym seen)
- (list sym)))))
- options)))
+(defun eshell--process-args (name args options option-syms)
+ "Process the given ARGS for the command NAME using OPTIONS.
+OPTION-SYMS is a list of symbols that will hold the processed arguments.
+
+Return a list of values corresponding to each element in OPTION-SYMS,
+followed by any additional positional arguments."
+ (let* ((opt-vals (mapcar #'list option-syms))
(ai 0) arg
(eshell--args args)
(pos-argument-found nil))
diff --git a/lisp/eshell/esh-proc.el b/lisp/eshell/esh-proc.el
index 2bb0043bddb..35c81f6a4b2 100644
--- a/lisp/eshell/esh-proc.el
+++ b/lisp/eshell/esh-proc.el
@@ -193,7 +193,7 @@ This is like `process-live-p', but additionally checks whether
(defalias 'eshell/wait #'eshell-wait-for-process)
-(defun eshell/jobs (&rest _args)
+(defun eshell/jobs ()
"List processes, if there are any."
(and (fboundp 'process-list)
(process-list)
diff --git a/lisp/eshell/esh-util.el b/lisp/eshell/esh-util.el
index f0acfecb701..129134814e3 100644
--- a/lisp/eshell/esh-util.el
+++ b/lisp/eshell/esh-util.el
@@ -447,29 +447,34 @@ Prepend remote identification of `default-directory', if any."
(parse-colon-path path-env))
(parse-colon-path path-env))))
-(defun eshell-split-path (path)
- "Split a path into multiple subparts."
- (let ((len (length path))
- (i 0) (li 0)
- parts)
- (if (and (eshell-under-windows-p)
- (> len 2)
- (eq (aref path 0) ?/)
- (eq (aref path 1) ?/))
- (setq i 2))
- (while (< i len)
- (if (and (eq (aref path i) ?/)
- (not (get-text-property i 'escaped path)))
- (setq parts (cons (if (= li i) "/"
- (substring path li (1+ i))) parts)
- li (1+ i)))
- (setq i (1+ i)))
- (if (< li i)
- (setq parts (cons (substring path li i) parts)))
- (if (and (eshell-under-windows-p)
- (string-match "\\`[A-Za-z]:\\'" (car (last parts))))
- (setcar (last parts) (concat (car (last parts)) "/")))
- (nreverse parts)))
+(defun eshell-split-filename (filename)
+ "Split a FILENAME into a list of file/directory components."
+ (let* ((remote (file-remote-p filename))
+ (filename (file-local-name filename))
+ (len (length filename))
+ (index 0) (curr-start 0)
+ parts)
+ (when (and (eshell-under-windows-p)
+ (string-prefix-p "//" filename))
+ (setq index 2))
+ (while (< index len)
+ (when (and (eq (aref filename index) ?/)
+ (not (get-text-property index 'escaped filename)))
+ (push (if (= curr-start index) "/"
+ (substring filename curr-start (1+ index)))
+ parts)
+ (setq curr-start (1+ index)))
+ (setq index (1+ index)))
+ (when (< curr-start len)
+ (push (substring filename curr-start) parts))
+ (setq parts (nreverse parts))
+ (when (and (eshell-under-windows-p)
+ (string-match "\\`[A-Za-z]:\\'" (car parts)))
+ (setcar parts (concat (car parts) "/")))
+ (if remote (cons remote parts) parts)))
+
+(define-obsolete-function-alias 'eshell-split-path
+ 'eshell-split-filename "30.1")
(defun eshell-to-flat-string (value)
"Make value a string. If separated by newlines change them to spaces."
diff --git a/lisp/eshell/esh-var.el b/lisp/eshell/esh-var.el
index ae0b18cd13a..02b5c785625 100644
--- a/lisp/eshell/esh-var.el
+++ b/lisp/eshell/esh-var.el
@@ -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,10 @@ 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-complex-commands
+ (append '("env") eshell-complex-commands))
(setq-local eshell-special-chars-inside-quoting
(append eshell-special-chars-inside-quoting '(?$)))
@@ -294,32 +306,36 @@ copied (a.k.a. \"exported\") to the environment of created subprocesses."
(add-hook 'pcomplete-try-first-hook
#'eshell-complete-variable-assignment nil t)))
-(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.
+(defun eshell-parse-local-variables (args)
+ "Parse a list of ARGS, looking for variable assignments.
+Variable assignments are of the form \"VAR=value\". If ARGS
+begins with any such assignments, throw `eshell-replace-command'
+with a form that will temporarily set those variables.
+Otherwise, return nil."
+ ;; 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))
+ (head (car args))
+ (rest (cdr args)))
+ (when (and (stringp head) (string-match setvar head))
(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 head)
+ (string-match setvar head))
+ (push `(eshell-set-variable
+ ,(match-string 1 head)
+ ,(match-string 2 head))
+ locals)
+ (setq head (pop rest)))
+ (nreverse locals))
+ (eshell-named-command ,head ',rest))))))
+
+(defun eshell-handle-local-variables ()
+ "Allow for the syntax `VAR=val <command> <args>'."
+ (eshell-parse-local-variables (cons eshell-last-command-name
+ eshell-last-arguments)))
(defun eshell-interpolate-variable ()
"Parse a variable interpolation.
@@ -409,19 +425,22 @@ the values of nil for each."
obarray #'boundp))
(pcomplete-here))))
-;; FIXME the real "env" command does more than this, it runs a program
-;; in a modified environment.
(defun eshell/env (&rest args)
"Implementation of `env' in Lisp."
- (eshell-init-print-buffer)
(eshell-eval-using-options
"env" args
- '((?h "help" nil nil "show this usage screen")
+ '(;; FIXME: Support more "env" options, like "--unset".
+ (?h "help" nil nil "show this usage screen")
:external "env"
- :usage "<no arguments>")
- (dolist (setting (sort (eshell-environment-variables) 'string-lessp))
- (eshell-buffered-print setting "\n"))
- (eshell-flush)))
+ :parse-leading-options-only
+ :usage "[NAME=VALUE]... [COMMAND]...")
+ (if args
+ (or (eshell-parse-local-variables args)
+ (eshell-named-command (car args) (cdr args)))
+ (eshell-init-print-buffer)
+ (dolist (setting (sort (eshell-environment-variables) 'string-lessp))
+ (eshell-buffered-print setting "\n"))
+ (eshell-flush))))
(defun eshell-insert-envvar (envvar-name)
"Insert ENVVAR-NAME into the current buffer at point."
@@ -709,7 +728,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)