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.el37
-rw-r--r--lisp/eshell/em-cmpl.el26
-rw-r--r--lisp/eshell/em-dirs.el4
-rw-r--r--lisp/eshell/em-extpipe.el190
-rw-r--r--lisp/eshell/em-hist.el92
-rw-r--r--lisp/eshell/em-pred.el18
-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.el30
-rw-r--r--lisp/eshell/esh-cmd.el232
-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.el18
-rw-r--r--lisp/eshell/esh-var.el130
-rw-r--r--lisp/eshell/eshell.el8
22 files changed, 831 insertions, 421 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..ba868cee59e 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."
diff --git a/lisp/eshell/em-cmpl.el b/lisp/eshell/em-cmpl.el
index 706eb8aede0..b79475f6e07 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.
@@ -316,7 +314,7 @@ to writing a completion function."
(defun eshell-complete-parse-arguments ()
"Parse the command line arguments for `pcomplete-argument'."
(when (and eshell-no-completion-during-jobs
- (eshell-interactive-process))
+ (eshell-interactive-process-p))
(insert-and-inherit "\t")
(throw 'pcompleted t))
(let ((end (point-marker))
diff --git a/lisp/eshell/em-dirs.el b/lisp/eshell/em-dirs.el
index 893cad7b4fb..3998026d7f4 100644
--- a/lisp/eshell/em-dirs.el
+++ b/lisp/eshell/em-dirs.el
@@ -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-extpipe.el b/lisp/eshell/em-extpipe.el
new file mode 100644
index 00000000000..eb5b3bfe1df
--- /dev/null
+++ b/lisp/eshell/em-extpipe.el
@@ -0,0 +1,190 @@
+;;; 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))
+
+(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 (or (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-hist.el b/lisp/eshell/em-hist.el
index 49b811eae37..16abf044899 100644
--- a/lisp/eshell/em-hist.el
+++ b/lisp/eshell/em-hist.el
@@ -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)
diff --git a/lisp/eshell/em-pred.el b/lisp/eshell/em-pred.el
index 4f4e85c1a69..970329e12a9 100644
--- a/lisp/eshell/em-pred.el
+++ b/lisp/eshell/em-pred.el
@@ -107,9 +107,8 @@ 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)
@@ -144,9 +143,8 @@ The format of each entry is
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)
+ :type '(repeat (cons character sexp))
+ :risky t)
(defvar eshell-predicate-help-string
"Eshell predicate quick reference:
@@ -225,11 +223,9 @@ FOR LISTS OF ARGUMENTS:
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-keymap eshell-pred-mode-map
+ "C-c M-q" #'eshell-display-predicate-help
+ "C-c M-m" #'eshell-display-modifier-help)
;;; Functions:
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..ee3f907f85c 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:
@@ -356,6 +354,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>'.
diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el
index 554e3a5c1d9..8be1136e311 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)
@@ -258,9 +259,8 @@ the command."
(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 +279,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 +314,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 +325,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 +350,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 +394,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 +810,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 +826,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)))
+ (setq headproc (or proc headproc))
+ (setq tailproc (or tailproc proc))
+ proc))))))
(defmacro eshell-do-pipelines-synchronously (pipeline)
"Execute the commands in PIPELINE in sequence synchronously.
@@ -822,7 +870,7 @@ 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 tailproc)
(progn
,(if (fboundp 'make-process)
`(eshell-do-pipelines ,pipeline)
@@ -832,7 +880,7 @@ 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 headproc tailproc)))))
(defmacro eshell-as-subcommand (command)
"Execute COMMAND using a temp buffer.
@@ -904,21 +952,55 @@ 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-command (command &optional input)
"Evaluate the given COMMAND iteratively."
@@ -958,24 +1040,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 +1220,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 +1319,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 +1344,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..0961e214f4f 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; otherwsie, 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..788404fc43a 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)
@@ -609,6 +609,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..ca4cbd744c1 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
@@ -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)))))
+ (const :tag "Use only in Eshell" nil))))
+ :risky t)
-(put 'eshell-variable-aliases-list 'risky-local-variable 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:
@@ -419,7 +408,7 @@ process any indices that come after the variable reference."
(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))
+ value `(let ((indices (eshell-eval-indices ',indices))) ,value))
(if get-len
`(length ,value)
value)))
@@ -443,18 +432,18 @@ 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)))))
+ indices)
(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 +455,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,15 +466,28 @@ Possible options are:
;; properly. See bug#54190.
(list (function (lambda ()
(delete-file ,temp))))))
- (quote ,temp)))
+ (eshell-apply-indices ,temp indices)))
(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)
(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)))))
((assoc (char-to-string (char-after))
eshell-variable-aliases-list)
(forward-char)
@@ -498,19 +502,29 @@ 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-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)
"Get the value for the variable NAME."
(let* ((alias (assoc name eshell-variable-aliases-list))
@@ -557,13 +571,11 @@ 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
(split-string value separator)))))
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)))