diff options
Diffstat (limited to 'lisp/eshell')
-rw-r--r-- | lisp/eshell/em-banner.el | 3 | ||||
-rw-r--r-- | lisp/eshell/em-basic.el | 37 | ||||
-rw-r--r-- | lisp/eshell/em-cmpl.el | 26 | ||||
-rw-r--r-- | lisp/eshell/em-dirs.el | 4 | ||||
-rw-r--r-- | lisp/eshell/em-extpipe.el | 190 | ||||
-rw-r--r-- | lisp/eshell/em-hist.el | 92 | ||||
-rw-r--r-- | lisp/eshell/em-pred.el | 18 | ||||
-rw-r--r-- | lisp/eshell/em-prompt.el | 8 | ||||
-rw-r--r-- | lisp/eshell/em-rebind.el | 8 | ||||
-rw-r--r-- | lisp/eshell/em-script.el | 18 | ||||
-rw-r--r-- | lisp/eshell/em-term.el | 2 | ||||
-rw-r--r-- | lisp/eshell/em-tramp.el | 118 | ||||
-rw-r--r-- | lisp/eshell/esh-arg.el | 30 | ||||
-rw-r--r-- | lisp/eshell/esh-cmd.el | 232 | ||||
-rw-r--r-- | lisp/eshell/esh-io.el | 32 | ||||
-rw-r--r-- | lisp/eshell/esh-mode.el | 94 | ||||
-rw-r--r-- | lisp/eshell/esh-module.el | 1 | ||||
-rw-r--r-- | lisp/eshell/esh-opt.el | 109 | ||||
-rw-r--r-- | lisp/eshell/esh-proc.el | 74 | ||||
-rw-r--r-- | lisp/eshell/esh-util.el | 18 | ||||
-rw-r--r-- | lisp/eshell/esh-var.el | 130 | ||||
-rw-r--r-- | lisp/eshell/eshell.el | 8 |
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))) |