summaryrefslogtreecommitdiff
path: root/lisp/eshell
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/eshell')
-rw-r--r--lisp/eshell/em-banner.el3
-rw-r--r--lisp/eshell/em-basic.el93
-rw-r--r--lisp/eshell/em-cmpl.el49
-rw-r--r--lisp/eshell/em-dirs.el6
-rw-r--r--lisp/eshell/em-elecslash.el120
-rw-r--r--lisp/eshell/em-extpipe.el204
-rw-r--r--lisp/eshell/em-glob.el14
-rw-r--r--lisp/eshell/em-hist.el96
-rw-r--r--lisp/eshell/em-ls.el13
-rw-r--r--lisp/eshell/em-pred.el329
-rw-r--r--lisp/eshell/em-prompt.el8
-rw-r--r--lisp/eshell/em-rebind.el8
-rw-r--r--lisp/eshell/em-script.el18
-rw-r--r--lisp/eshell/em-term.el2
-rw-r--r--lisp/eshell/em-tramp.el118
-rw-r--r--lisp/eshell/esh-arg.el96
-rw-r--r--lisp/eshell/esh-cmd.el251
-rw-r--r--lisp/eshell/esh-io.el32
-rw-r--r--lisp/eshell/esh-mode.el94
-rw-r--r--lisp/eshell/esh-module.el1
-rw-r--r--lisp/eshell/esh-opt.el109
-rw-r--r--lisp/eshell/esh-proc.el74
-rw-r--r--lisp/eshell/esh-util.el140
-rw-r--r--lisp/eshell/esh-var.el189
-rw-r--r--lisp/eshell/eshell.el8
25 files changed, 1355 insertions, 720 deletions
diff --git a/lisp/eshell/em-banner.el b/lisp/eshell/em-banner.el
index ecac9d2a30e..a2f8a58220c 100644
--- a/lisp/eshell/em-banner.el
+++ b/lisp/eshell/em-banner.el
@@ -61,10 +61,9 @@ modules may have a simple template to begin with."
"The banner message to be displayed when Eshell is loaded.
This can be any sexp, and should end with at least two newlines."
:type 'sexp
+ :risky t
:group 'eshell-banner)
-(put 'eshell-banner-message 'risky-local-variable t)
-
(defcustom eshell-banner-load-hook nil
"A list of functions to run when `eshell-banner' is loaded."
:version "24.1" ; removed eshell-banner-initialize
diff --git a/lisp/eshell/em-basic.el b/lisp/eshell/em-basic.el
index 27b343ad398..448b6787ee7 100644
--- a/lisp/eshell/em-basic.el
+++ b/lisp/eshell/em-basic.el
@@ -82,7 +82,11 @@ equivalent of `echo' can always be achieved by using `identity'."
It returns a formatted value that should be passed to `eshell-print'
or `eshell-printn' for display."
(if eshell-plain-echo-behavior
- (concat (apply 'eshell-flatten-and-stringify args) "\n")
+ (progn
+ ;; If the output does not end in a newline, do not emit one.
+ (setq eshell-ensure-newline-p nil)
+ (concat (apply #'eshell-flatten-and-stringify args)
+ (when output-newline "\n")))
(let ((value
(cond
((= (length args) 0) "")
@@ -109,18 +113,33 @@ or `eshell-printn' for display."
"Implementation of `echo'. See `eshell-plain-echo-behavior'."
(eshell-eval-using-options
"echo" args
- '((?n nil nil output-newline "terminate with a newline")
- (?h "help" nil nil "output this help screen")
+ '((?n nil (nil) output-newline
+ "do not output the trailing newline")
+ (?N nil (t) output-newline
+ "terminate with a newline")
+ (?E nil nil _disable-escapes
+ "don't interpret backslash escapes (default)")
+ (?h "help" nil nil
+ "output this help screen")
:preserve-args
- :usage "[-n] [object]")
- (eshell-echo args output-newline)))
+ :usage "[OPTION]... [OBJECT]...")
+ (if eshell-plain-echo-behavior
+ (eshell-echo args (if output-newline (car output-newline) t))
+ ;; In Emacs 28.1 and earlier, "-n" was used to add a newline to
+ ;; non-plain echo in Eshell. This caused confusion due to "-n"
+ ;; generally having the opposite meaning for echo. Retain this
+ ;; compatibility for the time being. For more info, see
+ ;; bug#27361.
+ (when (equal output-newline '(nil))
+ (display-warning
+ :warning "To terminate with a newline, you should use -N instead."))
+ (eshell-echo args output-newline))))
(defun eshell/printnl (&rest args)
- "Print out each of the arguments, separated by newlines."
+ "Print out each of the arguments as strings, separated by newlines."
(let ((elems (flatten-tree args)))
- (while elems
- (eshell-printn (eshell-echo (list (car elems))))
- (setq elems (cdr elems)))))
+ (dolist (elem elems)
+ (eshell-printn (eshell-stringify elem)))))
(defun eshell/listify (&rest args)
"Return the argument(s) as a single list."
@@ -136,39 +155,37 @@ or `eshell-printn' for display."
"umask" args
'((?S "symbolic" nil symbolic-p "display umask symbolically")
(?h "help" nil nil "display this usage message")
+ :preserve-args
:usage "[-S] [mode]")
- (if (or (not args) symbolic-p)
- (let ((modstr
- (concat "000"
- (format "%o"
- (logand (lognot (default-file-modes))
- 511)))))
- (setq modstr (substring modstr (- (length modstr) 3)))
- (when symbolic-p
- (let ((mode (default-file-modes)))
- (setq modstr
- (format
- "u=%s,g=%s,o=%s"
- (concat (and (= (logand mode 64) 64) "r")
- (and (= (logand mode 128) 128) "w")
- (and (= (logand mode 256) 256) "x"))
- (concat (and (= (logand mode 8) 8) "r")
- (and (= (logand mode 16) 16) "w")
- (and (= (logand mode 32) 32) "x"))
- (concat (and (= (logand mode 1) 1) "r")
- (and (= (logand mode 2) 2) "w")
- (and (= (logand mode 4) 4) "x"))))))
- (eshell-printn modstr))
- (setcar args (eshell-convert (car args)))
- (if (numberp (car args))
- (set-default-file-modes
- (- 511 (car (read-from-string
- (concat "?\\" (number-to-string (car args)))))))
- (error "Setting umask symbolically is not yet implemented"))
+ (cond
+ (symbolic-p
+ (let ((mode (default-file-modes)))
+ (eshell-printn
+ (format "u=%s,g=%s,o=%s"
+ (concat (and (= (logand mode 64) 64) "r")
+ (and (= (logand mode 128) 128) "w")
+ (and (= (logand mode 256) 256) "x"))
+ (concat (and (= (logand mode 8) 8) "r")
+ (and (= (logand mode 16) 16) "w")
+ (and (= (logand mode 32) 32) "x"))
+ (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"))
+ "Warning: umask changed for all new files created by Emacs.\n")))
nil))
+(put 'eshell/umask 'eshell-no-numeric-conversions t)
+
(provide 'em-basic)
;; Local Variables:
diff --git a/lisp/eshell/em-cmpl.el b/lisp/eshell/em-cmpl.el
index 706eb8aede0..f4c1302629b 100644
--- a/lisp/eshell/em-cmpl.el
+++ b/lisp/eshell/em-cmpl.el
@@ -226,19 +226,17 @@ to writing a completion function."
(let ((completion-at-point-functions '(elisp-completion-at-point)))
(completion-at-point)))
-(defvar eshell-cmpl-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map [(control ?i)] #'completion-at-point)
- ;; jww (1999-10-19): Will this work on anything but X?
- (define-key map [backtab] #'pcomplete-reverse)
- (define-key map [(meta ??)] #'completion-help-at-point)
- (define-key map [(meta control ?i)] #'eshell-complete-lisp-symbol)
- ;; C-c prefix:
- (define-key map (kbd "C-c M-h") #'eshell-completion-help)
- (define-key map (kbd "C-c TAB") #'pcomplete-expand-and-complete)
- (define-key map (kbd "C-c C-i") #'pcomplete-expand-and-complete)
- (define-key map (kbd "C-c SPC") #'pcomplete-expand)
- map))
+(defvar-keymap eshell-cmpl-mode-map
+ "C-i" #'completion-at-point
+ ;; jww (1999-10-19): Will this work on anything but X?
+ "<backtab>" #'pcomplete-reverse
+ "M-?" #'completion-help-at-point
+ "C-M-i" #'eshell-complete-lisp-symbol
+ ;; C-c prefix:
+ "C-c M-h" #'eshell-completion-help
+ "C-c TAB" #'pcomplete-expand-and-complete
+ "C-c C-i" #'pcomplete-expand-and-complete
+ "C-c SPC" #'pcomplete-expand)
(define-minor-mode eshell-cmpl-mode
"Minor mode that provides a keymap when `eshell-cmpl' active.
@@ -313,18 +311,24 @@ to writing a completion function."
(describe-prefix-bindings)
(call-interactively 'pcomplete-help)))
+(defun eshell--pcomplete-insert-tab ()
+ (if (not pcomplete-allow-modifications)
+ (throw 'pcompleted nil)
+ (insert-and-inherit "\t")
+ (throw 'pcompleted t)))
+
(defun eshell-complete-parse-arguments ()
"Parse the command line arguments for `pcomplete-argument'."
(when (and eshell-no-completion-during-jobs
- (eshell-interactive-process))
- (insert-and-inherit "\t")
- (throw 'pcompleted t))
+ (eshell-interactive-process-p))
+ (eshell--pcomplete-insert-tab))
(let ((end (point-marker))
(begin (save-excursion (eshell-bol) (point)))
(posns (list t))
args delim)
- (when (memq this-command '(pcomplete-expand
- pcomplete-expand-and-complete))
+ (when (and pcomplete-allow-modifications
+ (memq this-command '(pcomplete-expand
+ pcomplete-expand-and-complete)))
(run-hook-with-args 'eshell-expand-input-functions begin end)
(if (= begin end)
(end-of-line))
@@ -337,14 +341,11 @@ to writing a completion function."
(setq begin (1+ (cadr delim))
args (eshell-parse-arguments begin end)))
((eq (car delim) ?\()
- (eshell-complete-lisp-symbol)
- (throw 'pcompleted t))
+ (throw 'pcompleted (elisp-completion-at-point)))
(t
- (insert-and-inherit "\t")
- (throw 'pcompleted t))))
+ (eshell--pcomplete-insert-tab))))
(when (get-text-property (1- end) 'comment)
- (insert-and-inherit "\t")
- (throw 'pcompleted t))
+ (eshell--pcomplete-insert-tab))
(let ((pos begin))
(while (< pos end)
(if (get-text-property pos 'arg-begin)
diff --git a/lisp/eshell/em-dirs.el b/lisp/eshell/em-dirs.el
index 893cad7b4fb..5396044d8ca 100644
--- a/lisp/eshell/em-dirs.el
+++ b/lisp/eshell/em-dirs.el
@@ -313,7 +313,7 @@ With the following piece of advice, you can make this functionality
available in most of Emacs, with the exception of filename completion
in the minibuffer:
- (advice-add 'expand-file-name :around #'my-expand-multiple-dots)
+ (advice-add \\='expand-file-name :around #\\='my-expand-multiple-dots)
(defun my-expand-multiple-dots (orig-fun filename &rest args)
(apply orig-fun (eshell-expand-multiple-dots filename) args))"
(while (string-match "\\(?:\\`\\|/\\)\\.\\.\\(\\.+\\)\\(?:\\'\\|/\\)"
@@ -391,6 +391,10 @@ in the minibuffer:
(unless (equal curdir newdir)
(eshell-add-to-dir-ring curdir))
(let ((result (cd newdir)))
+ ;; If we're in "/" and cd to ".." or the like, make things
+ ;; less confusing by changing "/.." to "/".
+ (when (equal (file-truename result) "/")
+ (setq result (cd "/")))
(and eshell-cd-shows-directory
(eshell-printn result)))
(run-hooks 'eshell-directory-change-hook)
diff --git a/lisp/eshell/em-elecslash.el b/lisp/eshell/em-elecslash.el
new file mode 100644
index 00000000000..091acb9a861
--- /dev/null
+++ b/lisp/eshell/em-elecslash.el
@@ -0,0 +1,120 @@
+;;; em-elecslash.el --- electric forward slashes -*- lexical-binding:t -*-
+
+;; Copyright (C) 2022 Free Software Foundation, Inc.
+
+;; Author: Sean Whitton <spwhitton@spwhitton.name>
+
+;; 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:
+
+;; Electric forward slash in remote Eshells.
+
+;;; Code:
+
+(require 'tramp)
+(require 'thingatpt)
+(require 'esh-cmd)
+(require 'esh-ext)
+(require 'esh-mode)
+
+;; This makes us an option when customizing `eshell-modules-list'.
+;;;###autoload
+(progn
+(defgroup eshell-elecslash nil
+ "Electric forward slash in remote Eshells.
+
+This module helps with supplying absolute file name arguments to
+remote commands. After enabling it, typing a forward slash as
+the first character of a command line argument will automatically
+insert the Tramp prefix, /method:host:. The automatic insertion
+applies only when `default-directory' is remote and the command
+is a Lisp function.
+
+The result is that in most cases of supplying absolute file name
+arguments to commands you should see the Tramp prefix inserted
+automatically only when that's what you'd reasonably expect.
+This frees you from having to keep track of whether commands are
+Lisp functions or external when typing command line arguments."
+ :tag "Electric forward slash"
+ :group 'eshell-module))
+
+;;; Functions:
+
+(defun eshell-elecslash-initialize () ;Called from `eshell-mode' via intern-soft!
+ "Initialize remote Eshell electric forward slash support."
+ (add-hook 'post-self-insert-hook
+ #'eshell-electric-forward-slash nil t))
+
+(defun eshell-electric-forward-slash ()
+ "Implementation of electric forward slash in remote Eshells.
+
+Initializing the `eshell-elecslash' module adds this function to
+`post-self-insert-hook'. Typing / or ~/ as the first character
+of a command line argument automatically inserts the Tramp prefix
+in the case that `default-directory' is remote and the command is
+a Lisp function. Typing a second forward slash undoes the
+insertion."
+ (when (eq ?/ (char-before))
+ (delete-char -1)
+ (let ((tilde-before (eq ?~ (char-before)))
+ (command (save-excursion
+ (eshell-bol)
+ (skip-syntax-forward " ")
+ (thing-at-point 'sexp))))
+ (if (and (file-remote-p default-directory)
+ ;; We can't formally parse the input. But if there is
+ ;; one of these operators behind us, then looking at
+ ;; the first command would not be sensible. So be
+ ;; conservative: don't insert the Tramp prefix if there
+ ;; are any of these operators behind us.
+ (not (looking-back (regexp-opt '("&&" "|" ";"))
+ eshell-last-output-end))
+ (or (= (point) eshell-last-output-end)
+ (and tilde-before
+ (= (1- (point)) eshell-last-output-end))
+ (and (or tilde-before
+ (eq ?\s (char-syntax (char-before))))
+ (or (eshell-find-alias-function command)
+ (and (fboundp (intern-soft command))
+ (or eshell-prefer-lisp-functions
+ (not (eshell-search-path command))))))))
+ (let ((map (make-sparse-keymap))
+ (start (if tilde-before (1- (point)) (point)))
+ (localname
+ (tramp-file-name-localname
+ (tramp-dissect-file-name default-directory))))
+ (when tilde-before (delete-char -1))
+ (insert
+ (substring default-directory 0
+ (string-search localname default-directory)))
+ (unless tilde-before (insert "/"))
+ ;; Typing a second slash undoes the insertion, for when
+ ;; you really do want to type a local absolute file name.
+ (define-key map "/" (lambda ()
+ (interactive)
+ (delete-region start (point))
+ (insert (if tilde-before "~/" "/"))))
+ (set-transient-map map))
+ (insert "/")))))
+
+(provide 'em-elecslash)
+
+;; Local Variables:
+;; generated-autoload-file: "esh-groups.el"
+;; End:
+
+;;; esh-elecslash.el ends here
diff --git a/lisp/eshell/em-extpipe.el b/lisp/eshell/em-extpipe.el
new file mode 100644
index 00000000000..3db1dea5955
--- /dev/null
+++ b/lisp/eshell/em-extpipe.el
@@ -0,0 +1,204 @@
+;;; em-extpipe.el --- external shell pipelines -*- lexical-binding:t -*-
+
+;; Copyright (C) 2022 Free Software Foundation, Inc.
+
+;; Author: Sean Whitton <spwhitton@spwhitton.name>
+
+;; 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:
+
+;; When constructing shell pipelines that will move a lot of data, it
+;; is a good idea to bypass Eshell's own pipelining support and use
+;; the operating system shell's instead. This module tries to make
+;; that easy to do.
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'esh-arg)
+(require 'esh-cmd)
+(require 'esh-io)
+(require 'esh-util)
+
+(eval-when-compile (require 'files-x))
+
+;;; Functions:
+
+(defun eshell-extpipe-initialize () ;Called from `eshell-mode' via intern-soft!
+ "Initialize external pipelines support."
+ (when (boundp 'eshell-special-chars-outside-quoting)
+ (setq-local
+ eshell-special-chars-outside-quoting
+ (append eshell-special-chars-outside-quoting (list ?\*))))
+ (add-hook 'eshell-parse-argument-hook
+ #'eshell-parse-external-pipeline -20 t)
+ (add-hook 'eshell-pre-rewrite-command-hook
+ #'eshell-rewrite-external-pipeline -20 t))
+
+(defmacro em-extpipe--or-with-catch (&rest disjuncts)
+ "Evaluate DISJUNCTS like `or' but catch `eshell-incomplete'.
+
+If `eshell-incomplete' is thrown during the evaluation of a
+disjunct, that disjunct yields nil."
+ (let ((result (gensym)))
+ `(let (,result)
+ (or ,@(cl-loop for disjunct in disjuncts collect
+ `(if (catch 'eshell-incomplete
+ (ignore (setq ,result ,disjunct)))
+ nil
+ ,result))))))
+
+(defun eshell-parse-external-pipeline ()
+ "Parse a pipeline intended for execution by the external shell.
+
+A sequence of arguments is rewritten to use the operating system
+shell when it contains `*|', `*<' or `*>', where the asterisk is
+preceded by whitespace or located at the start of input.
+
+The command extends to the next `|' character which is not
+preceded by an unescaped asterisk following whitespace, or the
+end of input, except that any Eshell-specific output redirections
+occurring at the end are excluded. Any other `<' or `>'
+appearing before the end of the command are treated as though
+preceded by (whitespace and) an asterisk.
+
+For example,
+
+ foo <bar *| baz >#<buffer quux>
+
+is equivalent to
+
+ sh -c \"foo <bar | baz\" >#<buffer quux>
+
+when `shell-file-name' is `sh' and `shell-command-switch' is
+`-c', but in
+
+ foo >#<buffer quux> *| baz
+
+and
+
+ foo *| baz >#<buffer quux> --some-argument
+
+the Eshell-specific redirect will be passed on to the operating
+system shell, probably leading to undesired results.
+
+This function must appear early in `eshell-parse-argument-hook'
+to ensure that operating system shell syntax is not interpreted
+as though it were Eshell syntax."
+ ;; Our goal is to wrap the external command to protect it from the
+ ;; other members of `eshell-parse-argument-hook'. We must avoid
+ ;; misinterpreting a quoted `*|', `*<' or `*>' as indicating an
+ ;; external pipeline, hence the structure of the loop in `findbeg1'.
+ (cl-flet
+ ((findbeg1 (pat &optional go (bound (point-max)))
+ (let* ((start (point))
+ (result
+ (catch 'found
+ (while (> bound (point))
+ (let* ((found
+ (save-excursion
+ (re-search-forward
+ "\\(?:#?'\\|\"\\|\\\\\\)" bound t)))
+ (next (or (and found (match-beginning 0))
+ bound)))
+ (if (re-search-forward pat next t)
+ (throw 'found (match-beginning 1))
+ (goto-char next)
+ (while (em-extpipe--or-with-catch
+ (eshell-parse-lisp-argument)
+ (eshell-parse-backslash)
+ (eshell-parse-double-quote)
+ (eshell-parse-literal-quote)))
+ ;; Guard against an infinite loop if none of
+ ;; the parsers moved us forward.
+ (unless (or (> (point) next) (eobp))
+ (forward-char 1))))))))
+ (goto-char (if (and result go) (match-end 0) start))
+ result)))
+ (unless (or eshell-current-argument eshell-current-quoted)
+ (let ((beg (point)) end
+ (next-marked (findbeg1 "\\(?:\\=\\|\\s-\\)\\(\\*[|<>]\\)"))
+ (next-unmarked
+ (or (findbeg1 "\\(?:\\=\\|[^*]\\|\\S-\\*\\)\\(|\\)")
+ (point-max))))
+ (when (and next-marked (> next-unmarked next-marked)
+ (or (> next-marked (point))
+ (looking-back "\\`\\|\\s-" nil)))
+ ;; Skip to the final segment of the external pipeline.
+ (while (findbeg1 "\\(?:\\=\\|\\s-\\)\\(\\*|\\)" t))
+ ;; Find output redirections.
+ (while (findbeg1
+ "\\([0-9]?>+&?[0-9]?\\s-*\\S-\\)" t next-unmarked)
+ ;; Is the output redirection Eshell-specific? We have our
+ ;; own logic, rather than calling `eshell-parse-argument',
+ ;; to avoid specifying here all the possible cars of
+ ;; parsed special references -- `get-buffer-create' etc.
+ (forward-char -1)
+ (let ((this-end
+ (save-match-data
+ (cond ((looking-at "#<")
+ (forward-char 1)
+ (1+ (eshell-find-delimiter ?\< ?\>)))
+ ((and (looking-at "/\\S-+")
+ (assoc (match-string 0)
+ eshell-virtual-targets))
+ (match-end 0))))))
+ (cond ((and this-end end)
+ (goto-char this-end))
+ (this-end
+ (goto-char this-end)
+ (setq end (match-beginning 0)))
+ (t
+ (setq end nil)))))
+ ;; We've moved past all Eshell-specific output redirections
+ ;; we could find. If there is only whitespace left, then
+ ;; `end' is right before redirections we should exclude;
+ ;; otherwise, we must include everything.
+ (unless (and end (skip-syntax-forward "\s" next-unmarked)
+ (= next-unmarked (point)))
+ (setq end next-unmarked))
+ (let ((cmd (string-trim
+ (buffer-substring-no-properties beg end))))
+ (goto-char end)
+ ;; We must now drop the asterisks, unless quoted/escaped.
+ (with-temp-buffer
+ (insert cmd)
+ (goto-char (point-min))
+ (cl-loop
+ for next = (findbeg1 "\\(?:\\=\\|\\s-\\)\\(\\*[|<>]\\)" t)
+ while next do (forward-char -2) (delete-char 1))
+ (eshell-finish-arg
+ `(eshell-external-pipeline ,(buffer-string))))))))))
+
+(defun eshell-rewrite-external-pipeline (terms)
+ "Rewrite an external pipeline in TERMS as parsed by
+`eshell-parse-external-pipeline', which see."
+ (while terms
+ (when (and (listp (car terms))
+ (eq (caar terms) 'eshell-external-pipeline))
+ (with-connection-local-variables
+ (setcdr terms (cl-list*
+ shell-command-switch (cadar terms) (cdr terms)))
+ (setcar terms shell-file-name)))
+ (setq terms (cdr terms))))
+
+(defsubst eshell-external-pipeline (&rest _args)
+ "Stub to generate an error if a pipeline is not rewritten."
+ (error "Unhandled external pipeline in input text"))
+
+(provide 'em-extpipe)
+;;; esh-extpipe.el ends here
diff --git a/lisp/eshell/em-glob.el b/lisp/eshell/em-glob.el
index 842f27a4920..52531ff8939 100644
--- a/lisp/eshell/em-glob.el
+++ b/lisp/eshell/em-glob.el
@@ -233,7 +233,10 @@ resulting regular expression."
"\\'")))
(defun eshell-extended-glob (glob)
- "Return a list of files generated from GLOB, perhaps looking for DIRS-ONLY.
+ "Return a list of files matched by GLOB.
+If no files match, signal an error (if `eshell-error-if-no-glob'
+is non-nil), or otherwise return GLOB itself.
+
This function almost fully supports zsh style filename generation
syntax. Things that are not supported are:
@@ -243,12 +246,7 @@ syntax. Things that are not supported are:
foo~x(a|b) (a|b) will be interpreted as a predicate/modifier list
Mainly they are not supported because file matching is done with Emacs
-regular expressions, and these cannot support the above constructs.
-
-If this routine fails, it returns nil. Otherwise, it returns a list
-the form:
-
- (INCLUDE-REGEXP EXCLUDE-REGEXP (PRED-FUNC-LIST) (MOD-FUNC-LIST))"
+regular expressions, and these cannot support the above constructs."
(let ((paths (eshell-split-path glob))
eshell-glob-matches message-shown)
(unwind-protect
@@ -287,7 +285,7 @@ the form:
glob (car globs)
len (length glob)))))
(if (and recurse-p (not glob))
- (error "`**' cannot end a globbing pattern"))
+ (error "`**/' cannot end a globbing pattern"))
(let ((index 1))
(setq incl glob)
(while (and (eq incl glob)
diff --git a/lisp/eshell/em-hist.el b/lisp/eshell/em-hist.el
index 49b811eae37..1877749c5cf 100644
--- a/lisp/eshell/em-hist.el
+++ b/lisp/eshell/em-hist.el
@@ -104,7 +104,7 @@ in bash, and any other non-nil value mirrors the \"ignoredups\"
value."
:type '(choice (const :tag "Don't ignore anything" nil)
(const :tag "Ignore consecutive duplicates" t)
- (const :tag "Only keep last duplicate" 'erase)))
+ (const :tag "Only keep last duplicate" erase)))
(defcustom eshell-save-history-on-exit t
"Determine if history should be automatically saved.
@@ -125,16 +125,34 @@ the input history list. Default is to save anything that isn't all
whitespace."
:type '(radio (function-item eshell-input-filter-default)
(function-item eshell-input-filter-initial-space)
- (function :tag "Other function")))
-
-(put 'eshell-input-filter 'risky-local-variable t)
+ (function :tag "Other function"))
+ :risky t)
+
+(defun eshell-hist--update-keymap (symbol value)
+ "Update `eshell-hist-mode-map' for `eshell-hist-match-partial'."
+ ;; Don't try to set this before it is bound. See below.
+ (when (and (boundp 'eshell-hist-mode-map)
+ (eq symbol 'eshell-hist-match-partial))
+ (dolist (keyb
+ (if value
+ `(("M-p" . ,#'eshell-previous-matching-input-from-input)
+ ("M-n" . ,#'eshell-next-matching-input-from-input)
+ ("C-c M-p" . ,#'eshell-previous-input)
+ ("C-c M-n" . ,#'eshell-next-input))
+ `(("M-p" . ,#'eshell-previous-input)
+ ("M-n" . ,#'eshell-next-input)
+ ("C-c M-p" . ,#'eshell-previous-matching-input-from-input)
+ ("C-c M-n" . ,#'eshell-next-matching-input-from-input))))
+ (keymap-set eshell-hist-mode-map (car keyb) (cdr keyb))))
+ (set-default symbol value))
(defcustom eshell-hist-match-partial t
"If non-nil, movement through history is constrained by current input.
-Otherwise, typing <M-p> and <M-n> will always go to the next history
+Otherwise, typing \\`M-p' and \\`M-n' will always go to the next history
element, regardless of any text on the command line. In that case,
-<C-c M-r> and <C-c M-s> still offer that functionality."
- :type 'boolean)
+\\`C-c M-r' and \\`C-c M-s' still offer that functionality."
+ :type 'boolean
+ :set 'eshell-hist--update-keymap)
(defcustom eshell-hist-move-to-end t
"If non-nil, move to the end of the buffer before cycling history."
@@ -180,43 +198,31 @@ element, regardless of any text on the command line. In that case,
(defvar eshell-matching-input-from-input-string "")
(defvar eshell-save-history-index nil)
-(defvar eshell-isearch-map
- (let ((map (copy-keymap isearch-mode-map)))
- (define-key map [(control ?m)] 'eshell-isearch-return)
- (define-key map [(control ?r)] 'eshell-isearch-repeat-backward)
- (define-key map [(control ?s)] 'eshell-isearch-repeat-forward)
- (define-key map [(control ?g)] 'eshell-isearch-abort)
- (define-key map [backspace] 'eshell-isearch-delete-char)
- (define-key map [delete] 'eshell-isearch-delete-char)
- (define-key map "\C-c\C-c" 'eshell-isearch-cancel)
- map)
- "Keymap used in isearch in Eshell.")
-
-(defvar eshell-hist-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map [up] #'eshell-previous-matching-input-from-input)
- (define-key map [down] #'eshell-next-matching-input-from-input)
- (define-key map [(control up)] #'eshell-previous-input)
- (define-key map [(control down)] #'eshell-next-input)
- (define-key map [(meta ?r)] #'eshell-previous-matching-input)
- (define-key map [(meta ?s)] #'eshell-next-matching-input)
- (define-key map (kbd "C-c M-r") #'eshell-previous-matching-input-from-input)
- (define-key map (kbd "C-c M-s") #'eshell-next-matching-input-from-input)
- ;; FIXME: Relies on `eshell-hist-match-partial' being set _before_
- ;; em-hist is loaded and won't respect changes.
- (if eshell-hist-match-partial
- (progn
- (define-key map [(meta ?p)] 'eshell-previous-matching-input-from-input)
- (define-key map [(meta ?n)] 'eshell-next-matching-input-from-input)
- (define-key map (kbd "C-c M-p") #'eshell-previous-input)
- (define-key map (kbd "C-c M-n") #'eshell-next-input))
- (define-key map [(meta ?p)] #'eshell-previous-input)
- (define-key map [(meta ?n)] #'eshell-next-input)
- (define-key map (kbd "C-c M-p") #'eshell-previous-matching-input-from-input)
- (define-key map (kbd "C-c M-n") #'eshell-next-matching-input-from-input))
- (define-key map (kbd "C-c C-l") #'eshell-list-history)
- (define-key map (kbd "C-c C-x") #'eshell-get-next-from-history)
- map))
+(defvar-keymap eshell-isearch-map
+ :doc "Keymap used in isearch in Eshell."
+ :parent isearch-mode-map
+ "C-m" #'eshell-isearch-return
+ "C-r" #'eshell-isearch-repeat-backward
+ "C-s" #'eshell-isearch-repeat-forward
+ "C-g" #'eshell-isearch-abort
+ "<backspace>" #'eshell-isearch-delete-char
+ "<delete>" #'eshell-isearch-delete-char
+ "C-c C-c" #'eshell-isearch-cancel)
+
+(defvar-keymap eshell-hist-mode-map
+ "<up>" #'eshell-previous-matching-input-from-input
+ "<down>" #'eshell-next-matching-input-from-input
+ "C-<up>" #'eshell-previous-input
+ "C-<down>" #'eshell-next-input
+ "M-r" #'eshell-previous-matching-input
+ "M-s" #'eshell-next-matching-input
+ "C-c M-r" #'eshell-previous-matching-input-from-input
+ "C-c M-s" #'eshell-next-matching-input-from-input
+ "C-c C-l" #'eshell-list-history
+ "C-c C-x" #'eshell-get-next-from-history)
+;; Update `eshell-hist-mode-map' for `eshell-hist-match-partial'.
+(eshell-hist--update-keymap 'eshell-hist-match-partial
+ eshell-hist-match-partial)
(defvar eshell-rebind-keys-alist)
@@ -335,7 +341,7 @@ unless a different file is specified on the command line.")
(error "No history"))
(let (length file)
(when (and args (string-match "^[0-9]+$" (car args)))
- (setq length (min (eshell-convert (car args))
+ (setq length (min (string-to-number (car args))
(ring-length eshell-history-ring))
args (cdr args)))
(and length
diff --git a/lisp/eshell/em-ls.el b/lisp/eshell/em-ls.el
index 846f3d5e290..874591d2501 100644
--- a/lisp/eshell/em-ls.el
+++ b/lisp/eshell/em-ls.el
@@ -100,15 +100,14 @@ faster and conserves more memory."
:type 'boolean)
(defface eshell-ls-directory
- '((((class color) (background light)) (:foreground "Blue" :weight bold))
- (((class color) (background dark)) (:foreground "SkyBlue" :weight bold))
- (t (:weight bold)))
- "The face used for highlighting directories.")
+ '((t (:inherit font-lock-function-name-face)))
+ "The face used for highlighting directories."
+ :version "29.1")
(defface eshell-ls-symlink
- '((((class color) (background light)) (:foreground "Dark Cyan" :weight bold))
- (((class color) (background dark)) (:foreground "Cyan" :weight bold)))
- "The face used for highlighting symbolic links.")
+ '((t (:inherit font-lock-keyword-face)))
+ "The face used for highlighting symbolic links."
+ :version "29.1")
(defface eshell-ls-executable
'((((class color) (background light)) (:foreground "ForestGreen" :weight bold))
diff --git a/lisp/eshell/em-pred.el b/lisp/eshell/em-pred.el
index 4f4e85c1a69..d73976d3464 100644
--- a/lisp/eshell/em-pred.el
+++ b/lisp/eshell/em-pred.el
@@ -68,7 +68,7 @@ ordinary strings."
(defcustom eshell-predicate-alist
'((?/ . (eshell-pred-file-type ?d)) ; directories
(?. . (eshell-pred-file-type ?-)) ; regular files
- (?s . (eshell-pred-file-type ?s)) ; sockets
+ (?= . (eshell-pred-file-type ?s)) ; sockets
(?p . (eshell-pred-file-type ?p)) ; named pipes
(?@ . (eshell-pred-file-type ?l)) ; symbolic links
(?% . (eshell-pred-file-type ?%)) ; allow user to specify (c def.)
@@ -88,17 +88,17 @@ ordinary strings."
(if (file-exists-p file)
(= (file-attribute-user-id (file-attributes file))
(user-uid)))))
- ;; (?G . (lambda (file) ; owned by effective gid
- ;; (if (file-exists-p file)
- ;; (= (file-attribute-user-id (file-attributes file))
- ;; (user-uid)))))
+ (?G . (lambda (file) ; owned by effective gid
+ (if (file-exists-p file)
+ (= (file-attribute-group-id (file-attributes file))
+ (group-gid)))))
(?* . (lambda (file)
(and (file-regular-p file)
(not (file-symlink-p file))
(file-executable-p file))))
(?l . (eshell-pred-file-links))
- (?u . (eshell-pred-user-or-group ?u "user" 2 'eshell-user-id))
- (?g . (eshell-pred-user-or-group ?g "group" 3 'eshell-group-id))
+ (?u . (eshell-pred-user-or-group ?u "user" 2 #'eshell-user-id))
+ (?g . (eshell-pred-user-or-group ?g "group" 3 #'eshell-group-id))
(?a . (eshell-pred-file-time ?a "access" 4))
(?m . (eshell-pred-file-time ?m "modification" 5))
(?c . (eshell-pred-file-time ?c "change" 6))
@@ -107,33 +107,27 @@ ordinary strings."
The format of each entry is
(CHAR . PREDICATE-FUNC-SEXP)"
- :type '(repeat (cons character sexp)))
-
-(put 'eshell-predicate-alist 'risky-local-variable t)
+ :type '(repeat (cons character sexp))
+ :risky t)
(defcustom eshell-modifier-alist
- '((?E . (lambda (lst)
- (mapcar
- (lambda (str)
- (eshell-stringify
- (car (eshell-parse-argument str))))
- lst)))
+ '((?E . (lambda (lst) (mapcar #'eshell-eval-argument lst)))
(?L . (lambda (lst) (mapcar #'downcase lst)))
(?U . (lambda (lst) (mapcar #'upcase lst)))
(?C . (lambda (lst) (mapcar #'capitalize lst)))
(?h . (lambda (lst) (mapcar #'file-name-directory lst)))
- (?i . (eshell-include-members))
- (?x . (eshell-include-members t))
+ (?i . (eshell-include-members ?i))
+ (?x . (eshell-include-members ?x t))
(?r . (lambda (lst) (mapcar #'file-name-sans-extension lst)))
(?e . (lambda (lst) (mapcar #'file-name-extension lst)))
(?t . (lambda (lst) (mapcar #'file-name-nondirectory lst)))
(?q . (lambda (lst) (mapcar #'eshell-escape-arg lst)))
(?u . (lambda (lst) (seq-uniq lst)))
(?o . (lambda (lst) (sort lst #'string-lessp)))
- (?O . (lambda (lst) (nreverse (sort lst #'string-lessp))))
+ (?O . (lambda (lst) (sort lst #'string-greaterp)))
(?j . (eshell-join-members))
(?S . (eshell-split-members))
- (?R . 'reverse)
+ (?R . #'reverse)
(?g . (progn
(forward-char)
(if (eq (char-before) ?s)
@@ -143,10 +137,9 @@ The format of each entry is
"A list of modifiers than can be applied to an argument expansion.
The format of each entry is
- (CHAR ENTRYWISE-P MODIFIER-FUNC-SEXP)"
- :type '(repeat (cons character sexp)))
-
-(put 'eshell-modifier-alist 'risky-local-variable t)
+ (CHAR . MODIFIER-FUNC-SEXP)"
+ :type '(repeat (cons character sexp))
+ :risky t)
(defvar eshell-predicate-help-string
"Eshell predicate quick reference:
@@ -168,6 +161,7 @@ PERMISSION BITS (for owner/group/world):
OWNERSHIP:
U owned by effective uid
+ G owned by effective gid
u(UID|\\='user\\=') owned by UID/user
g(GID|\\='group\\=') owned by GID/group
@@ -219,17 +213,29 @@ FOR LISTS OF ARGUMENTS:
i/PAT/ exclude all members not matching PAT
x/PAT/ exclude all members matching PAT
- s/pat/match/ substitute PAT with MATCH
- g/pat/match/ substitute PAT with MATCH for all occurrences
+ s/pat/match/ substitute PAT with MATCH
+ gs/pat/match/ substitute PAT with MATCH for all occurrences
EXAMPLES:
*.c(:o) sorted list of .c files")
-(defvar eshell-pred-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map (kbd "C-c M-q") #'eshell-display-predicate-help)
- (define-key map (kbd "C-c M-m") #'eshell-display-modifier-help)
- map))
+(defvar eshell-pred-delimiter-pairs
+ '((?\( . ?\))
+ (?\[ . ?\])
+ (?\< . ?\>)
+ (?\{ . ?\})
+ (?\' . ?\')
+ (?\" . ?\")
+ (?/ . ?/)
+ (?| . ?|))
+ "A list of delimiter pairs that can be used in argument predicates/modifiers.
+Each element is of the form (OPEN . CLOSE), where OPEN and CLOSE
+are characters representing the opening and closing delimiter,
+respectively.")
+
+(defvar-keymap eshell-pred-mode-map
+ "C-c M-q" #'eshell-display-predicate-help
+ "C-c M-m" #'eshell-display-modifier-help)
;;; Functions:
@@ -372,38 +378,70 @@ resultant list of strings."
(lambda (file) (funcall pred (file-truename file))))))
(cons pred funcs))
+(defun eshell-get-comparison-modifier-argument (&optional functions)
+ "Starting at point, get the comparison modifier argument, if any.
+These are the -/+ characters, corresponding to `<' and `>',
+respectively. If no comparison modifier is at point, return `='.
+
+FUNCTIONS, if non-nil, is a list of comparison functions,
+specified as (LESS-THAN GREATER-THAN EQUAL-TO)."
+ (let ((functions (or functions (list #'< #'> #'=))))
+ (if (memq (char-after) '(?- ?+))
+ (prog1
+ (if (eq (char-after) ?-) (nth 0 functions) (nth 1 functions))
+ (forward-char))
+ (nth 2 functions))))
+
+(defun eshell-get-numeric-modifier-argument ()
+ "Starting at point, get the numeric modifier argument, if any.
+If a number is found, update point to just after the number."
+ (when (looking-at "[0-9]+")
+ (prog1
+ (string-to-number (match-string 0))
+ (goto-char (match-end 0)))))
+
+(defun eshell-get-delimited-modifier-argument (&optional chained-p)
+ "Starting at point, get the delimited modifier argument, if any.
+If the character after point is a predicate/modifier
+delimiter (see `eshell-pred-delimiter-pairs', read the value of
+the argument and update point to be just after the closing
+delimiter.
+
+If CHAINED-P is true, then another delimited modifier argument
+will immediately follow this one. In this case, when the opening
+and closing delimiters are the same, update point to be just
+before the closing delimiter. This allows modifiers like
+`:s/match/repl' to work as expected."
+ (when-let* ((open (char-after))
+ (close (cdr (assoc open eshell-pred-delimiter-pairs)))
+ (end (eshell-find-delimiter open close nil nil t)))
+ (prog1
+ (replace-regexp-in-string
+ (rx-to-string `(seq "\\" (group (or "\\" ,open ,close)))) "\\1"
+ (buffer-substring-no-properties (1+ (point)) end))
+ (goto-char (if (and chained-p (eq open close))
+ end
+ (1+ end))))))
+
(defun eshell-pred-user-or-group (mod-char mod-type attr-index get-id-func)
"Return a predicate to test whether a file match a given user/group id."
- (let (ugid open close end)
- (if (looking-at "[0-9]+")
- (progn
- (setq ugid (string-to-number (match-string 0)))
- (goto-char (match-end 0)))
- (setq open (char-after))
- (if (setq close (memq open '(?\( ?\[ ?\< ?\{)))
- (setq close (car (last '(?\) ?\] ?\> ?\})
- (length close))))
- (setq close open))
- (forward-char)
- (setq end (eshell-find-delimiter open close))
- (unless end
- (error "Malformed %s name string for modifier `%c'"
- mod-type mod-char))
- (setq ugid
- (funcall get-id-func (buffer-substring (point) end)))
- (goto-char (1+ end)))
+ (let ((ugid (eshell-get-numeric-modifier-argument)))
+ (unless ugid
+ (let ((ugname (or (eshell-get-delimited-modifier-argument)
+ (error "Malformed %s name string for modifier `%c'"
+ mod-type mod-char))))
+ (setq ugid (funcall get-id-func ugname))))
(unless ugid
(error "Unknown %s name specified for modifier `%c'"
mod-type mod-char))
(lambda (file)
- (let ((attrs (file-attributes file)))
- (if attrs
- (= (nth attr-index attrs) ugid))))))
+ (when-let ((attrs (file-attributes file)))
+ (= (nth attr-index attrs) ugid)))))
(defun eshell-pred-file-time (mod-char mod-type attr-index)
"Return a predicate to test whether a file matches a certain time."
(let* ((quantum 86400)
- qual when open close end)
+ qual when)
(when (memq (char-after) '(?M ?w ?h ?m ?s))
(setq quantum (char-after))
(cond
@@ -418,36 +456,21 @@ resultant list of strings."
((eq quantum ?s)
(setq quantum 1)))
(forward-char))
- (when (memq (char-after) '(?+ ?-))
- (setq qual (char-after))
- (forward-char))
- (if (looking-at "[0-9]+")
- (progn
- (setq when (time-since (* (string-to-number (match-string 0))
- quantum)))
- (goto-char (match-end 0)))
- (setq open (char-after))
- (if (setq close (memq open '(?\( ?\[ ?\< ?\{)))
- (setq close (car (last '(?\) ?\] ?\> ?\})
- (length close))))
- (setq close open))
- (forward-char)
- (setq end (eshell-find-delimiter open close))
- (unless end
- (error "Malformed %s time modifier `%c'" mod-type mod-char))
- (let* ((file (buffer-substring (point) end))
- (attrs (file-attributes file)))
- (unless attrs
- (error "Cannot stat file `%s'" file))
- (setq when (nth attr-index attrs)))
- (goto-char (1+ end)))
- (let ((f (cond ((eq qual ?-) #'time-less-p)
- ((eq qual ?+) (lambda (a b) (time-less-p b a)))
- (#'time-equal-p))))
- (lambda (file)
- (let ((attrs (file-attributes file)))
- (if attrs
- (funcall f when (nth attr-index attrs))))))))
+ (setq qual (eshell-get-comparison-modifier-argument
+ (list #'time-less-p
+ (lambda (a b) (time-less-p b a))
+ #'time-equal-p)))
+ (if-let ((number (eshell-get-numeric-modifier-argument)))
+ (setq when (time-since (* number quantum)))
+ (let* ((file (or (eshell-get-delimited-modifier-argument)
+ (error "Malformed %s time modifier `%c'"
+ mod-type mod-char)))
+ (attrs (or (file-attributes file)
+ (error "Cannot stat file `%s'" file))))
+ (setq when (nth attr-index attrs))))
+ (lambda (file)
+ (when-let ((attrs (file-attributes file)))
+ (funcall qual when (nth attr-index attrs))))))
(defun eshell-pred-file-type (type)
"Return a test which tests that the file is of a certain TYPE.
@@ -462,36 +485,23 @@ that `ls -l' will show in the first column of its display."
'(?b ?c)
(list type))))
(lambda (file)
- (let ((attrs (eshell-file-attributes (directory-file-name file))))
- (if attrs
- (memq (aref (file-attribute-modes attrs) 0) set))))))
+ (when-let ((attrs (eshell-file-attributes (directory-file-name file))))
+ (memq (aref (file-attribute-modes attrs) 0) set)))))
(defsubst eshell-pred-file-mode (mode)
"Return a test which tests that MODE pertains to the file."
(lambda (file)
- (let ((modes (file-modes file 'nofollow)))
- (if modes
- (not (zerop (logand mode modes)))))))
+ (when-let ((modes (file-modes file 'nofollow)))
+ (not (zerop (logand mode modes))))))
(defun eshell-pred-file-links ()
"Return a predicate to test whether a file has a given number of links."
- (let (qual amount)
- (when (memq (char-after) '(?- ?+))
- (setq qual (char-after))
- (forward-char))
- (unless (looking-at "[0-9]+")
- (error "Invalid file link count modifier `l'"))
- (setq amount (string-to-number (match-string 0)))
- (goto-char (match-end 0))
- (let ((f (if (eq qual ?-)
- #'<
- (if (eq qual ?+)
- #'>
- #'=))))
- (lambda (file)
- (let ((attrs (eshell-file-attributes file)))
- (if attrs
- (funcall f (file-attribute-link-number attrs) amount)))))))
+ (let ((qual (eshell-get-comparison-modifier-argument))
+ (amount (or (eshell-get-numeric-modifier-argument)
+ (error "Invalid file link count modifier `l'"))))
+ (lambda (file)
+ (when-let ((attrs (eshell-file-attributes file)))
+ (funcall qual (file-attribute-link-number attrs) amount)))))
(defun eshell-pred-file-size ()
"Return a predicate to test whether a file is of a given size."
@@ -506,89 +516,52 @@ that `ls -l' will show in the first column of its display."
((eq qual ?p)
(setq quantum 512)))
(forward-char))
- (when (memq (char-after) '(?- ?+))
- (setq qual (char-after))
- (forward-char))
- (unless (looking-at "[0-9]+")
- (error "Invalid file size modifier `L'"))
- (setq amount (* (string-to-number (match-string 0)) quantum))
- (goto-char (match-end 0))
- (let ((f (if (eq qual ?-)
- #'<
- (if (eq qual ?+)
- #'>
- #'=))))
- (lambda (file)
- (let ((attrs (eshell-file-attributes file)))
- (if attrs
- (funcall f (file-attribute-size attrs) amount)))))))
+ (setq qual (eshell-get-comparison-modifier-argument))
+ (setq amount (* (or (eshell-get-numeric-modifier-argument)
+ (error "Invalid file size modifier `L'"))
+ quantum))
+ (lambda (file)
+ (when-let ((attrs (eshell-file-attributes file)))
+ (funcall qual (file-attribute-size attrs) amount)))))
(defun eshell-pred-substitute (&optional repeat)
"Return a modifier function that will substitute matches."
- (let ((delim (char-after))
- match replace end)
- (forward-char)
- (setq end (eshell-find-delimiter delim delim nil nil t)
- match (buffer-substring-no-properties (point) end))
- (goto-char (1+ end))
- (setq end (eshell-find-delimiter delim delim nil nil t)
- replace (buffer-substring-no-properties (point) end))
- (goto-char (1+ end))
- (if repeat
- (lambda (lst)
- (mapcar
- (lambda (str)
- (let ((i 0))
- (while (setq i (string-match match str i))
- (setq str (replace-match replace t nil str))))
- str)
- lst))
- (lambda (lst)
- (mapcar
- (lambda (str)
- (if (string-match match str)
- (setq str (replace-match replace t nil str))
- (error (concat str ": substitution failed")))
- str)
- lst)))))
-
-(defun eshell-include-members (&optional invert-p)
- "Include only Lisp members matching a regexp."
- (let ((delim (char-after))
- regexp end)
- (forward-char)
- (setq end (eshell-find-delimiter delim delim nil nil t)
- regexp (buffer-substring-no-properties (point) end))
- (goto-char (1+ end))
- (let ((predicates
- (list (if invert-p
- (lambda (elem) (not (string-match regexp elem)))
- (lambda (elem) (string-match regexp elem))))))
- (lambda (lst)
- (eshell-winnow-list lst nil predicates)))))
+ (let* ((match (or (eshell-get-delimited-modifier-argument t)
+ (error "Malformed pattern string for modifier `s'")))
+ (replace (or (eshell-get-delimited-modifier-argument)
+ (error "Malformed replace string for modifier `s'")))
+ (function (if repeat
+ (lambda (str)
+ (replace-regexp-in-string match replace str t))
+ (lambda (str)
+ (if (string-match match str)
+ (replace-match replace t nil str)
+ (error (concat str ": substitution failed")))))))
+ (lambda (lst) (mapcar function lst))))
+
+(defun eshell-include-members (mod-char &optional invert-p)
+ "Include only Lisp members matching a regexp.
+If INVERT-P is non-nil, include only members not matching a regexp."
+ (let* ((regexp (or (eshell-get-delimited-modifier-argument)
+ (error "Malformed pattern string for modifier `%c'"
+ mod-char)))
+ (predicates
+ (list (if invert-p
+ (lambda (elem) (not (string-match regexp elem)))
+ (lambda (elem) (string-match regexp elem))))))
+ (lambda (lst)
+ (eshell-winnow-list lst nil predicates))))
(defun eshell-join-members ()
"Return a modifier function that join matches."
- (let ((delim (char-after))
- str end)
- (if (not (memq delim '(?' ?/)))
- (setq delim " ")
- (forward-char)
- (setq end (eshell-find-delimiter delim delim nil nil t)
- str (buffer-substring-no-properties (point) end))
- (goto-char (1+ end)))
+ (let ((str (or (eshell-get-delimited-modifier-argument)
+ " ")))
(lambda (lst)
(mapconcat #'identity lst str))))
(defun eshell-split-members ()
"Return a modifier function that splits members."
- (let ((delim (char-after))
- sep end)
- (when (memq delim '(?' ?/))
- (forward-char)
- (setq end (eshell-find-delimiter delim delim nil nil t)
- sep (buffer-substring-no-properties (point) end))
- (goto-char (1+ end)))
+ (let ((sep (eshell-get-delimited-modifier-argument)))
(lambda (lst)
(mapcar
(lambda (str)
diff --git a/lisp/eshell/em-prompt.el b/lisp/eshell/em-prompt.el
index 3901265e9d4..a1a91e7d634 100644
--- a/lisp/eshell/em-prompt.el
+++ b/lisp/eshell/em-prompt.el
@@ -96,11 +96,9 @@ arriving, or after."
:options '(eshell-show-maximum-output)
:group 'eshell-prompt)
-(defvar eshell-prompt-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map (kbd "C-c C-n") #'eshell-next-prompt)
- (define-key map (kbd "C-c C-p") #'eshell-previous-prompt)
- map))
+(defvar-keymap eshell-prompt-mode-map
+ "C-c C-n" #'eshell-next-prompt
+ "C-c C-p" #'eshell-previous-prompt)
;;; Functions:
diff --git a/lisp/eshell/em-rebind.el b/lisp/eshell/em-rebind.el
index 1919c87d4da..2b56c9e8444 100644
--- a/lisp/eshell/em-rebind.el
+++ b/lisp/eshell/em-rebind.el
@@ -136,10 +136,8 @@ This is default behavior of shells like bash."
:type '(repeat function)
:group 'eshell-rebind)
-(defvar eshell-rebind-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map (kbd "C-c M-l") #'eshell-lock-local-map)
- map))
+(defvar-keymap eshell-rebind-mode-map
+ "C-c M-l" #'eshell-lock-local-map)
;; Internal Variables:
@@ -240,7 +238,7 @@ lock it at that."
Sends an EOF only if point is at the end of the buffer and there is no
input."
(interactive "p")
- (let ((proc (eshell-interactive-process)))
+ (let ((proc (eshell-head-process)))
(if (eobp)
(cond
((/= (point) eshell-last-output-end)
diff --git a/lisp/eshell/em-script.el b/lisp/eshell/em-script.el
index e8459513f39..e0bcd8b099f 100644
--- a/lisp/eshell/em-script.el
+++ b/lisp/eshell/em-script.el
@@ -113,27 +113,13 @@ Comments begin with `#'."
(defun eshell/source (&rest args)
"Source a file in a subshell environment."
- (eshell-eval-using-options
- "source" args
- '((?h "help" nil nil "show this usage screen")
- :show-usage
- :usage "FILE [ARGS]
-Invoke the Eshell commands in FILE in a subshell, binding ARGS to $1,
-$2, etc.")
- (eshell-source-file (car args) (cdr args) t)))
+ (eshell-source-file (car args) (cdr args) t))
(put 'eshell/source 'eshell-no-numeric-conversions t)
(defun eshell/. (&rest args)
"Source a file in the current environment."
- (eshell-eval-using-options
- "." args
- '((?h "help" nil nil "show this usage screen")
- :show-usage
- :usage "FILE [ARGS]
-Invoke the Eshell commands in FILE within the current shell
-environment, binding ARGS to $1, $2, etc.")
- (eshell-source-file (car args) (cdr args))))
+ (eshell-source-file (car args) (cdr args)))
(put 'eshell/. 'eshell-no-numeric-conversions t)
diff --git a/lisp/eshell/em-term.el b/lisp/eshell/em-term.el
index e34c5ae47ce..d150c07b030 100644
--- a/lisp/eshell/em-term.el
+++ b/lisp/eshell/em-term.el
@@ -224,7 +224,7 @@ the buffer."
; (defun eshell-term-send-raw-string (chars)
; (goto-char eshell-last-output-end)
-; (process-send-string (eshell-interactive-process) chars))
+; (process-send-string (eshell-head-process) chars))
; (defun eshell-term-send-raw ()
; "Send the last character typed through the terminal-emulator
diff --git a/lisp/eshell/em-tramp.el b/lisp/eshell/em-tramp.el
index e9018bdb934..aebbc36e71d 100644
--- a/lisp/eshell/em-tramp.el
+++ b/lisp/eshell/em-tramp.el
@@ -61,37 +61,33 @@
"Alias \"su\" to call TRAMP.
Uses the system su through TRAMP's su method."
- (setq args (eshell-stringify-list (flatten-tree args)))
- (let ((orig-args (copy-tree args)))
- (eshell-eval-using-options
- "su" args
- '((?h "help" nil nil "show this usage screen")
- (?l "login" nil login "provide a login environment")
- (? nil nil login "provide a login environment")
- :usage "[- | -l | --login] [USER]
+ (eshell-eval-using-options
+ "su" args
+ '((?h "help" nil nil "show this usage screen")
+ (?l "login" nil login "provide a login environment")
+ (? nil nil login "provide a login environment")
+ :usage "[- | -l | --login] [USER]
Become another USER during a login session.")
- (throw 'eshell-replace-command
- (let ((user "root")
- (host (or (file-remote-p default-directory 'host)
- "localhost"))
- (dir (file-local-name (expand-file-name default-directory)))
- (prefix (file-remote-p default-directory)))
- (dolist (arg args)
- (if (string-equal arg "-") (setq login t) (setq user arg)))
- ;; `eshell-eval-using-options' does not handle "-".
- (if (member "-" orig-args) (setq login t))
- (if login (setq dir "~/"))
- (if (and prefix
- (or
- (not (string-equal
- "su" (file-remote-p default-directory 'method)))
- (not (string-equal
- user (file-remote-p default-directory 'user)))))
- (eshell-parse-command
- "cd" (list (format "%s|su:%s@%s:%s"
- (substring prefix 0 -1) user host dir)))
- (eshell-parse-command
- "cd" (list (format "/su:%s@%s:%s" user host dir)))))))))
+ (throw 'eshell-replace-command
+ (let ((user "root")
+ (host (or (file-remote-p default-directory 'host)
+ tramp-default-host))
+ (dir (file-local-name (expand-file-name default-directory)))
+ (prefix (file-remote-p default-directory)))
+ (dolist (arg args)
+ (if (string-equal arg "-") (setq login t) (setq user arg)))
+ (when login (setq dir "~/"))
+ (if (and prefix
+ (or
+ (not (string-equal
+ "su" (file-remote-p default-directory 'method)))
+ (not (string-equal
+ user (file-remote-p default-directory 'user)))))
+ (eshell-parse-command
+ "cd" (list (format "%s|su:%s@%s:%s"
+ (substring prefix 0 -1) user host dir)))
+ (eshell-parse-command
+ "cd" (list (format "/su:%s@%s:%s" user host dir))))))))
(put 'eshell/su 'eshell-no-numeric-conversions t)
@@ -99,41 +95,35 @@ Become another USER during a login session.")
"Alias \"sudo\" to call Tramp.
Uses the system sudo through TRAMP's sudo method."
- (setq args (eshell-stringify-list (flatten-tree args)))
- (let ((orig-args (copy-tree args)))
- (eshell-eval-using-options
- "sudo" args
- '((?h "help" nil nil "show this usage screen")
- (?u "user" t user "execute a command as another USER")
- :show-usage
- :parse-leading-options-only
- :usage "[(-u | --user) USER] COMMAND
+ (eshell-eval-using-options
+ "sudo" args
+ '((?h "help" nil nil "show this usage screen")
+ (?u "user" t user "execute a command as another USER")
+ :show-usage
+ :parse-leading-options-only
+ :usage "[(-u | --user) USER] COMMAND
Execute a COMMAND as the superuser or another USER.")
- (throw 'eshell-external
- (let ((user (or user "root"))
- (host (or (file-remote-p default-directory 'host)
- "localhost"))
- (dir (file-local-name (expand-file-name default-directory)))
- (prefix (file-remote-p default-directory)))
- ;; `eshell-eval-using-options' reads options of COMMAND.
- (while (and (stringp (car orig-args))
- (member (car orig-args) '("-u" "--user")))
- (setq orig-args (cddr orig-args)))
- (let ((default-directory
- (if (and prefix
- (or
- (not
- (string-equal
- "sudo"
- (file-remote-p default-directory 'method)))
- (not
- (string-equal
- user
- (file-remote-p default-directory 'user)))))
- (format "%s|sudo:%s@%s:%s"
- (substring prefix 0 -1) user host dir)
- (format "/sudo:%s@%s:%s" user host dir))))
- (eshell-named-command (car orig-args) (cdr orig-args))))))))
+ (throw 'eshell-external
+ (let* ((user (or user "root"))
+ (host (or (file-remote-p default-directory 'host)
+ tramp-default-host))
+ (dir (file-local-name (expand-file-name default-directory)))
+ (prefix (file-remote-p default-directory))
+ (default-directory
+ (if (and prefix
+ (or
+ (not
+ (string-equal
+ "sudo"
+ (file-remote-p default-directory 'method)))
+ (not
+ (string-equal
+ user
+ (file-remote-p default-directory 'user)))))
+ (format "%s|sudo:%s@%s:%s"
+ (substring prefix 0 -1) user host dir)
+ (format "/sudo:%s@%s:%s" user host dir))))
+ (eshell-named-command (car args) (cdr args))))))
(put 'eshell/sudo 'eshell-no-numeric-conversions t)
diff --git a/lisp/eshell/esh-arg.el b/lisp/eshell/esh-arg.el
index 127a46abc39..459487f4358 100644
--- a/lisp/eshell/esh-arg.el
+++ b/lisp/eshell/esh-arg.el
@@ -152,10 +152,8 @@ treated as a literal character."
:type 'hook
:group 'eshell-arg)
-(defvar eshell-arg-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map (kbd "C-c M-b") #'eshell-insert-buffer-name)
- map))
+(defvar-keymap eshell-arg-mode-map
+ "C-c M-b" #'eshell-insert-buffer-name)
;;; Functions:
@@ -182,19 +180,63 @@ treated as a literal character."
(add-text-properties 0 (length string) '(escaped t) string))
string)
+(defun eshell-concat (quoted &rest rest)
+ "Concatenate all the arguments in REST and return the result.
+If QUOTED is nil, the resulting value(s) may be converted to
+numbers (see `eshell-concat-1').
+
+If each argument in REST is a non-list value, the result will be
+a single value, as if (mapconcat #'eshell-stringify REST) had been
+called, possibly converted to a number.
+
+If there is at least one (non-nil) list argument, the result will
+be a list, with \"adjacent\" elements of consecutive arguments
+concatenated as strings (again, possibly converted to numbers).
+For example, concatenating \"a\", (\"b\"), and (\"c\" \"d\")
+would produce (\"abc\" \"d\")."
+ (let (result)
+ (dolist (i rest result)
+ (when i
+ (cond
+ ((null result)
+ (setq result i))
+ ((listp result)
+ (let (curr-head curr-tail)
+ (if (listp i)
+ (setq curr-head (car i)
+ curr-tail (cdr i))
+ (setq curr-head i
+ curr-tail nil))
+ (setq result
+ (append
+ (butlast result 1)
+ (list (eshell-concat-1 quoted (car (last result))
+ curr-head))
+ curr-tail))))
+ ((listp i)
+ (setq result
+ (cons (eshell-concat-1 quoted result (car i))
+ (cdr i))))
+ (t
+ (setq result (eshell-concat-1 quoted result i))))))))
+
+(defun eshell-concat-1 (quoted first second)
+ "Concatenate FIRST and SECOND.
+If QUOTED is nil and either FIRST or SECOND are numbers, try to
+convert the result to a number as well."
+ (let ((result (concat (eshell-stringify first) (eshell-stringify second))))
+ (if (and (not quoted)
+ (or (numberp first) (numberp second)))
+ (eshell-convert-to-number result)
+ result)))
+
(defun eshell-resolve-current-argument ()
"If there are pending modifications to be made, make them now."
(when eshell-current-argument
(when eshell-arg-listified
- (let ((parts eshell-current-argument))
- (while parts
- (unless (stringp (car parts))
- (setcar parts
- (list 'eshell-to-flat-string (car parts))))
- (setq parts (cdr parts)))
- (setq eshell-current-argument
- (list 'eshell-convert
- (append (list 'concat) eshell-current-argument))))
+ (setq eshell-current-argument
+ (append (list 'eshell-concat eshell-current-quoted)
+ eshell-current-argument))
(setq eshell-arg-listified nil))
(while eshell-current-modifiers
(setq eshell-current-argument
@@ -356,6 +398,30 @@ after are both returned."
(list 'eshell-escape-arg arg))))
(goto-char (1+ end)))))))
+(defun eshell-unescape-inner-double-quote (bound)
+ "Unescape escaped characters inside a double-quoted string.
+The string to parse starts at point and ends at BOUND.
+
+If Eshell is currently parsing a quoted string and there are any
+backslash-escaped characters, this will return the unescaped
+string, updating point to BOUND. Otherwise, this returns nil and
+leaves point where it was."
+ (when eshell-current-quoted
+ (let (strings
+ (start (point))
+ (special-char
+ (rx-to-string
+ `(seq "\\" (group (any ,@eshell-special-chars-inside-quoting))))))
+ (while (re-search-forward special-char bound t)
+ (push (concat (buffer-substring start (match-beginning 0))
+ (match-string 1))
+ strings)
+ (setq start (match-end 0)))
+ (when strings
+ (push (buffer-substring start bound) strings)
+ (goto-char bound)
+ (apply #'concat (nreverse strings))))))
+
(defun eshell-parse-special-reference ()
"Parse a special syntax reference, of the form `#<args>'.
@@ -379,7 +445,9 @@ If the form has no `type', the syntax is parsed as if `type' were
(if (eshell-arg-delimiter (1+ end))
(prog1
(list (if buffer-p 'get-buffer-create 'get-process)
- (buffer-substring-no-properties (point) end))
+ (replace-regexp-in-string
+ (rx "\\" (group (or "\\" "<" ">"))) "\\1"
+ (buffer-substring-no-properties (point) end)))
(goto-char (1+ end)))
(ignore (goto-char here)))))))
diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el
index 554e3a5c1d9..775e4c1057e 100644
--- a/lisp/eshell/esh-cmd.el
+++ b/lisp/eshell/esh-cmd.el
@@ -107,6 +107,7 @@
(require 'esh-module)
(require 'esh-io)
(require 'esh-ext)
+(require 'generator)
(eval-when-compile
(require 'cl-lib)
@@ -255,12 +256,12 @@ the command."
(defcustom eshell-subcommand-bindings
'((eshell-in-subcommand-p t)
+ (eshell-in-pipeline-p nil)
(default-directory default-directory)
(process-environment (eshell-copy-environment)))
"A list of `let' bindings for subcommand environments."
- :type 'sexp)
-
-(put 'risky-local-variable 'eshell-subcommand-bindings t)
+ :type 'sexp
+ :risky t)
(defvar eshell-ensure-newline-p nil
"If non-nil, ensure that a newline is emitted after a Lisp form.
@@ -279,14 +280,33 @@ otherwise t.")
(defvar eshell-in-subcommand-p nil)
(defvar eshell-last-arguments nil)
(defvar eshell-last-command-name nil)
-(defvar eshell-last-async-proc nil
- "When this foreground process completes, resume command evaluation.")
+(defvar eshell-last-async-procs nil
+ "The currently-running foreground process(es).
+When executing a pipeline, this is a cons cell whose CAR is the
+first process (usually reading from stdin) and whose CDR is the
+last process (usually writing to stdout). Otherwise, the CAR and
+CDR are the same process.
+
+When the process in the CDR completes, resume command evaluation.")
;;; Functions:
-(defsubst eshell-interactive-process ()
- "Return currently running command process, if non-Lisp."
- eshell-last-async-proc)
+(defsubst eshell-interactive-process-p ()
+ "Return non-nil if there is a currently running command process."
+ eshell-last-async-procs)
+
+(defsubst eshell-head-process ()
+ "Return the currently running process at the head of any pipeline.
+This only returns external (non-Lisp) processes."
+ (car-safe eshell-last-async-procs))
+
+(defsubst eshell-tail-process ()
+ "Return the currently running process at the tail of any pipeline.
+This only returns external (non-Lisp) processes."
+ (cdr-safe eshell-last-async-procs))
+
+(define-obsolete-function-alias 'eshell-interactive-process
+ 'eshell-tail-process "29.1")
(defun eshell-cmd-initialize () ;Called from `eshell-mode' via intern-soft!
"Initialize the Eshell command processing module."
@@ -295,7 +315,7 @@ otherwise t.")
(setq-local eshell-command-arguments nil)
(setq-local eshell-last-arguments nil)
(setq-local eshell-last-command-name nil)
- (setq-local eshell-last-async-proc nil)
+ (setq-local eshell-last-async-procs nil)
(add-hook 'eshell-kill-hook #'eshell-resume-command nil t)
@@ -306,7 +326,7 @@ otherwise t.")
(add-hook 'eshell-post-command-hook
(lambda ()
(setq eshell-current-command nil
- eshell-last-async-proc nil))
+ eshell-last-async-procs nil))
nil t)
(add-hook 'eshell-parse-argument-hook
@@ -331,6 +351,39 @@ otherwise t.")
(defvar eshell--sep-terms)
+(defmacro eshell-with-temp-command (region &rest body)
+ "Narrow the buffer to REGION and execute the forms in BODY.
+
+REGION is a cons cell (START . END) that specifies the region to
+which to narrow the buffer. REGION can also be a string, in
+which case the macro temporarily inserts it into the buffer at
+point, and narrows the buffer to the inserted string. Before
+executing BODY, point is set to the beginning of the narrowed
+REGION.
+
+The value returned is the last form in BODY."
+ (declare (indent 1))
+ `(let ((reg ,region))
+ (if (stringp reg)
+ ;; Since parsing relies partly on buffer-local state
+ ;; (e.g. that of `eshell-parse-argument-hook'), we need to
+ ;; perform the parsing in the Eshell buffer.
+ (let ((begin (point)) end
+ (inhibit-point-motion-hooks t))
+ (with-silent-modifications
+ (insert reg)
+ (setq end (point))
+ (unwind-protect
+ (save-restriction
+ (narrow-to-region begin end)
+ (goto-char begin)
+ ,@body)
+ (delete-region begin end))))
+ (save-restriction
+ (narrow-to-region (car reg) (cdr reg))
+ (goto-char (car reg))
+ ,@body))))
+
(defun eshell-parse-command (command &optional args toplevel)
"Parse the COMMAND, adding ARGS if given.
COMMAND can either be a string, or a cons cell demarcating a buffer
@@ -342,15 +395,9 @@ hooks should be run before and after the command."
(append
(if (consp command)
(eshell-parse-arguments (car command) (cdr command))
- (let ((here (point))
- (inhibit-point-motion-hooks t))
- (with-silent-modifications
- ;; FIXME: Why not use a temporary buffer and avoid this
- ;; "insert&delete" business? --Stef
- (insert command)
- (prog1
- (eshell-parse-arguments here (point))
- (delete-region here (point))))))
+ (eshell-with-temp-command command
+ (goto-char (point-max))
+ (eshell-parse-arguments (point-min) (point-max))))
args))
(commands
(mapcar
@@ -764,8 +811,7 @@ This macro calls itself recursively, with NOTFIRST non-nil."
(eshell-set-output-handle ,eshell-output-handle
'append nextproc)
(eshell-set-output-handle ,eshell-error-handle
- 'append nextproc)
- (setq tailproc (or tailproc nextproc))))
+ 'append nextproc)))
,(let ((head (car pipeline)))
(if (memq (car head) '(let progn))
(setq head (car (last head))))
@@ -781,7 +827,10 @@ This macro calls itself recursively, with NOTFIRST non-nil."
,(cond ((not notfirst) (quote 'first))
((cdr pipeline) t)
(t (quote 'last)))))
- ,(car pipeline))))))
+ (let ((proc ,(car pipeline)))
+ (set headproc (or proc (symbol-value headproc)))
+ (set tailproc (or (symbol-value tailproc) proc))
+ proc))))))
(defmacro eshell-do-pipelines-synchronously (pipeline)
"Execute the commands in PIPELINE in sequence synchronously.
@@ -813,7 +862,7 @@ This is used on systems where async subprocesses are not supported."
(let ((result ,(car pipeline)))
;; tailproc gets the result of the last successful process in
;; the pipeline.
- (setq tailproc (or result tailproc))
+ (set tailproc (or result (symbol-value tailproc)))
,(if (cdr pipeline)
`(eshell-do-pipelines-synchronously (quote ,(cdr pipeline))))
result))))
@@ -822,7 +871,11 @@ This is used on systems where async subprocesses are not supported."
(defmacro eshell-execute-pipeline (pipeline)
"Execute the commands in PIPELINE, connecting each to one another."
- `(let ((eshell-in-pipeline-p t) tailproc)
+ `(let ((eshell-in-pipeline-p t)
+ (headproc (make-symbol "headproc"))
+ (tailproc (make-symbol "tailproc")))
+ (set headproc nil)
+ (set tailproc nil)
(progn
,(if (fboundp 'make-process)
`(eshell-do-pipelines ,pipeline)
@@ -832,7 +885,8 @@ This is used on systems where async subprocesses are not supported."
(car (aref eshell-current-handles
,eshell-error-handle)) nil)))
(eshell-do-pipelines-synchronously ,pipeline)))
- (eshell-process-identity tailproc))))
+ (eshell-process-identity (cons (symbol-value headproc)
+ (symbol-value tailproc))))))
(defmacro eshell-as-subcommand (command)
"Execute COMMAND using a temp buffer.
@@ -854,7 +908,8 @@ This avoids the need to use `let*'."
(defmacro eshell-command-to-value (object)
"Run OBJECT synchronously, returning its result as a string.
Returns a string comprising the output from the command."
- `(let ((value (make-symbol "eshell-temp")))
+ `(let ((value (make-symbol "eshell-temp"))
+ (eshell-in-pipeline-p nil))
(eshell-do-command-to-value ,object)))
;;;_* Iterative evaluation
@@ -904,21 +959,63 @@ at the moment are:
"Completion for the `debug' command."
(while (pcomplete-here '("errors" "commands"))))
+(iter-defun eshell--find-subcommands (haystack)
+ "Recursively search for subcommand forms in HAYSTACK.
+This yields the SUBCOMMANDs when found in forms like
+\"(eshell-as-subcommand SUBCOMMAND)\"."
+ (dolist (elem haystack)
+ (cond
+ ((eq (car-safe elem) 'eshell-as-subcommand)
+ (iter-yield (cdr elem)))
+ ((listp elem)
+ (iter-yield-from (eshell--find-subcommands elem))))))
+
+(defun eshell--invoke-command-directly (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.
+
+* 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)
- (let ((base (cadr (nth 2 (nth 2 (cadr command))))) name)
- (if (and (eq (car base) 'eshell-trap-errors)
- (eq (car (cadr base)) 'eshell-named-command))
- (setq name (cadr (cadr base))))
- (and name (stringp name)
- (not (member name eshell-complex-commands))
- (catch 'simple
- (progn
- (dolist (pred eshell-complex-commands)
- (if (and (functionp pred)
- (funcall pred name))
- (throw 'simple nil)))
- t))
- (eshell-find-alias-function name))))
+ "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)))
+
+(defun eshell-eval-argument (argument)
+ "Evaluate a single Eshell ARGUMENT and return the result."
+ (let* ((form (eshell-with-temp-command argument
+ (eshell-parse-argument)))
+ (result (eshell-do-eval form t)))
+ (cl-assert (eq (car result) 'quote))
+ (cadr result)))
(defun eshell-eval-command (command &optional input)
"Evaluate the given COMMAND iteratively."
@@ -958,24 +1055,24 @@ at the moment are:
(unless (or (not (stringp status))
(string= "stopped" status)
(string-match eshell-reset-signals status))
- (if (eq proc (eshell-interactive-process))
+ (if (eq proc (eshell-tail-process))
(eshell-resume-eval)))))
(defun eshell-resume-eval ()
"Destructively evaluate a form which may need to be deferred."
(eshell-condition-case err
(progn
- (setq eshell-last-async-proc nil)
+ (setq eshell-last-async-procs nil)
(when eshell-current-command
(let* (retval
- (proc (catch 'eshell-defer
+ (procs (catch 'eshell-defer
(ignore
(setq retval
(eshell-do-eval
eshell-current-command))))))
- (if (eshell-processp proc)
- (ignore (setq eshell-last-async-proc proc))
- (cadr retval)))))
+ (if (eshell-process-pair-p procs)
+ (ignore (setq eshell-last-async-procs procs))
+ (cadr retval)))))
(error
(error (error-message-string err)))))
@@ -1138,17 +1235,16 @@ be finished later after the completion of an asynchronous subprocess."
(setcar form (car new-form))
(setcdr form (cdr new-form)))
(eshell-do-eval form synchronous-p))
- (if (and (memq (car form) eshell-deferrable-commands)
- (not eshell-current-subjob-p)
- result
- (eshell-processp result))
- (if synchronous-p
- (eshell/wait result)
+ (if-let (((memq (car form) eshell-deferrable-commands))
+ ((not eshell-current-subjob-p))
+ (procs (eshell-make-process-pair result)))
+ (if synchronous-p
+ (eshell/wait (cdr procs))
(eshell-manipulate "inserting ignore form"
(setcar form 'ignore)
(setcdr form nil))
- (throw 'eshell-defer result))
- (list 'quote result))))))))))))
+ (throw 'eshell-defer procs))
+ (list 'quote result))))))))))))
;; command invocation
@@ -1238,8 +1334,9 @@ or an external command."
(defun eshell-exec-lisp (printer errprint func-or-form args form-p)
"Execute a Lisp FUNC-OR-FORM, maybe passing ARGS.
PRINTER and ERRPRINT are functions to use for printing regular
-messages, and errors. FORM-P should be non-nil if FUNC-OR-FORM
-represent a Lisp form; ARGS will be ignored in that case."
+messages and errors, respectively. FORM-P should be non-nil if
+FUNC-OR-FORM represent a Lisp form; ARGS will be ignored in that
+case."
(eshell-condition-case err
(let ((result
(save-current-buffer
@@ -1262,44 +1359,56 @@ represent a Lisp form; ARGS will be ignored in that case."
(defsubst eshell-apply* (printer errprint func args)
"Call FUNC, with ARGS, trapping errors and return them as output.
PRINTER and ERRPRINT are functions to use for printing regular
-messages, and errors."
+messages and errors, respectively."
(eshell-exec-lisp printer errprint func args nil))
(defsubst eshell-funcall* (printer errprint func &rest args)
- "Call FUNC, with ARGS, trapping errors and return them as output."
+ "Call FUNC, with ARGS, trapping errors and return them as output.
+PRINTER and ERRPRINT are functions to use for printing regular
+messages and errors, respectively."
(eshell-apply* printer errprint func args))
(defsubst eshell-eval* (printer errprint form)
- "Evaluate FORM, trapping errors and returning them."
+ "Evaluate FORM, trapping errors and returning them.
+PRINTER and ERRPRINT are functions to use for printing regular
+messages and errors, respectively."
(eshell-exec-lisp printer errprint form nil t))
(defsubst eshell-apply (func args)
"Call FUNC, with ARGS, trapping errors and return them as output.
-PRINTER and ERRPRINT are functions to use for printing regular
-messages, and errors."
- (eshell-apply* 'eshell-print 'eshell-error func args))
+Print the result using `eshell-print'; if an error occurs, print
+it via `eshell-error'."
+ (eshell-apply* #'eshell-print #'eshell-error func args))
(defsubst eshell-funcall (func &rest args)
- "Call FUNC, with ARGS, trapping errors and return them as output."
+ "Call FUNC, with ARGS, trapping errors and return them as output.
+Print the result using `eshell-print'; if an error occurs, print
+it via `eshell-error'."
(eshell-apply func args))
(defsubst eshell-eval (form)
- "Evaluate FORM, trapping errors and returning them."
- (eshell-eval* 'eshell-print 'eshell-error form))
+ "Evaluate FORM, trapping errors and returning them.
+Print the result using `eshell-print'; if an error occurs, print
+it via `eshell-error'."
+ (eshell-eval* #'eshell-print #'eshell-error form))
(defsubst eshell-applyn (func args)
"Call FUNC, with ARGS, trapping errors and return them as output.
-PRINTER and ERRPRINT are functions to use for printing regular
-messages, and errors."
- (eshell-apply* 'eshell-printn 'eshell-errorn func args))
+Print the result using `eshell-printn'; if an error occurs, print it
+via `eshell-errorn'."
+ (eshell-apply* #'eshell-printn #'eshell-errorn func args))
(defsubst eshell-funcalln (func &rest args)
- "Call FUNC, with ARGS, trapping errors and return them as output."
+ "Call FUNC, with ARGS, trapping errors and return them as output.
+Print the result using `eshell-printn'; if an error occurs, print it
+via `eshell-errorn'."
(eshell-applyn func args))
(defsubst eshell-evaln (form)
- "Evaluate FORM, trapping errors and returning them."
- (eshell-eval* 'eshell-printn 'eshell-errorn form))
+ "Evaluate FORM, trapping errors and returning them.
+Print the result using `eshell-printn'; if an error occurs, print it
+via `eshell-errorn'."
+ (eshell-eval* #'eshell-printn #'eshell-errorn form))
(defvar eshell-last-output-end) ;Defined in esh-mode.el.
diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el
index 5179947da76..3644c1a18b5 100644
--- a/lisp/eshell/esh-io.el
+++ b/lisp/eshell/esh-io.el
@@ -147,9 +147,10 @@ not be added to this variable."
function
(choice (const :tag "Func returns output-func" t)
(const :tag "Func is output-func" nil))))
+ :risky t
:group 'eshell-io)
-(put 'eshell-virtual-targets 'risky-local-variable t)
+(define-error 'eshell-pipe-broken "Pipe broken")
;;; Internal Variables:
@@ -376,8 +377,6 @@ it defaults to `insert'."
(error "Invalid redirection target: %s"
(eshell-stringify target)))))
-(defvar grep-null-device)
-
(defun eshell-set-output-handle (index mode &optional target)
"Set handle INDEX, using MODE, to point to TARGET."
(when target
@@ -484,24 +483,31 @@ Returns what was actually sent, or nil if nothing was sent."
(goto-char target))))))
((eshell-processp target)
- (when (eq (process-status target) 'run)
- (unless (stringp object)
- (setq object (eshell-stringify object)))
- (process-send-string target object)))
+ (unless (stringp object)
+ (setq object (eshell-stringify object)))
+ (condition-case nil
+ (process-send-string target object)
+ ;; If `process-send-string' raises an error, treat it as a broken pipe.
+ (error (signal 'eshell-pipe-broken target))))
((consp target)
(apply (car target) object (cdr target))))
object)
(defun eshell-output-object (object &optional handle-index handles)
- "Insert OBJECT, using HANDLE-INDEX specifically)."
+ "Insert OBJECT, using HANDLE-INDEX specifically.
+If HANDLE-INDEX is nil, output to `eshell-output-handle'.
+HANDLES is the set of file handles to use; if nil, use
+`eshell-current-handles'."
(let ((target (car (aref (or handles eshell-current-handles)
(or handle-index eshell-output-handle)))))
- (if (and target (not (listp target)))
- (eshell-output-object-to-target object target)
- (while target
- (eshell-output-object-to-target object (car target))
- (setq target (cdr target))))))
+ (if (listp target)
+ (while target
+ (eshell-output-object-to-target object (car target))
+ (setq target (cdr target)))
+ (eshell-output-object-to-target object target)
+ ;; Explicitly return nil to match the list case above.
+ nil)))
(provide 'esh-io)
;;; esh-io.el ends here
diff --git a/lisp/eshell/esh-mode.el b/lisp/eshell/esh-mode.el
index a3d9d582e58..59c8f8034fe 100644
--- a/lisp/eshell/esh-mode.el
+++ b/lisp/eshell/esh-mode.el
@@ -260,31 +260,28 @@ This is used by `eshell-watch-for-password-prompt'."
(standard-syntax-table))
st))
-(defvar eshell-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map [(control ?c)] 'eshell-command-map)
- (define-key map "\r" #'eshell-send-input)
- (define-key map "\M-\r" #'eshell-queue-input)
- (define-key map [(meta control ?l)] #'eshell-show-output)
- (define-key map [(control ?a)] #'eshell-bol)
- map))
-
-(defvar eshell-command-map
- (let ((map (define-prefix-command 'eshell-command-map)))
- (define-key map [(meta ?o)] #'eshell-mark-output)
- (define-key map [(meta ?d)] #'eshell-toggle-direct-send)
- (define-key map [(control ?a)] #'eshell-bol)
- (define-key map [(control ?b)] #'eshell-backward-argument)
- (define-key map [(control ?e)] #'eshell-show-maximum-output)
- (define-key map [(control ?f)] #'eshell-forward-argument)
- (define-key map [(control ?m)] #'eshell-copy-old-input)
- (define-key map [(control ?o)] #'eshell-kill-output)
- (define-key map [(control ?r)] #'eshell-show-output)
- (define-key map [(control ?t)] #'eshell-truncate-buffer)
- (define-key map [(control ?u)] #'eshell-kill-input)
- (define-key map [(control ?w)] #'backward-kill-word)
- (define-key map [(control ?y)] #'eshell-repeat-argument)
- map))
+(defvar-keymap eshell-mode-map
+ "C-c" 'eshell-command-map
+ "RET" #'eshell-send-input
+ "M-RET" #'eshell-queue-input
+ "C-M-l" #'eshell-show-output
+ "C-a" #'eshell-bol)
+
+(defvar-keymap eshell-command-map
+ :prefix 'eshell-command-map
+ "M-o" #'eshell-mark-output
+ "M-d" #'eshell-toggle-direct-send
+ "C-a" #'eshell-bol
+ "C-b" #'eshell-backward-argument
+ "C-e" #'eshell-show-maximum-output
+ "C-f" #'eshell-forward-argument
+ "C-m" #'eshell-copy-old-input
+ "C-o" #'eshell-kill-output
+ "C-r" #'eshell-show-output
+ "C-t" #'eshell-truncate-buffer
+ "C-u" #'eshell-kill-input
+ "C-w" #'backward-kill-word
+ "C-y" #'eshell-repeat-argument)
;;; User Functions:
@@ -308,7 +305,7 @@ and the hook `eshell-exit-hook'."
(make-local-variable 'eshell-command-running-string)
(let ((fmt (copy-sequence mode-line-format)))
(setq-local mode-line-format fmt))
- (let ((mode-line-elt (memq 'mode-line-modified mode-line-format)))
+ (let ((mode-line-elt (cdr (memq 'mode-line-front-space mode-line-format))))
(if mode-line-elt
(setcar mode-line-elt 'eshell-command-running-string))))
@@ -426,13 +423,13 @@ and the hook `eshell-exit-hook'."
(defun eshell-self-insert-command ()
(interactive)
(process-send-string
- (eshell-interactive-process)
+ (eshell-head-process)
(char-to-string (if (symbolp last-command-event)
(get last-command-event 'ascii-character)
last-command-event))))
(defun eshell-intercept-commands ()
- (when (and (eshell-interactive-process)
+ (when (and (eshell-interactive-process-p)
(not (and (integerp last-input-event)
(memq last-input-event '(?\C-x ?\C-c)))))
(let ((possible-events (where-is-internal this-command))
@@ -598,13 +595,13 @@ 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-interactive-process)
+ (let ((proc-running-p (and (eshell-head-process)
(not queue-p)))
(inhibit-point-motion-hooks t)
(inhibit-modification-hooks t))
(unless (and proc-running-p
(not (eq (process-status
- (eshell-interactive-process))
+ (eshell-head-process))
'run)))
(if (or proc-running-p
(>= (point) eshell-last-output-end))
@@ -616,14 +613,22 @@ newline."
(and eshell-send-direct-to-subprocesses
proc-running-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
+ ;; insertion will delete the region, moving the process mark
+ ;; back to its original position.
+ (let ((text (buffer-substring eshell-last-output-end (point)))
+ (inhibit-read-only t))
+ (delete-region eshell-last-output-end (point))
+ (insert text))
(if proc-running-p
(progn
(eshell-update-markers eshell-last-output-end)
(if (or eshell-send-direct-to-subprocesses
(= eshell-last-input-start eshell-last-input-end))
(unless no-newline
- (process-send-string (eshell-interactive-process) "\n"))
- (process-send-region (eshell-interactive-process)
+ (process-send-string (eshell-head-process) "\n"))
+ (process-send-region (eshell-head-process)
eshell-last-input-start
eshell-last-input-end)))
(if (= eshell-last-output-end (point))
@@ -660,6 +665,16 @@ newline."
(run-hooks 'eshell-post-command-hook)
(insert-and-inherit input)))))))))
+(defun eshell-send-eof-to-process ()
+ "Send EOF to the currently-running \"head\" process."
+ (interactive)
+ (require 'esh-mode)
+ (declare-function eshell-send-input "esh-mode"
+ (&optional use-region queue-p no-newline))
+ (eshell-send-input nil nil t)
+ (when (eshell-head-process)
+ (process-send-eof (eshell-head-process))))
+
(defsubst eshell-kill-new ()
"Add the last input text to the kill ring."
(kill-ring-save eshell-last-input-start eshell-last-input-end))
@@ -919,9 +934,9 @@ Then send it to the process running in the current buffer."
(interactive) ; Don't pass str as argument, to avoid snooping via C-x ESC ESC
(let ((str (read-passwd
(format "%s Password: "
- (process-name (eshell-interactive-process))))))
+ (process-name (eshell-head-process))))))
(if (stringp str)
- (process-send-string (eshell-interactive-process)
+ (process-send-string (eshell-head-process)
(concat str "\n"))
(message "Warning: text will be echoed"))))
@@ -932,14 +947,21 @@ buffer's process if STRING contains a password prompt defined by
`eshell-password-prompt-regexp'.
This function could be in the list `eshell-output-filter-functions'."
- (when (eshell-interactive-process)
+ (when (eshell-interactive-process-p)
(save-excursion
(let ((case-fold-search t))
(goto-char eshell-last-output-block-begin)
(beginning-of-line)
(if (re-search-forward eshell-password-prompt-regexp
eshell-last-output-end t)
- (eshell-send-invisible))))))
+ ;; Use `run-at-time' in order not to pause execution of
+ ;; the process filter with a minibuffer
+ (run-at-time
+ 0 nil
+ (lambda (current-buf)
+ (with-current-buffer current-buf
+ (eshell-send-invisible)))
+ (current-buffer)))))))
(custom-add-option 'eshell-output-filter-functions
'eshell-watch-for-password-prompt)
diff --git a/lisp/eshell/esh-module.el b/lisp/eshell/esh-module.el
index ade151d7cd5..14e91912d11 100644
--- a/lisp/eshell/esh-module.el
+++ b/lisp/eshell/esh-module.el
@@ -54,6 +54,7 @@ customizing the variable `eshell-modules-list'."
eshell-basic
eshell-cmpl
eshell-dirs
+ eshell-extpipe
eshell-glob
eshell-hist
eshell-ls
diff --git a/lisp/eshell/esh-opt.el b/lisp/eshell/esh-opt.el
index d96b77ddd37..f52b70fe7a6 100644
--- a/lisp/eshell/esh-opt.el
+++ b/lisp/eshell/esh-opt.el
@@ -97,10 +97,10 @@ let-bound variable `args'."
(declare (debug (form form sexp body)))
`(let* ((temp-args
,(if (memq ':preserve-args (cadr options))
- macro-args
+ (list 'copy-tree macro-args)
(list 'eshell-stringify-list
(list 'flatten-tree macro-args))))
- (processed-args (eshell--do-opts ,name ,options temp-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)
@@ -117,7 +117,7 @@ let-bound variable `args'."
;; Documented part of the interface; see eshell-eval-using-options.
(defvar eshell--args)
-(defun eshell--do-opts (name options args)
+(defun eshell--do-opts (name options args orig-args)
"Helper function for `eshell-eval-using-options'.
This code doesn't really need to be macro expanded everywhere."
(require 'esh-ext)
@@ -135,7 +135,7 @@ This code doesn't really need to be macro expanded everywhere."
(error "%s" usage-msg))))))
(if ext-command
(throw 'eshell-external
- (eshell-external-command ext-command args))
+ (eshell-external-command ext-command orig-args))
args)))
(defun eshell-show-usage (name options)
@@ -187,49 +187,82 @@ passed to this command, the external version `%s'
will be called instead." extcmd)))))
(throw 'eshell-usage usage)))
-(defun eshell--set-option (name ai opt options opt-vals)
+(defun eshell--split-switch (switch kind)
+ "Split SWITCH into its option name and potential value, if any.
+KIND should be the integer 0 if SWITCH is a short option, or 1 if it's
+a long option."
+ (if (eq kind 0)
+ ;; Short option
+ (cons (aref switch 0)
+ (and (> (length switch) 1) (substring switch 1)))
+ ;; Long option
+ (save-match-data
+ (string-match "\\([^=]*\\)\\(?:=\\(.*\\)\\)?" switch)
+ (cons (match-string 1 switch) (match-string 2 switch)))))
+
+(defun eshell--set-option (name ai opt value options opt-vals)
"Using NAME's remaining args (index AI), set the OPT within OPTIONS.
-If the option consumes an argument for its value, the argument list
-will be modified."
+VALUE is the potential value of the OPT, coming from args like
+\"-fVALUE\" or \"--foo=VALUE\", or nil if no value was supplied. If
+OPT doesn't consume a value, return VALUE unchanged so that it can be
+processed later; otherwise, return nil.
+
+If the OPT consumes an argument for its value and VALUE is nil, the
+argument list will be modified."
(if (not (nth 3 opt))
(eshell-show-usage name options)
- (setcdr (assq (nth 3 opt) opt-vals)
- (if (eq (nth 2 opt) t)
- (if (> ai (length eshell--args))
- (error "%s: missing option argument" name)
- (pop (nthcdr ai eshell--args)))
- (or (nth 2 opt) t)))))
+ (if (eq (nth 2 opt) t)
+ (progn
+ (setcdr (assq (nth 3 opt) opt-vals)
+ (or value
+ (if (> ai (length eshell--args))
+ (error "%s: missing option argument" name)
+ (pop (nthcdr ai eshell--args)))))
+ nil)
+ (setcdr (assq (nth 3 opt) opt-vals)
+ (or (nth 2 opt) t))
+ value)))
(defun eshell--process-option (name switch kind ai options opt-vals)
"For NAME, process SWITCH (of type KIND), from args at index AI.
The SWITCH will be looked up in the set of OPTIONS.
-SWITCH should be either a string or character. KIND should be the
-integer 0 if it's a character, or 1 if it's a string.
-
-The SWITCH is then be matched against OPTIONS. If no matching handler
-is found, and an :external command is defined (and available), it will
-be called; otherwise, an error will be triggered to say that the
-switch is unrecognized."
- (let* ((opts options)
- found)
+SWITCH should be a string starting with the option to process,
+possibly followed by its value, e.g. \"u\" or \"uUSER\". KIND should
+be the integer 0 if it's a short option, or 1 if it's a long option.
+
+The SWITCH is then be matched against OPTIONS. If KIND is 0 and the
+SWITCH matches an option that doesn't take a value, return the
+remaining characters in SWITCH to be processed later as further short
+options.
+
+If no matching handler is found, and an :external command is defined
+(and available), it will be called; otherwise, an error will be
+triggered to say that the switch is unrecognized."
+ (let ((switch (eshell--split-switch switch kind))
+ (opts options)
+ found remaining)
(while opts
(if (and (listp (car opts))
- (nth kind (car opts))
- (equal switch (nth kind (car opts))))
+ (equal (car switch) (nth kind (car opts))))
(progn
- (eshell--set-option name ai (car opts) options opt-vals)
+ (setq remaining (eshell--set-option name ai (car opts)
+ (cdr switch) options opt-vals))
+ (when (and remaining (eq kind 1))
+ (error "%s: option --%s doesn't allow an argument"
+ name (car switch)))
(setq found t opts nil))
(setq opts (cdr opts))))
- (unless found
+ (if found
+ remaining
(let ((extcmd (memq ':external options)))
(when extcmd
- (setq extcmd (eshell-search-path (cadr extcmd)))
- (if extcmd
- (throw 'eshell-ext-command extcmd)
- (error (if (characterp switch) "%s: unrecognized option -%c"
- "%s: unrecognized option --%s")
- name switch)))))))
+ (setq extcmd (eshell-search-path (cadr extcmd))))
+ (if extcmd
+ (throw 'eshell-ext-command extcmd)
+ (error (if (characterp (car switch)) "%s: unrecognized option -%c"
+ "%s: unrecognized option --%s")
+ name (car switch)))))))
(defun eshell--process-args (name args options)
"Process the given ARGS using OPTIONS."
@@ -250,6 +283,9 @@ switch is unrecognized."
(memq :parse-leading-options-only options))))
(setq arg (nth ai eshell--args))
(if (not (and (stringp arg)
+ ;; A string of length 1 can't be an option; (if
+ ;; it's "-", that generally means stdin).
+ (> (length arg) 1)
(string-match "^-\\(-\\)?\\(.*\\)" arg)))
;; Positional argument found, skip
(setq ai (1+ ai)
@@ -262,12 +298,9 @@ switch is unrecognized."
(if (> (length switch) 0)
(eshell--process-option name switch 1 ai options opt-vals)
(setq ai (length eshell--args)))
- (let ((len (length switch))
- (index 0))
- (while (< index len)
- (eshell--process-option name (aref switch index)
- 0 ai options opt-vals)
- (setq index (1+ index))))))))
+ (while (> (length switch) 0)
+ (setq switch (eshell--process-option name switch 0
+ ai options opt-vals)))))))
(nconc (mapcar #'cdr opt-vals) eshell--args)))
(provide 'esh-opt)
diff --git a/lisp/eshell/esh-proc.el b/lisp/eshell/esh-proc.el
index c4103fbafbb..70426ccaf2a 100644
--- a/lisp/eshell/esh-proc.el
+++ b/lisp/eshell/esh-proc.el
@@ -101,15 +101,16 @@ information, for example."
(defvar eshell-process-list nil
"A list of the current status of subprocesses.")
-(defvar eshell-proc-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map (kbd "C-c M-i") #'eshell-insert-process)
- (define-key map (kbd "C-c C-c") #'eshell-interrupt-process)
- (define-key map (kbd "C-c C-k") #'eshell-kill-process)
- (define-key map (kbd "C-c C-d") #'eshell-send-eof-to-process)
- (define-key map (kbd "C-c C-s") #'list-processes)
- (define-key map (kbd "C-c C-\\") #'eshell-quit-process)
- map))
+(declare-function eshell-send-eof-to-process "esh-mode")
+(declare-function eshell-tail-process "esh-cmd")
+
+(defvar-keymap eshell-proc-mode-map
+ "C-c M-i" #'eshell-insert-process
+ "C-c C-c" #'eshell-interrupt-process
+ "C-c C-k" #'eshell-kill-process
+ "C-c C-d" #'eshell-send-eof-to-process
+ "C-c C-s" #'list-processes
+ "C-c C-\\" #'eshell-quit-process)
;;; Functions:
@@ -119,7 +120,9 @@ Runs `eshell-reset-after-proc' and `eshell-kill-hook', passing arguments
PROC and STATUS to functions on the latter."
;; Was there till 24.1, but it is not optional.
(remove-hook 'eshell-kill-hook #'eshell-reset-after-proc)
- (eshell-reset-after-proc status)
+ ;; Only reset the prompt if this process is running interactively.
+ (when (eq proc (eshell-tail-process))
+ (eshell-reset-after-proc status))
(run-hook-with-args 'eshell-kill-hook proc status))
(define-minor-mode eshell-proc-mode
@@ -386,8 +389,27 @@ output."
(let ((data (nth 3 entry)))
(setcar (nthcdr 3 entry) nil)
(setcar (nthcdr 4 entry) t)
- (eshell-output-object data nil (cadr entry))
- (setcar (nthcdr 4 entry) nil)))))))))
+ (unwind-protect
+ (condition-case nil
+ (eshell-output-object data nil (cadr entry))
+ ;; FIXME: We want to send SIGPIPE to the process
+ ;; here. However, remote processes don't
+ ;; currently support that, and not all systems
+ ;; have SIGPIPE in the first place (e.g. MS
+ ;; Windows). In these cases, just delete the
+ ;; process; this is reasonably close to the
+ ;; right behavior, since the default action for
+ ;; SIGPIPE is to terminate the process. For use
+ ;; cases where SIGPIPE is truly needed, using an
+ ;; external pipe operator (`*|') may work
+ ;; instead (e.g. when working with remote
+ ;; processes).
+ (eshell-pipe-broken
+ (if (or (process-get proc 'remote-pid)
+ (eq system-type 'windows-nt))
+ (delete-process proc)
+ (signal-process proc 'SIGPIPE))))
+ (setcar (nthcdr 4 entry) nil))))))))))
(defun eshell-sentinel (proc string)
"Generic sentinel for command processes. Reports only signals.
@@ -395,7 +417,7 @@ PROC is the process that's exiting. STRING is the exit message."
(when (buffer-live-p (process-buffer proc))
(with-current-buffer (process-buffer proc)
(unwind-protect
- (let* ((entry (assq proc eshell-process-list)))
+ (let ((entry (assq proc eshell-process-list)))
; (if (not entry)
; (error "Sentinel called for unowned process `%s'"
; (process-name proc))
@@ -403,8 +425,13 @@ PROC is the process that's exiting. STRING is the exit message."
(unwind-protect
(progn
(unless (string= string "run")
- (unless (string-match "^\\(finished\\|exited\\)" string)
- (eshell-insertion-filter proc string))
+ ;; Write the exit message if the status is
+ ;; abnormal and the process is already writing
+ ;; to the terminal.
+ (when (and (eq proc (eshell-tail-process))
+ (not (string-match "^\\(finished\\|exited\\)"
+ string)))
+ (funcall (process-filter proc) proc string))
(let ((handles (nth 1 entry))
(str (prog1 (nth 3 entry)
(setf (nth 3 entry) nil)))
@@ -416,8 +443,12 @@ PROC is the process that's exiting. STRING is the exit message."
(lambda ()
(if (nth 4 entry)
(run-at-time 0 nil finish-io)
- (when str (eshell-output-object str nil handles))
- (eshell-close-handles status 'nil handles)))))
+ (when str
+ (ignore-error 'eshell-pipe-broken
+ (eshell-output-object
+ str nil handles)))
+ (eshell-close-handles
+ status 'nil handles)))))
(funcall finish-io)))))
(eshell-remove-process-entry entry))))
(eshell-kill-process-function proc string)))))
@@ -544,14 +575,5 @@ See the variable `eshell-kill-processes-on-exit'."
; ;; `eshell-resume-eval'.
; (eshell-kill-process-function nil "continue")))
-(defun eshell-send-eof-to-process ()
- "Send EOF to process."
- (interactive)
- (require 'esh-mode)
- (declare-function eshell-send-input "esh-mode"
- (&optional use-region queue-p no-newline))
- (eshell-send-input nil nil t)
- (eshell-process-interact 'process-send-eof))
-
(provide 'esh-proc)
;;; esh-proc.el ends here
diff --git a/lisp/eshell/esh-util.el b/lisp/eshell/esh-util.el
index bacb41eceff..b5a423f0237 100644
--- a/lisp/eshell/esh-util.el
+++ b/lisp/eshell/esh-util.el
@@ -63,11 +63,11 @@ has no effect."
Setting this to nil is offered as an aid to debugging only."
:type 'boolean)
-(defcustom eshell-private-file-modes 384 ; umask 177
+(defcustom eshell-private-file-modes #o600 ; umask 177
"The file-modes value to use for creating \"private\" files."
:type 'integer)
-(defcustom eshell-private-directory-modes 448 ; umask 077
+(defcustom eshell-private-directory-modes #o700 ; umask 077
"The file-modes value to use for creating \"private\" directories."
:type 'integer)
@@ -151,67 +151,98 @@ Otherwise, evaluates FORM with no error handling."
(defun eshell-find-delimiter
(open close &optional bound reverse-p backslash-p)
"From point, find the CLOSE delimiter corresponding to OPEN.
-The matching is bounded by BOUND.
-If REVERSE-P is non-nil, process the region backwards.
-If BACKSLASH-P is non-nil, and OPEN and CLOSE are the same character,
-then quoting is done by a backslash, rather than a doubled delimiter."
+The matching is bounded by BOUND. If REVERSE-P is non-nil,
+process the region backwards.
+
+If BACKSLASH-P is non-nil, or OPEN and CLOSE are different
+characters, then a backslash can be used to escape a delimiter
+(or another backslash). Otherwise, the delimiter is escaped by
+doubling it up."
(save-excursion
(let ((depth 1)
(bound (or bound (point-max))))
- (if (if reverse-p
- (eq (char-before) close)
- (eq (char-after) open))
- (forward-char (if reverse-p -1 1)))
+ (when (if reverse-p
+ (eq (char-before) close)
+ (eq (char-after) open))
+ (forward-char (if reverse-p -1 1)))
(while (and (> depth 0)
- (funcall (if reverse-p '> '<) (point) bound))
- (let ((c (if reverse-p (char-before) (char-after))) nc)
+ (funcall (if reverse-p #'> #'<) (point) bound))
+ (let ((c (if reverse-p (char-before) (char-after))))
(cond ((and (not reverse-p)
(or (not (eq open close))
backslash-p)
(eq c ?\\)
- (setq nc (char-after (1+ (point))))
- (or (eq nc open) (eq nc close)))
+ (memq (char-after (1+ (point)))
+ (list open close ?\\)))
(forward-char 1))
((and reverse-p
(or (not (eq open close))
backslash-p)
- (or (eq c open) (eq c close))
- (eq (char-before (1- (point)))
- ?\\))
+ (eq (char-before (1- (point))) ?\\)
+ (memq c (list open close ?\\)))
(forward-char -1))
((eq open close)
- (if (eq c open)
- (if (and (not backslash-p)
- (eq (if reverse-p
- (char-before (1- (point)))
- (char-after (1+ (point)))) open))
- (forward-char (if reverse-p -1 1))
- (setq depth (1- depth)))))
+ (when (eq c open)
+ (if (and (not backslash-p)
+ (eq (if reverse-p
+ (char-before (1- (point)))
+ (char-after (1+ (point))))
+ open))
+ (forward-char (if reverse-p -1 1))
+ (setq depth (1- depth)))))
((= c open)
(setq depth (+ depth (if reverse-p -1 1))))
((= c close)
(setq depth (+ depth (if reverse-p 1 -1))))))
(forward-char (if reverse-p -1 1)))
- (if (= depth 0)
- (if reverse-p (point) (1- (point)))))))
-
-(defun eshell-convert (string)
- "Convert STRING into a more native looking Lisp object."
- (if (not (stringp string))
- string
- (let ((len (length string)))
- (if (= len 0)
- string
- (if (eq (aref string (1- len)) ?\n)
+ (when (= depth 0)
+ (if reverse-p (point) (1- (point)))))))
+
+(defun eshell-convertible-to-number-p (string)
+ "Return non-nil if STRING can be converted to a number.
+If `eshell-convert-numeric-aguments', always return nil."
+ (and eshell-convert-numeric-arguments
+ (string-match
+ (concat "\\`\\s-*" eshell-number-regexp "\\s-*\\'")
+ string)))
+
+(defun eshell-convert-to-number (string)
+ "Try to convert STRING to a number.
+If STRING doesn't look like a number (or
+`eshell-convert-numeric-aguments' is nil), just return STRING
+unchanged."
+ (if (eshell-convertible-to-number-p string)
+ (string-to-number string)
+ string))
+
+(defun eshell-convert (string &optional to-string)
+ "Convert STRING into a more-native Lisp object.
+If TO-STRING is non-nil, always return a single string with
+trailing newlines removed. Otherwise, this behaves as follows:
+
+* Return non-strings as-is.
+
+* Split multiline strings by line.
+
+* If `eshell-convert-numeric-aguments' is non-nil and every line
+ of output looks like a number, convert them to numbers."
+ (cond
+ ((not (stringp string))
+ (if to-string
+ (eshell-stringify string)
+ string))
+ (to-string (string-trim-right string "\n+"))
+ (t (let ((len (length string)))
+ (if (= len 0)
+ string
+ (when (eq (aref string (1- len)) ?\n)
(setq string (substring string 0 (1- len))))
- (if (string-search "\n" string)
- (split-string string "\n")
- (if (and eshell-convert-numeric-arguments
- (string-match
- (concat "\\`\\s-*" eshell-number-regexp
- "\\s-*\\'") string))
- (string-to-number string)
- string))))))
+ (if (string-search "\n" string)
+ (let ((lines (split-string string "\n")))
+ (if (seq-every-p #'eshell-convertible-to-number-p lines)
+ (mapcar #'string-to-number lines)
+ lines))
+ (eshell-convert-to-number string)))))))
(defvar-local eshell-path-env (getenv "PATH")
"Content of $PATH.
@@ -262,6 +293,7 @@ Prepend remote identification of `default-directory', if any."
(defun eshell-to-flat-string (value)
"Make value a string. If separated by newlines change them to spaces."
+ (declare (obsolete nil "29.1"))
(let ((text (eshell-stringify value)))
(if (string-match "\n+\\'" text)
(setq text (replace-match "" t t text)))
@@ -589,11 +621,11 @@ list."
The optional argument ID-FORMAT specifies the preferred uid and
gid format. Valid values are `string' and `integer', defaulting to
`integer'. See `file-attributes'."
- (let* ((file (expand-file-name file))
+ (let* ((expanded-file (expand-file-name file))
entry)
- (if (string-equal (file-remote-p file 'method) "ftp")
- (let ((base (file-name-nondirectory file))
- (dir (file-name-directory file)))
+ (if (string-equal (file-remote-p expanded-file 'method) "ftp")
+ (let ((base (file-name-nondirectory expanded-file))
+ (dir (file-name-directory expanded-file)))
(if (string-equal "" base) (setq base "."))
(unless entry
(setq entry (eshell-parse-ange-ls dir))
@@ -609,6 +641,20 @@ gid format. Valid values are `string' and `integer', defaulting to
"If the `processp' function does not exist, PROC is not a process."
(and (fboundp 'processp) (processp proc)))
+(defun eshell-process-pair-p (procs)
+ "Return non-nil if PROCS is a pair of process objects."
+ (and (consp procs)
+ (eshell-processp (car procs))
+ (eshell-processp (cdr procs))))
+
+(defun eshell-make-process-pair (procs)
+ "Make a pair of process objects from PROCS if possible.
+This represents the head and tail of a pipeline of processes,
+where the head and tail may be the same process."
+ (pcase procs
+ ((pred eshell-processp) (cons procs procs))
+ ((pred eshell-process-pair-p) procs)))
+
;; (defun eshell-copy-file
;; (file newname &optional ok-if-already-exists keep-date)
;; "Copy FILE to NEWNAME. See docs for `copy-file'."
diff --git a/lisp/eshell/esh-var.el b/lisp/eshell/esh-var.el
index 1d5d85debad..186f6358bca 100644
--- a/lisp/eshell/esh-var.el
+++ b/lisp/eshell/esh-var.el
@@ -39,11 +39,6 @@
;;
;; Only "MYVAR" is part of the variable name in this case.
;;
-;; $#VARIABLE
-;;
-;; Returns the length of the value of VARIABLE. This could also be
-;; done using the `length' Lisp function.
-;;
;; $(lisp)
;;
;; Returns result of Lisp evaluation. Note: Used alone like this, it
@@ -61,38 +56,35 @@
;; Evaluates an eshell subcommand, redirecting the output to a
;; temporary file, and returning the file name.
;;
-;; $ANYVAR[10]
+;; $EXPR[10]
;;
-;; Return the 10th element of ANYVAR. If ANYVAR's value is a string,
-;; it will be split in order to make it a list. The splitting will
-;; occur at whitespace.
+;; Return the 10th element of $EXPR, which can be any dollar
+;; expression. If $EXPR's value is a string, it will be split in
+;; order to make it a list. The splitting will occur at whitespace.
;;
-;; $ANYVAR[: 10]
+;; $EXPR[10 20]
;;
-;; As above, except that splitting occurs at the colon now.
+;; As above, but instead of returning a single element, it now returns a
+;; list of two elements.
;;
-;; $ANYVAR[: 10 20]
+;; $EXPR[: 10]
;;
-;; As above, but instead of returning just a string, it now returns a
-;; list of two strings. If the result is being interpolated into a
-;; larger string, this list will be flattened into one big string,
-;; with each element separated by a space.
+;; Like $EXPR[10], except that splitting occurs at the colon now.
;;
-;; $ANYVAR["\\\\" 10]
+;; $EXPR["\\\\" 10]
;;
;; Separate on backslash characters. Actually, the first argument --
-;; if it doesn't have the form of a number, or a plain variable name
-;; -- can be any regular expression. So to split on numbers, use
-;; '$ANYVAR["[0-9]+" 10 20]'.
+;; if it doesn't have the form of a number -- can be any regular
+;; expression. So to split on numbers, use '$EXPR["[0-9]+" 10 20]'.
;;
-;; $ANYVAR[hello]
+;; $EXPR[hello]
;;
-;; Calls `assoc' on ANYVAR with 'hello', expecting it to be an alist.
+;; Calls `assoc' on $EXPR with 'hello', expecting it to be an alist.
;;
-;; $#ANYVAR[hello]
+;; $#EXPR
;;
-;; Returns the length of the cdr of the element of ANYVAR who car is
-;; equal to "hello".
+;; Returns the length of the value of $EXPR. This could also be
+;; done using the `length' Lisp function.
;;
;; There are also a few special variables defined by Eshell. '$$' is
;; the value of the last command (t or nil, in the case of an external
@@ -193,7 +185,7 @@ list of the indices that was used in the reference, and whether the
user is requesting the length of the ultimate element. For example, a
reference of `$NAME[10][20]' would result in the function for alias
`NAME' being called (assuming it were aliased to a function), and the
-arguments passed to this function would be the list '(10 20)', and
+arguments passed to this function would be the list `(10 20)', and
nil.
If the value is a string, return the value for the variable with that
@@ -211,14 +203,11 @@ Additionally, each member may specify if it should be copied to the
environment of created subprocesses."
:type '(repeat (list string sexp
(choice (const :tag "Copy to environment" t)
- (const :tag "Use only in Eshell" nil)))))
-
-(put 'eshell-variable-aliases-list 'risky-local-variable t)
+ (const :tag "Use only in Eshell" nil))))
+ :risky t)
-(defvar eshell-var-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map (kbd "C-c M-v") #'eshell-insert-envvar)
- map))
+(defvar-keymap eshell-var-mode-map
+ "C-c M-v" #'eshell-insert-envvar)
;;; Functions:
@@ -413,27 +402,34 @@ process any indices that come after the variable reference."
(let* ((get-len (when (eq (char-after) ?#)
(forward-char) t))
value indices)
- (setq value (eshell-parse-variable-ref)
+ (setq value (eshell-parse-variable-ref get-len)
indices (and (not (eobp))
(eq (char-after) ?\[)
(eshell-parse-indices))
;; This is an expression that will be evaluated by `eshell-do-eval',
;; which only support let-binding of dynamically-scoped vars
- value `(let ((indices ',indices)) ,value))
- (if get-len
- `(length ,value)
- value)))
-
-(defun eshell-parse-variable-ref ()
+ value `(let ((indices (eshell-eval-indices ',indices))) ,value))
+ (when get-len
+ (setq value `(length ,value)))
+ (when eshell-current-quoted
+ (setq value `(eshell-stringify ,value)))
+ value))
+
+(defun eshell-parse-variable-ref (&optional modifier-p)
"Eval a variable reference.
Returns a Lisp form which, if evaluated, will return the value of the
variable.
-Possible options are:
+If MODIFIER-P is non-nil, the value of the variable will be
+modified by some function. If MODIFIER-P is nil, the value will be
+used as-is; this allows optimization of some kinds of variable
+references.
+
+Possible variable references are:
NAME an environment or Lisp variable value
\"LONG-NAME\" disambiguates the length of the name
- 'LONG-NAME' as above
+ `LONG-NAME' as above
{COMMAND} result of command is variable's value
(LISP-FORM) result of Lisp form is variable's value
<COMMAND> write the output of command to a temporary file;
@@ -443,18 +439,26 @@ Possible options are:
(let ((end (eshell-find-delimiter ?\{ ?\})))
(if (not end)
(throw 'eshell-incomplete ?\{)
+ (forward-char)
(prog1
- `(eshell-convert
- (eshell-command-to-value
- (eshell-as-subcommand
- ,(eshell-parse-command (cons (1+ (point)) end)))))
+ `(eshell-apply-indices
+ (eshell-convert
+ (eshell-command-to-value
+ (eshell-as-subcommand
+ ,(let ((subcmd (or (eshell-unescape-inner-double-quote end)
+ (cons (point) end)))
+ (eshell-current-quoted nil))
+ (eshell-parse-command subcmd))))
+ ;; If this is a simple double-quoted form like
+ ;; "${COMMAND}" (i.e. no indices after the subcommand
+ ;; and no `#' modifier before), ensure we convert to a
+ ;; single string. This avoids unnecessary work
+ ;; (e.g. splitting the output by lines) when it would
+ ;; just be joined back together afterwards.
+ ,(when (and (not modifier-p) eshell-current-quoted)
+ '(not indices)))
+ indices ,eshell-current-quoted)
(goto-char (1+ end))))))
- ((memq (char-after) '(?\' ?\"))
- (let ((name (if (eq (char-after) ?\')
- (eshell-parse-literal-quote)
- (eshell-parse-double-quote))))
- (if name
- `(eshell-get-variable ,(eval name) indices))))
((eq (char-after) ?\<)
(let ((end (eshell-find-delimiter ?\< ?\>)))
(if (not end)
@@ -466,7 +470,9 @@ Possible options are:
`(let ((eshell-current-handles
(eshell-create-handles ,temp 'overwrite)))
(progn
- (eshell-as-subcommand ,(eshell-parse-command cmd))
+ (eshell-as-subcommand
+ ,(let ((eshell-current-quoted nil))
+ (eshell-parse-command cmd)))
(ignore
(nconc eshell-this-command-hook
;; Quote this lambda; it will be evaluated
@@ -475,22 +481,36 @@ Possible options are:
;; properly. See bug#54190.
(list (function (lambda ()
(delete-file ,temp))))))
- (quote ,temp)))
+ (eshell-apply-indices ,temp indices ,eshell-current-quoted)))
(goto-char (1+ end)))))))
((eq (char-after) ?\()
(condition-case nil
- `(eshell-command-to-value
- (eshell-lisp-command
- ',(read (current-buffer))))
+ `(eshell-apply-indices
+ (eshell-command-to-value
+ (eshell-lisp-command
+ ',(read (or (eshell-unescape-inner-double-quote (point-max))
+ (current-buffer)))))
+ indices ,eshell-current-quoted)
(end-of-file
(throw 'eshell-incomplete ?\())))
+ ((looking-at (rx-to-string
+ `(or "'" ,(if eshell-current-quoted "\\\"" "\""))))
+ (eshell-with-temp-command
+ (or (eshell-unescape-inner-double-quote (point-max))
+ (cons (point) (point-max)))
+ (let ((name (if (eq (char-after) ?\')
+ (eshell-parse-literal-quote)
+ (eshell-parse-double-quote))))
+ (when name
+ `(eshell-get-variable ,(eval name) indices ,eshell-current-quoted)))))
((assoc (char-to-string (char-after))
eshell-variable-aliases-list)
(forward-char)
- `(eshell-get-variable ,(char-to-string (char-before)) indices))
+ `(eshell-get-variable ,(char-to-string (char-before)) indices
+ ,eshell-current-quoted))
((looking-at eshell-variable-name-regexp)
(prog1
- `(eshell-get-variable ,(match-string 0) indices)
+ `(eshell-get-variable ,(match-string 0) indices ,eshell-current-quoted)
(goto-char (match-end 0))))
(t
(error "Invalid variable reference"))))
@@ -498,21 +518,33 @@ Possible options are:
(defvar eshell-glob-function)
(defun eshell-parse-indices ()
- "Parse and return a list of list of indices."
+ "Parse and return a list of index-lists.
+
+For example, \"[0 1][2]\" becomes:
+ ((\"0\" \"1\") (\"2\")."
(let (indices)
(while (eq (char-after) ?\[)
(let ((end (eshell-find-delimiter ?\[ ?\])))
(if (not end)
(throw 'eshell-incomplete ?\[)
(forward-char)
- (let (eshell-glob-function)
- (setq indices (cons (eshell-parse-arguments (point) end)
- indices)))
+ (eshell-with-temp-command (or (eshell-unescape-inner-double-quote end)
+ (cons (point) end))
+ (let (eshell-glob-function (eshell-current-quoted nil))
+ (setq indices (cons (eshell-parse-arguments
+ (point-min) (point-max))
+ indices))))
(goto-char (1+ end)))))
(nreverse indices)))
-(defun eshell-get-variable (name &optional indices)
- "Get the value for the variable NAME."
+(defun eshell-eval-indices (indices)
+ "Evaluate INDICES, a list of index-lists generated by `eshell-parse-indices'."
+ (mapcar (lambda (i) (mapcar #'eval i)) indices))
+
+(defun eshell-get-variable (name &optional indices quoted)
+ "Get the value for the variable NAME.
+INDICES is a list of index-lists (see `eshell-parse-indices').
+If QUOTED is non-nil, this was invoked inside double-quotes."
(let* ((alias (assoc name eshell-variable-aliases-list))
(var (if alias
(cadr alias)
@@ -533,9 +565,9 @@ Possible options are:
(symbol-value var))
(t
(error "Unknown variable `%s'" (eshell-stringify var))))
- indices))))
+ indices quoted))))
-(defun eshell-apply-indices (value indices)
+(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:
@@ -544,12 +576,17 @@ The format of INDICES is:
Each member of INDICES represents a level of nesting. If the first
member of a sublist is not an integer or name, and the value it's
-reference is a string, that will be used as the regexp with which is
-to divide the string into sub-parts. The default is whitespace.
+referencing is a string, that will be used as the regexp with which
+is to divide the string into sub-parts. The default is whitespace.
Otherwise, each INT-OR-NAME refers to an element of the list value.
Integers imply a direct index, and names, an associate lookup using
`assoc'.
+If QUOTED is non-nil, this was invoked inside double-quotes. This
+affects the behavior of splitting strings: without quoting, the
+split values are converted to Lisp forms via `eshell-convert'; with
+quoting, they're left as strings.
+
For example, to retrieve the second element of a user's record in
'/etc/passwd', the variable reference would look like:
@@ -557,15 +594,13 @@ For example, to retrieve the second element of a user's record in
(while indices
(let ((refs (car indices)))
(when (stringp value)
- (let (separator)
- (if (not (or (not (stringp (caar indices)))
- (string-match
- (concat "^" eshell-variable-name-regexp "$")
- (caar indices))))
- (setq separator (caar indices)
- refs (cdr refs)))
+ (let (separator (index (caar indices)))
+ (when (and (stringp index)
+ (not (get-text-property 0 'number index)))
+ (setq separator index
+ refs (cdr refs)))
(setq value
- (mapcar #'eshell-convert
+ (mapcar (lambda (i) (eshell-convert i quoted))
(split-string value separator)))))
(cond
((< (length refs) 0)
diff --git a/lisp/eshell/eshell.el b/lisp/eshell/eshell.el
index fbf347e55a7..2c472a2afad 100644
--- a/lisp/eshell/eshell.el
+++ b/lisp/eshell/eshell.el
@@ -260,7 +260,7 @@ information on Eshell, see Info node `(eshell)Top'."
(t
(get-buffer-create eshell-buffer-name)))))
(cl-assert (and buf (buffer-live-p buf)))
- (pop-to-buffer-same-window buf)
+ (pop-to-buffer buf display-comint-buffer-action)
(unless (derived-mode-p 'eshell-mode)
(eshell-mode))
buf))
@@ -332,9 +332,9 @@ With prefix ARG, insert output into the current buffer at point."
;; make the output as attractive as possible, with no
;; extraneous newlines
(when intr
- (if (eshell-interactive-process)
- (eshell-wait-for-process (eshell-interactive-process)))
- (cl-assert (not (eshell-interactive-process)))
+ (if (eshell-interactive-process-p)
+ (eshell-wait-for-process (eshell-tail-process)))
+ (cl-assert (not (eshell-interactive-process-p)))
(goto-char (point-max))
(while (and (bolp) (not (bobp)))
(delete-char -1)))