summaryrefslogtreecommitdiff
path: root/lisp/eshell
diff options
context:
space:
mode:
authorStefan Monnier <monnier@iro.umontreal.ca>2022-09-25 16:15:16 -0400
committerStefan Monnier <monnier@iro.umontreal.ca>2022-09-25 16:15:16 -0400
commit650c20f1ca4e07591a727e1cfcc74b3363d15985 (patch)
tree85d11f6437cde22f410c25e0e5f71a3131ebd07d /lisp/eshell
parent8869332684c2302b5ba1ead4568bbc7ba1c0183e (diff)
parent4b85ae6a24380fb67a3315eaec9233f17a872473 (diff)
downloademacs-650c20f1ca4e07591a727e1cfcc74b3363d15985.tar.gz
emacs-650c20f1ca4e07591a727e1cfcc74b3363d15985.tar.bz2
emacs-650c20f1ca4e07591a727e1cfcc74b3363d15985.zip
Merge 'master' into noverlay
Diffstat (limited to 'lisp/eshell')
-rw-r--r--lisp/eshell/em-alias.el12
-rw-r--r--lisp/eshell/em-banner.el7
-rw-r--r--lisp/eshell/em-basic.el106
-rw-r--r--lisp/eshell/em-cmpl.el472
-rw-r--r--lisp/eshell/em-dirs.el176
-rw-r--r--lisp/eshell/em-elecslash.el120
-rw-r--r--lisp/eshell/em-extpipe.el204
-rw-r--r--lisp/eshell/em-glob.el247
-rw-r--r--lisp/eshell/em-hist.el299
-rw-r--r--lisp/eshell/em-ls.el182
-rw-r--r--lisp/eshell/em-pred.el485
-rw-r--r--lisp/eshell/em-prompt.el33
-rw-r--r--lisp/eshell/em-rebind.el27
-rw-r--r--lisp/eshell/em-script.el48
-rw-r--r--lisp/eshell/em-smart.el39
-rw-r--r--lisp/eshell/em-term.el84
-rw-r--r--lisp/eshell/em-tramp.el131
-rw-r--r--lisp/eshell/em-unix.el186
-rw-r--r--lisp/eshell/em-xtra.el78
-rw-r--r--lisp/eshell/esh-arg.el310
-rw-r--r--lisp/eshell/esh-cmd.el552
-rw-r--r--lisp/eshell/esh-ext.el35
-rw-r--r--lisp/eshell/esh-io.el344
-rw-r--r--lisp/eshell/esh-mode.el439
-rw-r--r--lisp/eshell/esh-module.el28
-rw-r--r--lisp/eshell/esh-opt.el172
-rw-r--r--lisp/eshell/esh-proc.el346
-rw-r--r--lisp/eshell/esh-util.el512
-rw-r--r--lisp/eshell/esh-var.el484
-rw-r--r--lisp/eshell/eshell.el110
30 files changed, 3508 insertions, 2760 deletions
diff --git a/lisp/eshell/em-alias.el b/lisp/eshell/em-alias.el
index 742234574f3..9ad218d5988 100644
--- a/lisp/eshell/em-alias.el
+++ b/lisp/eshell/em-alias.el
@@ -1,6 +1,6 @@
;;; em-alias.el --- creation and management of command aliases -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -90,7 +90,7 @@
;;; Code:
-(require 'eshell)
+(require 'esh-mode)
;;;###autoload
(progn
@@ -141,12 +141,12 @@ file named by `eshell-aliases-file'.")
(defvar eshell-failed-commands-alist nil
"An alist of command name failures.")
-(defun eshell-alias-initialize ()
+(defun eshell-alias-initialize () ;Called from `eshell-mode' via intern-soft!
"Initialize the alias handling code."
(make-local-variable 'eshell-failed-commands-alist)
- (add-hook 'eshell-alternate-command-hook 'eshell-fix-bad-commands t t)
+ (add-hook 'eshell-alternate-command-hook #'eshell-fix-bad-commands t t)
(eshell-read-aliases-list)
- (add-hook 'eshell-named-command-hook 'eshell-maybe-replace-by-alias t t)
+ (add-hook 'eshell-named-command-hook #'eshell-maybe-replace-by-alias t t)
(make-local-variable 'eshell-complex-commands)
(add-to-list 'eshell-complex-commands 'eshell-command-aliased-p))
@@ -206,7 +206,7 @@ file named by `eshell-aliases-file'.")
(let ((eshell-current-handles
(eshell-create-handles eshell-aliases-file 'overwrite)))
(eshell/alias)
- (eshell-close-handles 0))))
+ (eshell-close-handles 0 'nil))))
(defsubst eshell-lookup-alias (name)
"Check whether NAME is aliased. Return the alias if there is one."
diff --git a/lisp/eshell/em-banner.el b/lisp/eshell/em-banner.el
index 268b4289f4a..a2f8a58220c 100644
--- a/lisp/eshell/em-banner.el
+++ b/lisp/eshell/em-banner.el
@@ -1,6 +1,6 @@
;;; em-banner.el --- sample module that displays a login banner -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -61,17 +61,16 @@ 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
:type 'hook
:group 'eshell-banner)
-(defun eshell-banner-initialize ()
+(defun eshell-banner-initialize () ;Called from `eshell-mode' via intern-soft!
"Output a welcome banner on initialization."
;; it's important to use `eshell-interactive-print' rather than
;; `insert', because `insert' doesn't know how to interact with the
diff --git a/lisp/eshell/em-basic.el b/lisp/eshell/em-basic.el
index 33ce3b5e93a..448b6787ee7 100644
--- a/lisp/eshell/em-basic.el
+++ b/lisp/eshell/em-basic.el
@@ -1,6 +1,6 @@
;;; em-basic.el --- basic shell builtin commands -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -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) "")
@@ -90,11 +94,10 @@ or `eshell-printn' for display."
(car args))
(t
(mapcar
- (function
- (lambda (arg)
- (if (stringp arg)
- (set-text-properties 0 (length arg) nil arg))
- arg))
+ (lambda (arg)
+ (if (stringp arg)
+ (set-text-properties 0 (length arg) nil arg))
+ arg)
args)))))
(if output-newline
(cond
@@ -110,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."
- (let ((elems (eshell-flatten-list args)))
- (while elems
- (eshell-printn (eshell-echo (list (car elems))))
- (setq elems (cdr elems)))))
+ "Print out each of the arguments as strings, separated by newlines."
+ (let ((elems (flatten-tree args)))
+ (dolist (elem elems)
+ (eshell-printn (eshell-stringify elem)))))
(defun eshell/listify (&rest args)
"Return the argument(s) as a single list."
@@ -137,39 +155,37 @@ or `eshell-printn' for display."
"umask" args
'((?S "symbolic" nil symbolic-p "display umask symbolically")
(?h "help" nil nil "display this usage message")
+ :preserve-args
:usage "[-S] [mode]")
- (if (or (not args) symbolic-p)
- (let ((modstr
- (concat "000"
- (format "%o"
- (logand (lognot (default-file-modes))
- 511)))))
- (setq modstr (substring modstr (- (length modstr) 3)))
- (when symbolic-p
- (let ((mode (default-file-modes)))
- (setq modstr
- (format
- "u=%s,g=%s,o=%s"
- (concat (and (= (logand mode 64) 64) "r")
- (and (= (logand mode 128) 128) "w")
- (and (= (logand mode 256) 256) "x"))
- (concat (and (= (logand mode 8) 8) "r")
- (and (= (logand mode 16) 16) "w")
- (and (= (logand mode 32) 32) "x"))
- (concat (and (= (logand mode 1) 1) "r")
- (and (= (logand mode 2) 2) "w")
- (and (= (logand mode 4) 4) "x"))))))
- (eshell-printn modstr))
- (setcar args (eshell-convert (car args)))
- (if (numberp (car args))
- (set-default-file-modes
- (- 511 (car (read-from-string
- (concat "?\\" (number-to-string (car args)))))))
- (error "setting umask symbolically is not yet implemented"))
+ (cond
+ (symbolic-p
+ (let ((mode (default-file-modes)))
+ (eshell-printn
+ (format "u=%s,g=%s,o=%s"
+ (concat (and (= (logand mode 64) 64) "r")
+ (and (= (logand mode 128) 128) "w")
+ (and (= (logand mode 256) 256) "x"))
+ (concat (and (= (logand mode 8) 8) "r")
+ (and (= (logand mode 16) 16) "w")
+ (and (= (logand mode 32) 32) "x"))
+ (concat (and (= (logand mode 1) 1) "r")
+ (and (= (logand mode 2) 2) "w")
+ (and (= (logand mode 4) 4) "x"))))))
+ ((not args)
+ (eshell-printn (format "%03o" (logand (lognot (default-file-modes))
+ #o777))))
+ (t
+ (when (stringp (car args))
+ (if (string-match "^[0-7]+$" (car args))
+ (setcar args (string-to-number (car args) 8))
+ (error "Setting umask symbolically is not yet implemented")))
+ (set-default-file-modes (- #o777 (car args)))
(eshell-print
- "Warning: umask changed for all new files created by Emacs.\n"))
+ "Warning: umask changed for all new files created by Emacs.\n")))
nil))
+(put 'eshell/umask 'eshell-no-numeric-conversions t)
+
(provide 'em-basic)
;; Local Variables:
diff --git a/lisp/eshell/em-cmpl.el b/lisp/eshell/em-cmpl.el
index c64c2df3427..ac82e3f225c 100644
--- a/lisp/eshell/em-cmpl.el
+++ b/lisp/eshell/em-cmpl.el
@@ -1,6 +1,6 @@
;;; em-cmpl.el --- completion using the TAB key -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -72,6 +72,7 @@
(require 'esh-mode)
(require 'esh-util)
+(require 'em-dirs)
(eval-when-compile
(require 'cl-lib)
@@ -91,31 +92,30 @@ variable names, arguments, etc."
(defcustom eshell-cmpl-load-hook nil
"A list of functions to run when `eshell-cmpl' is loaded."
:version "24.1" ; removed eshell-cmpl-initialize
- :type 'hook
- :group 'eshell-cmpl)
+ :type 'hook)
(defcustom eshell-show-lisp-completions nil
"If non-nil, include Lisp functions in the command completion list.
If this variable is nil, Lisp completion can still be done in command
position by using M-TAB instead of TAB."
- :type 'boolean
- :group 'eshell-cmpl)
+ :type 'boolean)
(defcustom eshell-show-lisp-alternatives t
"If non-nil, and no other completions found, show Lisp functions.
Setting this variable means nothing if `eshell-show-lisp-completions'
is non-nil."
- :type 'boolean
- :group 'eshell-cmpl)
+ :type 'boolean)
(defcustom eshell-no-completion-during-jobs t
"If non-nil, don't allow completion while a process is running."
- :type 'boolean
- :group 'eshell-cmpl)
+ :type 'boolean)
(defcustom eshell-command-completions-alist
'(("acroread" . "\\.pdf\\'")
("xpdf" . "\\.pdf\\'")
+ ("gunzip" . "\\.t?gz\\'")
+ ("bunzip2" . "\\.t?bz2\\'")
+ ("unxz" . "\\.t?xz\\'")
("ar" . "\\.[ao]\\'")
("gcc" . "\\.[Cc]\\([Cc]\\|[Pp][Pp]\\)?\\'")
("g++" . "\\.[Cc]\\([Cc]\\|[Pp][Pp]\\)?\\'")
@@ -133,184 +133,155 @@ is non-nil."
"An alist that defines simple argument type correlations.
This is provided for common commands, as a simplistic alternative
to writing a completion function."
- :type '(repeat (cons string regexp))
- :group 'eshell-cmpl)
+ :type '(repeat (cons string regexp)))
+
+(defun eshell-cmpl--custom-variable-docstring (pcomplete-var)
+ "Generate the docstring of a variable derived from a pcomplete-* variable."
+ (format "%s\n\nIts value is assigned to `%s' locally after eshell starts."
+ (documentation-property pcomplete-var
+ 'variable-documentation t)
+ (symbol-name pcomplete-var)))
(defcustom eshell-cmpl-file-ignore "~\\'"
- (documentation-property 'pcomplete-file-ignore
- 'variable-documentation)
- :type (get 'pcomplete-file-ignore 'custom-type)
- :group 'eshell-cmpl)
+ (eshell-cmpl--custom-variable-docstring 'pcomplete-file-ignore)
+ :type (get 'pcomplete-file-ignore 'custom-type))
(defcustom eshell-cmpl-dir-ignore "\\`\\(\\.\\.?\\|CVS\\)/\\'"
- (documentation-property 'pcomplete-dir-ignore
- 'variable-documentation)
- :type (get 'pcomplete-dir-ignore 'custom-type)
- :group 'eshell-cmpl)
+ (eshell-cmpl--custom-variable-docstring 'pcomplete-dir-ignore)
+ :type (get 'pcomplete-dir-ignore 'custom-type))
(defcustom eshell-cmpl-ignore-case (eshell-under-windows-p)
- (documentation-property 'pcomplete-ignore-case
- 'variable-documentation)
- :type (get 'pcomplete-ignore-case 'custom-type)
- :group 'eshell-cmpl)
+ (eshell-cmpl--custom-variable-docstring 'completion-ignore-case)
+ :type (get 'completion-ignore-case 'custom-type))
(defcustom eshell-cmpl-autolist nil
- (documentation-property 'pcomplete-autolist
- 'variable-documentation)
- :type (get 'pcomplete-autolist 'custom-type)
- :group 'eshell-cmpl)
-
-(defcustom eshell-cmpl-suffix-list (list ?/ ?:)
- (documentation-property 'pcomplete-suffix-list
- 'variable-documentation)
- :type (get 'pcomplete-suffix-list 'custom-type)
- :group 'pcomplete)
+ (eshell-cmpl--custom-variable-docstring 'pcomplete-autolist)
+ :type (get 'pcomplete-autolist 'custom-type))
(defcustom eshell-cmpl-recexact nil
- (documentation-property 'pcomplete-recexact
- 'variable-documentation)
- :type (get 'pcomplete-recexact 'custom-type)
- :group 'eshell-cmpl)
-
-(defcustom eshell-cmpl-man-function 'man
- (documentation-property 'pcomplete-man-function
- 'variable-documentation)
- :type (get 'pcomplete-man-function 'custom-type)
- :group 'eshell-cmpl)
-
-(defcustom eshell-cmpl-compare-entry-function 'file-newer-than-file-p
- (documentation-property 'pcomplete-compare-entry-function
- 'variable-documentation)
- :type (get 'pcomplete-compare-entry-function 'custom-type)
- :group 'eshell-cmpl)
+ (eshell-cmpl--custom-variable-docstring 'pcomplete-recexact)
+ :type (get 'pcomplete-recexact 'custom-type))
+
+(defcustom eshell-cmpl-man-function #'man
+ (eshell-cmpl--custom-variable-docstring 'pcomplete-man-function)
+ :type (get 'pcomplete-man-function 'custom-type))
+
+(defcustom eshell-cmpl-compare-entry-function #'file-newer-than-file-p
+ (eshell-cmpl--custom-variable-docstring 'pcomplete-compare-entry-function)
+ :type (get 'pcomplete-compare-entry-function 'custom-type))
(defcustom eshell-cmpl-expand-before-complete nil
- (documentation-property 'pcomplete-expand-before-complete
- 'variable-documentation)
- :type (get 'pcomplete-expand-before-complete 'custom-type)
- :group 'eshell-cmpl)
+ (eshell-cmpl--custom-variable-docstring 'pcomplete-expand-before-complete)
+ :type (get 'pcomplete-expand-before-complete 'custom-type))
(defcustom eshell-cmpl-cycle-completions t
- (documentation-property 'pcomplete-cycle-completions
- 'variable-documentation)
- :type (get 'pcomplete-cycle-completions 'custom-type)
- :group 'eshell-cmpl)
+ (eshell-cmpl--custom-variable-docstring 'pcomplete-cycle-completions)
+ :type (get 'pcomplete-cycle-completions 'custom-type))
(defcustom eshell-cmpl-cycle-cutoff-length 5
- (documentation-property 'pcomplete-cycle-cutoff-length
- 'variable-documentation)
- :type (get 'pcomplete-cycle-cutoff-length 'custom-type)
- :group 'eshell-cmpl)
+ (eshell-cmpl--custom-variable-docstring 'pcomplete-cycle-cutoff-length)
+ :type (get 'pcomplete-cycle-cutoff-length 'custom-type))
(defcustom eshell-cmpl-restore-window-delay 1
- (documentation-property 'pcomplete-restore-window-delay
- 'variable-documentation)
- :type (get 'pcomplete-restore-window-delay 'custom-type)
- :group 'eshell-cmpl)
+ (eshell-cmpl--custom-variable-docstring 'pcomplete-restore-window-delay)
+ :type (get 'pcomplete-restore-window-delay 'custom-type))
(defcustom eshell-command-completion-function
- (function
- (lambda ()
- (pcomplete-here (eshell-complete-commands-list))))
- (documentation-property 'pcomplete-command-completion-function
- 'variable-documentation)
- :type (get 'pcomplete-command-completion-function 'custom-type)
- :group 'eshell-cmpl)
+ (lambda ()
+ (pcomplete-here (eshell--complete-commands-list)))
+ (eshell-cmpl--custom-variable-docstring 'pcomplete-command-completion-function)
+ :type (get 'pcomplete-command-completion-function 'custom-type))
(defcustom eshell-cmpl-command-name-function
- 'eshell-completion-command-name
- (documentation-property 'pcomplete-command-name-function
- 'variable-documentation)
- :type (get 'pcomplete-command-name-function 'custom-type)
- :group 'eshell-cmpl)
+ #'eshell-completion-command-name
+ (eshell-cmpl--custom-variable-docstring 'pcomplete-command-name-function)
+ :type (get 'pcomplete-command-name-function 'custom-type))
(defcustom eshell-default-completion-function
- (function
- (lambda ()
- (while (pcomplete-here
- (pcomplete-dirs-or-entries
- (cdr (assoc (funcall eshell-cmpl-command-name-function)
- eshell-command-completions-alist)))))))
- (documentation-property 'pcomplete-default-completion-function
- 'variable-documentation)
- :type (get 'pcomplete-default-completion-function 'custom-type)
- :group 'eshell-cmpl)
+ (lambda ()
+ (while (pcomplete-here
+ (pcomplete-dirs-or-entries
+ (cdr (assoc (funcall eshell-cmpl-command-name-function)
+ eshell-command-completions-alist))))))
+ (eshell-cmpl--custom-variable-docstring 'pcomplete-default-completion-function)
+ :type (get 'pcomplete-default-completion-function 'custom-type))
(defcustom eshell-cmpl-use-paring t
- (documentation-property 'pcomplete-use-paring 'variable-documentation)
- :type (get 'pcomplete-use-paring 'custom-type)
- :group 'eshell-cmpl)
+ (eshell-cmpl--custom-variable-docstring 'pcomplete-use-paring)
+ :type (get 'pcomplete-use-paring 'custom-type))
;;; Functions:
(defun eshell-complete-lisp-symbol ()
"Try to complete the text around point as a Lisp symbol."
(interactive)
- (let ((completion-at-point-functions '(lisp-completion-at-point)))
+ (let ((completion-at-point-functions '(elisp-completion-at-point)))
(completion-at-point)))
-(defun eshell-cmpl-initialize ()
+(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.
+
+\\{eshell-cmpl-mode-map}"
+ :keymap eshell-cmpl-mode-map)
+
+(defun eshell-cmpl-initialize () ;Called from `eshell-mode' via intern-soft!
"Initialize the completions module."
- (set (make-local-variable 'pcomplete-command-completion-function)
- eshell-command-completion-function)
- (set (make-local-variable 'pcomplete-command-name-function)
- eshell-cmpl-command-name-function)
- (set (make-local-variable 'pcomplete-default-completion-function)
- eshell-default-completion-function)
- (set (make-local-variable 'pcomplete-parse-arguments-function)
- 'eshell-complete-parse-arguments)
- (set (make-local-variable 'pcomplete-file-ignore)
- eshell-cmpl-file-ignore)
- (set (make-local-variable 'pcomplete-dir-ignore)
- eshell-cmpl-dir-ignore)
- (set (make-local-variable 'pcomplete-ignore-case)
- eshell-cmpl-ignore-case)
- (set (make-local-variable 'pcomplete-autolist)
- eshell-cmpl-autolist)
- (set (make-local-variable 'pcomplete-suffix-list)
- eshell-cmpl-suffix-list)
- (set (make-local-variable 'pcomplete-recexact)
- eshell-cmpl-recexact)
- (set (make-local-variable 'pcomplete-man-function)
- eshell-cmpl-man-function)
- (set (make-local-variable 'pcomplete-compare-entry-function)
- eshell-cmpl-compare-entry-function)
- (set (make-local-variable 'pcomplete-expand-before-complete)
- eshell-cmpl-expand-before-complete)
- (set (make-local-variable 'pcomplete-cycle-completions)
- eshell-cmpl-cycle-completions)
- (set (make-local-variable 'pcomplete-cycle-cutoff-length)
- eshell-cmpl-cycle-cutoff-length)
- (set (make-local-variable 'pcomplete-restore-window-delay)
- eshell-cmpl-restore-window-delay)
- (set (make-local-variable 'pcomplete-use-paring)
- eshell-cmpl-use-paring)
+ (setq-local pcomplete-command-completion-function
+ eshell-command-completion-function)
+ (setq-local pcomplete-command-name-function
+ eshell-cmpl-command-name-function)
+ (setq-local pcomplete-default-completion-function
+ eshell-default-completion-function)
+ (setq-local pcomplete-parse-arguments-function
+ #'eshell-complete-parse-arguments)
+ (setq-local pcomplete-file-ignore
+ eshell-cmpl-file-ignore)
+ (setq-local pcomplete-dir-ignore
+ eshell-cmpl-dir-ignore)
+ (setq-local completion-ignore-case
+ eshell-cmpl-ignore-case)
+ (setq-local pcomplete-autolist
+ eshell-cmpl-autolist)
+ (setq-local pcomplete-recexact
+ eshell-cmpl-recexact)
+ (setq-local pcomplete-man-function
+ eshell-cmpl-man-function)
+ (setq-local pcomplete-compare-entry-function
+ eshell-cmpl-compare-entry-function)
+ (setq-local pcomplete-expand-before-complete
+ eshell-cmpl-expand-before-complete)
+ (setq-local pcomplete-cycle-completions
+ eshell-cmpl-cycle-completions)
+ (setq-local pcomplete-cycle-cutoff-length
+ eshell-cmpl-cycle-cutoff-length)
+ (setq-local pcomplete-restore-window-delay
+ eshell-cmpl-restore-window-delay)
+ (setq-local pcomplete-use-paring
+ eshell-cmpl-use-paring)
;; `comint-file-name-quote-list' should only be set after all the
;; load-hooks for any other extension modules have been run, which
;; is true at the time `eshell-mode-hook' is run
(add-hook 'eshell-mode-hook
- (function
- (lambda ()
- (set (make-local-variable 'comint-file-name-quote-list)
- eshell-special-chars-outside-quoting))) nil t)
- (add-hook 'pcomplete-quote-arg-hook 'eshell-quote-backslash nil t)
- (define-key eshell-mode-map [(meta tab)] 'eshell-complete-lisp-symbol)
- (define-key eshell-mode-map [(meta control ?i)] 'eshell-complete-lisp-symbol)
- (define-key eshell-command-map [(meta ?h)] 'eshell-completion-help)
- (define-key eshell-command-map [tab] 'pcomplete-expand-and-complete)
- (define-key eshell-command-map [(control ?i)]
- 'pcomplete-expand-and-complete)
- (define-key eshell-command-map [space] 'pcomplete-expand)
- (define-key eshell-command-map [? ] 'pcomplete-expand)
- (define-key eshell-mode-map [tab] 'eshell-pcomplete)
- (define-key eshell-mode-map [(control ?i)] 'eshell-pcomplete)
+ (lambda ()
+ (setq-local comint-file-name-quote-list
+ eshell-special-chars-outside-quoting))
+ nil t)
+ (add-hook 'pcomplete-quote-arg-hook #'eshell-quote-backslash nil t)
(add-hook 'completion-at-point-functions
#'pcomplete-completions-at-point nil t)
- ;; jww (1999-10-19): Will this work on anything but X?
- (if (featurep 'xemacs)
- (define-key eshell-mode-map [iso-left-tab] 'pcomplete-reverse)
- (define-key eshell-mode-map [backtab] 'pcomplete-reverse))
- (define-key eshell-mode-map [(meta ??)] 'pcomplete-list))
+ (eshell-cmpl-mode))
(defun eshell-completion-command-name ()
"Return the command name, possibly sans globbing."
@@ -329,18 +300,24 @@ to writing a completion function."
(describe-prefix-bindings)
(call-interactively 'pcomplete-help)))
+(defun eshell--pcomplete-insert-tab ()
+ (if (not pcomplete-allow-modifications)
+ (throw 'pcompleted nil)
+ (insert-and-inherit "\t")
+ (throw 'pcompleted t)))
+
(defun eshell-complete-parse-arguments ()
"Parse the command line arguments for `pcomplete-argument'."
(when (and eshell-no-completion-during-jobs
- (eshell-interactive-process))
- (insert-and-inherit "\t")
- (throw 'pcompleted t))
+ (eshell-interactive-process-p))
+ (eshell--pcomplete-insert-tab))
(let ((end (point-marker))
(begin (save-excursion (eshell-bol) (point)))
(posns (list t))
args delim)
- (when (memq this-command '(pcomplete-expand
- pcomplete-expand-and-complete))
+ (when (and pcomplete-allow-modifications
+ (memq this-command '(pcomplete-expand
+ pcomplete-expand-and-complete)))
(run-hook-with-args 'eshell-expand-input-functions begin end)
(if (= begin end)
(end-of-line))
@@ -353,14 +330,11 @@ to writing a completion function."
(setq begin (1+ (cadr delim))
args (eshell-parse-arguments begin end)))
((eq (car delim) ?\()
- (eshell-complete-lisp-symbol)
- (throw 'pcompleted t))
+ (throw 'pcompleted (elisp-completion-at-point)))
(t
- (insert-and-inherit "\t")
- (throw 'pcompleted t))))
+ (eshell--pcomplete-insert-tab))))
(when (get-text-property (1- end) 'comment)
- (insert-and-inherit "\t")
- (throw 'pcompleted t))
+ (eshell--pcomplete-insert-tab))
(let ((pos begin))
(while (< pos end)
(if (get-text-property pos 'arg-begin)
@@ -385,95 +359,109 @@ to writing a completion function."
(nconc args (list ""))
(nconc posns (list (point))))
(cons (mapcar
- (function
- (lambda (arg)
- (let ((val
- (if (listp arg)
- (let ((result
- (eshell-do-eval
- (list 'eshell-commands arg) t)))
- (cl-assert (eq (car result) 'quote))
- (cadr result))
- arg)))
- (if (numberp val)
- (setq val (number-to-string val)))
- (or val ""))))
+ (lambda (arg)
+ (let ((val
+ (if (listp arg)
+ (let ((result
+ (eshell-do-eval
+ (list 'eshell-commands arg) t)))
+ (cl-assert (eq (car result) 'quote))
+ (cadr result))
+ arg)))
+ (cond ((numberp val)
+ (setq val (number-to-string val)))
+ ;; expand .../ etc that only eshell understands to
+ ;; standard ../../
+ ((and (stringp val)) (string-match "\\.\\.\\.+/" val)
+ (setq val (eshell-expand-multiple-dots val))))
+ (or val "")))
args)
posns)))
-(defun eshell-complete-commands-list ()
+(defun eshell--pcomplete-executables ()
+ "Complete amongst a list of directories and executables.
+
+Wrapper for `pcomplete-executables' or `pcomplete-dirs-or-entries',
+depending on the value of `eshell-force-execution'.
+
+Adds path prefix to candidates independent of `action' value."
+ ;; `pcomplete-entries' returns filenames without path on `action' to
+ ;; use current string directory as done in `completion-file-name-table'
+ ;; when `action' is nil to construct executable candidates.
+ (let ((table (if eshell-force-execution
+ (pcomplete-dirs-or-entries nil #'file-readable-p)
+ (pcomplete-executables))))
+ (lambda (string pred action)
+ (let ((cands (funcall table string pred action)))
+ (if (eq action t)
+ (let ((specdir (file-name-directory string)))
+ (mapcar
+ (lambda (cand)
+ (if (stringp cand)
+ (file-name-concat specdir cand)
+ cand))
+ cands))
+ cands)))))
+
+(defun eshell--complete-commands-list ()
"Generate list of applicable, visible commands."
- (let ((filename (pcomplete-arg)) glob-name)
- (if (file-name-directory filename)
- (if eshell-force-execution
- (pcomplete-dirs-or-entries nil 'file-readable-p)
- (pcomplete-executables))
- (if (and (> (length filename) 0)
- (eq (aref filename 0) eshell-explicit-command-char))
- (setq filename (substring filename 1)
- pcomplete-stub filename
- glob-name t))
- (let* ((paths (eshell-parse-colon-path eshell-path-env))
- (cwd (file-name-as-directory
- (expand-file-name default-directory)))
- (path "") (comps-in-path ())
- (file "") (filepath "") (completions ()))
- (if (eshell-under-windows-p)
- (push "." paths))
- ;; Go thru each path in the search path, finding completions.
- (while paths
- (setq path (file-name-as-directory
- (expand-file-name (or (car paths) ".")))
- comps-in-path
- (and (file-accessible-directory-p path)
- (file-name-all-completions filename path)))
- ;; Go thru each completion found, to see whether it should
- ;; be used.
- (while comps-in-path
- (setq file (car comps-in-path)
- filepath (concat path file))
- (if (and (not (member file completions)) ;
- (or (string-equal path cwd)
- (not (file-directory-p filepath)))
- (if eshell-force-execution
- (file-readable-p filepath)
- (file-executable-p filepath)))
- (setq completions (cons file completions)))
- (setq comps-in-path (cdr comps-in-path)))
- (setq paths (cdr paths)))
- ;; Add aliases which are currently visible, and Lisp functions.
- (pcomplete-uniqify-list
- (if glob-name
- completions
- (setq completions
- (append (and (eshell-using-module 'eshell-alias)
- (funcall (symbol-function 'eshell-alias-completions)
- filename))
- (eshell-winnow-list
- (mapcar
- (function
- (lambda (name)
- (substring name 7)))
- (all-completions (concat "eshell/" filename)
- obarray 'functionp))
- nil '(eshell-find-alias-function))
- completions))
- (append (and (or eshell-show-lisp-completions
- (and eshell-show-lisp-alternatives
- (null completions)))
- (all-completions filename obarray 'functionp))
- completions)))))))
-
-(defun eshell-pcomplete (&optional interactively)
- "Eshell wrapper for `pcomplete'."
- (interactive "p")
- ;; Pretend to be pcomplete so that cycling works (bug#13293).
- (setq this-command 'pcomplete)
- (condition-case nil
- (if interactively
- (call-interactively 'pcomplete)
- (pcomplete))
- (text-read-only (completion-at-point)))) ; Workaround for bug#12838.
+ ;; Building the commands list can take quite a while, especially over Tramp
+ ;; (bug#41423), so do it lazily.
+ (let ((glob-name
+ ;; When a command is specified using `eshell-explicit-command-char',
+ ;; that char is not part of the command and hence not part of what
+ ;; we complete. Adjust `pcomplete-stub' accordingly!
+ (if (and (> (length pcomplete-stub) 0)
+ (eq (aref pcomplete-stub 0) eshell-explicit-command-char))
+ (setq pcomplete-stub (substring pcomplete-stub 1)))))
+ (completion-table-dynamic
+ (lambda (filename)
+ (if (file-name-directory filename)
+ (eshell--pcomplete-executables)
+ (let* ((paths (eshell-get-path))
+ (cwd (file-name-as-directory
+ (expand-file-name default-directory)))
+ (filepath "") (completions ()))
+ ;; Go thru each path in the search path, finding completions.
+ (dolist (path paths)
+ (setq path (file-name-as-directory
+ (expand-file-name (or path "."))))
+ ;; Go thru each completion found, to see whether it should
+ ;; be used.
+ (dolist (file (and (file-accessible-directory-p path)
+ (file-name-all-completions filename path)))
+ (setq filepath (concat path file))
+ (if (and (not (member file completions)) ;
+ (or (string-equal path cwd)
+ (not (file-directory-p filepath)))
+ ;; FIXME: Those repeated file tests end up
+ ;; very costly over Tramp, we should cache the result.
+ (if eshell-force-execution
+ (file-readable-p filepath)
+ (file-executable-p filepath)))
+ (push file completions))))
+ ;; Add aliases which are currently visible, and Lisp functions.
+ (pcomplete-uniquify-list
+ (if glob-name
+ completions
+ (setq completions
+ (append (if (fboundp 'eshell-alias-completions)
+ (eshell-alias-completions filename))
+ (eshell-winnow-list
+ (mapcar
+ (lambda (name)
+ (substring name 7))
+ (all-completions (concat "eshell/" filename)
+ obarray #'functionp))
+ nil '(eshell-find-alias-function))
+ completions))
+ (append (and (or eshell-show-lisp-completions
+ (and eshell-show-lisp-alternatives
+ (null completions)))
+ (all-completions filename obarray #'functionp))
+ completions)))))))))
+
+(define-obsolete-function-alias 'eshell-pcomplete #'completion-at-point "27.1")
(provide 'em-cmpl)
diff --git a/lisp/eshell/em-dirs.el b/lisp/eshell/em-dirs.el
index 0d87f2a599e..00880b9f281 100644
--- a/lisp/eshell/em-dirs.el
+++ b/lisp/eshell/em-dirs.el
@@ -1,6 +1,6 @@
;;; em-dirs.el --- directory navigation commands -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -42,7 +42,8 @@
;;; Code:
-(require 'eshell)
+(require 'esh-mode) ;For eshell-directory-name
+(require 'esh-var) ;For eshell-variable-aliases-list
(require 'ring)
(require 'esh-opt)
@@ -62,12 +63,11 @@ they lack somewhat in feel from the typical shell equivalents."
(defcustom eshell-dirs-load-hook nil
"A hook that gets run when `eshell-dirs' is loaded."
:version "24.1" ; removed eshell-dirs-initialize
- :type 'hook
- :group 'eshell-dirs)
+ :type 'hook)
(defcustom eshell-pwd-convert-function (if (eshell-under-windows-p)
- 'expand-file-name
- 'identity)
+ #'expand-file-name
+ #'identity)
"The function used to normalize the value of Eshell's `pwd'.
The value returned by `pwd' is also used when recording the
last-visited directory in the last-dir-ring, so it will affect the
@@ -75,8 +75,7 @@ form of the list used by `cd ='."
:type '(radio (function-item file-truename)
(function-item expand-file-name)
(function-item identity)
- (function :tag "Other"))
- :group 'eshell-dirs)
+ (function :tag "Other")))
(defcustom eshell-ask-to-save-last-dir 'always
"Determine if the last-dir-ring should be automatically saved.
@@ -88,63 +87,53 @@ If set to t, always ask if any Eshell buffers are open at exit time.
If set to `always', the list-dir-ring will always be saved, silently."
:type '(choice (const :tag "Never" nil)
(const :tag "Ask" t)
- (const :tag "Always save" always))
- :group 'eshell-dirs)
+ (const :tag "Always save" always)))
(defcustom eshell-cd-shows-directory nil
"If non-nil, using `cd' will report the directory it changes to."
- :type 'boolean
- :group 'eshell-dirs)
+ :type 'boolean)
(defcustom eshell-cd-on-directory t
"If non-nil, do a cd if a directory is in command position."
- :type 'boolean
- :group 'eshell-dirs)
+ :type 'boolean)
(defcustom eshell-directory-change-hook nil
"A hook to run when the current directory changes."
- :type 'hook
- :group 'eshell-dirs)
+ :type 'hook)
(defcustom eshell-list-files-after-cd nil
"If non-nil, call \"ls\" with any remaining args after doing a cd.
This is provided for convenience, since the same effect is easily
achieved by adding a function to `eshell-directory-change-hook' that
calls \"ls\" and references `eshell-last-arguments'."
- :type 'boolean
- :group 'eshell-dirs)
+ :type 'boolean)
(defcustom eshell-pushd-tohome nil
"If non-nil, make pushd with no arg behave as `pushd ~' (like `cd').
This mirrors the optional behavior of tcsh."
- :type 'boolean
- :group 'eshell-dirs)
+ :type 'boolean)
(defcustom eshell-pushd-dextract nil
"If non-nil, make \"pushd +n\" pop the nth dir to the stack top.
This mirrors the optional behavior of tcsh."
- :type 'boolean
- :group 'eshell-dirs)
+ :type 'boolean)
(defcustom eshell-pushd-dunique nil
"If non-nil, make pushd only add unique directories to the stack.
This mirrors the optional behavior of tcsh."
- :type 'boolean
- :group 'eshell-dirs)
+ :type 'boolean)
(defcustom eshell-dirtrack-verbose t
"If non-nil, show the directory stack following directory change.
This is effective only if directory tracking is enabled."
- :type 'boolean
- :group 'eshell-dirs)
+ :type 'boolean)
(defcustom eshell-last-dir-ring-file-name
(expand-file-name "lastdir" eshell-directory-name)
"If non-nil, name of the file to read/write the last-dir-ring.
See also `eshell-read-last-dir-ring' and `eshell-write-last-dir-ring'.
If it is nil, the last-dir-ring will not be written to disk."
- :type 'file
- :group 'eshell-dirs)
+ :type 'file)
(defcustom eshell-last-dir-ring-size 32
"If non-nil, the size of the directory history ring.
@@ -164,13 +153,11 @@ directories gets pushed, and its size is unlimited.
explicitly very much, but every once in a while would like to return to
a previously visited directory without having to type in the whole
thing again."
- :type 'integer
- :group 'eshell-dirs)
+ :type 'integer)
(defcustom eshell-last-dir-unique t
"If non-nil, `eshell-last-dir-ring' contains only unique entries."
- :type 'boolean
- :group 'eshell-dirs)
+ :type 'boolean)
;;; Internal Variables:
@@ -183,44 +170,48 @@ Thus, this does not include the current directory.")
;;; Functions:
-(defun eshell-dirs-initialize ()
+(defun eshell-dirs-initialize () ;Called from `eshell-mode' via intern-soft!
"Initialize the builtin functions for Eshell."
- (make-local-variable 'eshell-variable-aliases-list)
- (setq eshell-variable-aliases-list
+ (setq-local eshell-variable-aliases-list
(append
eshell-variable-aliases-list
- '(("-" (lambda (indices)
- (if (not indices)
- (unless (ring-empty-p eshell-last-dir-ring)
- (expand-file-name
- (ring-ref eshell-last-dir-ring 0)))
- (expand-file-name
- (eshell-apply-indices eshell-last-dir-ring indices)))))
- ("+" "PWD")
- ("PWD" (lambda (indices)
- (expand-file-name (eshell/pwd))) t)
- ("OLDPWD" (lambda (indices)
- (unless (ring-empty-p eshell-last-dir-ring)
- (expand-file-name
- (ring-ref eshell-last-dir-ring 0)))) t))))
+ `(("-" ,(lambda (indices quoted)
+ (if (not indices)
+ (unless (ring-empty-p eshell-last-dir-ring)
+ (expand-file-name
+ (ring-ref eshell-last-dir-ring 0)))
+ ;; Apply the first index, expand the file name,
+ ;; and then apply the rest of the indices.
+ (eshell-apply-indices
+ (expand-file-name
+ (eshell-apply-indices eshell-last-dir-ring
+ (list (car indices)) quoted))
+ (cdr indices) quoted))))
+ ("+" "PWD")
+ ("PWD" ,(lambda () (expand-file-name (eshell/pwd)))
+ t t)
+ ("OLDPWD" ,(lambda ()
+ (unless (ring-empty-p eshell-last-dir-ring)
+ (expand-file-name
+ (ring-ref eshell-last-dir-ring 0))))
+ t t))))
(when eshell-cd-on-directory
- (make-local-variable 'eshell-interpreter-alist)
- (setq eshell-interpreter-alist
- (cons (cons #'(lambda (file args)
- (eshell-lone-directory-p file))
- 'eshell-dirs-substitute-cd)
- eshell-interpreter-alist)))
+ (setq-local eshell-interpreter-alist
+ (cons (cons (lambda (file _args)
+ (eshell-lone-directory-p file))
+ 'eshell-dirs-substitute-cd)
+ eshell-interpreter-alist)))
(add-hook 'eshell-parse-argument-hook
- 'eshell-parse-user-reference nil t)
+ #'eshell-parse-user-reference nil t)
(if (eshell-under-windows-p)
(add-hook 'eshell-parse-argument-hook
- 'eshell-parse-drive-letter nil t))
+ #'eshell-parse-drive-letter nil t))
(when (eshell-using-module 'eshell-cmpl)
(add-hook 'pcomplete-try-first-hook
- 'eshell-complete-user-reference nil t))
+ #'eshell-complete-user-reference nil t))
(make-local-variable 'eshell-dirstack)
(make-local-variable 'eshell-last-dir-ring)
@@ -230,9 +221,9 @@ Thus, this does not include the current directory.")
(unless eshell-last-dir-ring
(setq eshell-last-dir-ring (make-ring eshell-last-dir-ring-size)))
- (add-hook 'eshell-exit-hook 'eshell-write-last-dir-ring nil t)
+ (add-hook 'eshell-exit-hook #'eshell-write-last-dir-ring nil t)
- (add-hook 'kill-emacs-hook 'eshell-save-some-last-dir))
+ (add-hook 'kill-emacs-query-functions #'eshell-save-some-last-dir))
(defun eshell-save-some-last-dir ()
"Save the list-dir-ring for any open Eshell buffers."
@@ -246,7 +237,8 @@ Thus, this does not include the current directory.")
(format-message
"Save last dir ring for Eshell buffer `%s'? "
(buffer-name buf)))))
- (eshell-write-last-dir-ring))))))
+ (eshell-write-last-dir-ring)))))
+ t)
(defun eshell-lone-directory-p (file)
"Test whether FILE is just a directory name, and not a command name."
@@ -259,7 +251,7 @@ Thus, this does not include the current directory.")
(if (> (length args) 1)
(error "%s: command not found" (car args))
(throw 'eshell-replace-command
- (eshell-parse-command "cd" (eshell-flatten-list args)))))
+ (eshell-parse-command "cd" (flatten-tree args)))))
(defun eshell-parse-user-reference ()
"An argument beginning with ~ is a filename to be expanded."
@@ -272,7 +264,7 @@ Thus, this does not include the current directory.")
(defun eshell-parse-drive-letter ()
"An argument beginning with X:[^/] is a drive letter reference."
(when (and (not eshell-current-argument)
- (looking-at "\\([A-Za-z]:\\)\\([^/\\\\]\\|\\'\\)"))
+ (looking-at "\\([A-Za-z]:\\)\\([^/\\]\\|\\'\\)"))
(goto-char (match-end 1))
(let* ((letter (match-string 1))
(regexp (concat "\\`" letter))
@@ -282,7 +274,7 @@ Thus, this does not include the current directory.")
(defvar pcomplete-stub)
(defvar pcomplete-last-completion-raw)
(declare-function pcomplete-actual-arg "pcomplete")
-(declare-function pcomplete-uniqify-list "pcomplete")
+(declare-function pcomplete-uniquify-list "pcomplete")
(defun eshell-complete-user-reference ()
"If there is a user reference, complete it."
@@ -293,47 +285,47 @@ Thus, this does not include the current directory.")
(throw 'pcomplete-completions
(progn
(eshell-read-user-names)
- (pcomplete-uniqify-list
+ (pcomplete-uniquify-list
(mapcar
- (function
- (lambda (user)
- (file-name-as-directory (cdr user))))
+ (lambda (user)
+ (file-name-as-directory (cdr user)))
eshell-user-names)))))))
-(defun eshell/pwd (&rest args)
+(defun eshell/pwd (&rest _args)
"Change output from `pwd' to be cleaner."
(let* ((path default-directory)
(len (length path)))
(if (and (> len 1)
(eq (aref path (1- len)) ?/)
(not (and (eshell-under-windows-p)
- (string-match "\\`[A-Za-z]:[\\\\/]\\'" path))))
+ (string-match "\\`[A-Za-z]:[\\/]\\'" path))))
(setq path (substring path 0 (1- (length path)))))
- (if eshell-pwd-convert-function
- (funcall eshell-pwd-convert-function path)
- path)))
+ (funcall (or eshell-pwd-convert-function #'identity) path)))
-(defun eshell-expand-multiple-dots (path)
+(defun eshell-expand-multiple-dots (filename)
+ ;; FIXME: This advice recommendation is rather odd: it's somewhat
+ ;; dangerous and it claims not to work with minibuffer-completion, which
+ ;; makes it much less interesting.
"Convert `...' to `../..', `....' to `../../..', etc..
With the following piece of advice, you can make this functionality
available in most of Emacs, with the exception of filename completion
in the minibuffer:
- (defadvice expand-file-name
- (before translate-multiple-dots
- (filename &optional directory) activate)
- (setq filename (eshell-expand-multiple-dots filename)))"
- (while (string-match "\\(?:^\\|/\\)\\.\\.\\(\\.+\\)\\(?:$\\|/\\)" path)
- (let* ((extra-dots (match-string 1 path))
+ (advice-add \\='expand-file-name :around #\\='my-expand-multiple-dots)
+ (defun my-expand-multiple-dots (orig-fun filename &rest args)
+ (apply orig-fun (eshell-expand-multiple-dots filename) args))"
+ (while (string-match "\\(?:\\`\\|/\\)\\.\\.\\(\\.+\\)\\(?:\\'\\|/\\)"
+ filename)
+ (let* ((extra-dots (match-string 1 filename))
(len (length extra-dots))
replace-text)
(while (> len 0)
(setq replace-text (concat replace-text "/..")
len (1- len)))
- (setq path
- (replace-match replace-text t t path 1))))
- path)
+ (setq filename
+ (replace-match replace-text t t filename 1))))
+ filename)
(defun eshell-find-previous-directory (regexp)
"Find the most recent last-dir matching REGEXP."
@@ -351,7 +343,7 @@ in the minibuffer:
(defun eshell/cd (&rest args) ; all but first ignored
"Alias to extend the behavior of `cd'."
- (setq args (eshell-flatten-list args))
+ (setq args (flatten-tree args))
(let ((path (car args))
(subpath (car (cdr args)))
(case-fold-search (eshell-under-windows-p))
@@ -398,6 +390,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)
@@ -407,6 +403,7 @@ in the minibuffer:
nil))))
(put 'eshell/cd 'eshell-no-numeric-conversions t)
+(put 'eshell/cd 'eshell-filename-arguments t)
(defun eshell-add-to-dir-ring (path)
"Add PATH to the last-dir-ring, if applicable."
@@ -470,6 +467,7 @@ in the minibuffer:
nil)
(put 'eshell/pushd 'eshell-no-numeric-conversions t)
+(put 'eshell/pushd 'eshell-filename-arguments t)
;;; popd [+n]
(defun eshell/popd (&rest args)
@@ -500,6 +498,7 @@ in the minibuffer:
nil)
(put 'eshell/popd 'eshell-no-numeric-conversions t)
+(put 'eshell/pop 'eshell-filename-arguments t)
(defun eshell/dirs (&optional if-verbose)
"Implementation of dirs in Lisp."
@@ -547,15 +546,16 @@ in the minibuffer:
(defun eshell-write-last-dir-ring ()
"Write the buffer's `eshell-last-dir-ring' to a history file."
- (let ((file eshell-last-dir-ring-file-name))
+ (let* ((file eshell-last-dir-ring-file-name)
+ (resolved-file (if (stringp file) (file-truename file))))
(cond
((or (null file)
(equal file "")
(null eshell-last-dir-ring)
(ring-empty-p eshell-last-dir-ring))
nil)
- ((not (file-writable-p file))
- (message "Cannot write last-dir-ring file %s" file))
+ ((not (file-writable-p resolved-file))
+ (message "Cannot write last-dir-ring file %s" resolved-file))
(t
(let* ((ring eshell-last-dir-ring)
(index (ring-length ring)))
@@ -565,7 +565,7 @@ in the minibuffer:
(insert (ring-ref ring index) ?\n))
(insert (eshell/pwd) ?\n)
(eshell-with-private-file-modes
- (write-region (point-min) (point-max) file nil
+ (write-region (point-min) (point-max) resolved-file nil
'no-message))))))))
(provide 'em-dirs)
diff --git a/lisp/eshell/em-elecslash.el b/lisp/eshell/em-elecslash.el
new file mode 100644
index 00000000000..091acb9a861
--- /dev/null
+++ b/lisp/eshell/em-elecslash.el
@@ -0,0 +1,120 @@
+;;; em-elecslash.el --- electric forward slashes -*- lexical-binding:t -*-
+
+;; Copyright (C) 2022 Free Software Foundation, Inc.
+
+;; Author: Sean Whitton <spwhitton@spwhitton.name>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Electric forward slash in remote Eshells.
+
+;;; Code:
+
+(require 'tramp)
+(require 'thingatpt)
+(require 'esh-cmd)
+(require 'esh-ext)
+(require 'esh-mode)
+
+;; This makes us an option when customizing `eshell-modules-list'.
+;;;###autoload
+(progn
+(defgroup eshell-elecslash nil
+ "Electric forward slash in remote Eshells.
+
+This module helps with supplying absolute file name arguments to
+remote commands. After enabling it, typing a forward slash as
+the first character of a command line argument will automatically
+insert the Tramp prefix, /method:host:. The automatic insertion
+applies only when `default-directory' is remote and the command
+is a Lisp function.
+
+The result is that in most cases of supplying absolute file name
+arguments to commands you should see the Tramp prefix inserted
+automatically only when that's what you'd reasonably expect.
+This frees you from having to keep track of whether commands are
+Lisp functions or external when typing command line arguments."
+ :tag "Electric forward slash"
+ :group 'eshell-module))
+
+;;; Functions:
+
+(defun eshell-elecslash-initialize () ;Called from `eshell-mode' via intern-soft!
+ "Initialize remote Eshell electric forward slash support."
+ (add-hook 'post-self-insert-hook
+ #'eshell-electric-forward-slash nil t))
+
+(defun eshell-electric-forward-slash ()
+ "Implementation of electric forward slash in remote Eshells.
+
+Initializing the `eshell-elecslash' module adds this function to
+`post-self-insert-hook'. Typing / or ~/ as the first character
+of a command line argument automatically inserts the Tramp prefix
+in the case that `default-directory' is remote and the command is
+a Lisp function. Typing a second forward slash undoes the
+insertion."
+ (when (eq ?/ (char-before))
+ (delete-char -1)
+ (let ((tilde-before (eq ?~ (char-before)))
+ (command (save-excursion
+ (eshell-bol)
+ (skip-syntax-forward " ")
+ (thing-at-point 'sexp))))
+ (if (and (file-remote-p default-directory)
+ ;; We can't formally parse the input. But if there is
+ ;; one of these operators behind us, then looking at
+ ;; the first command would not be sensible. So be
+ ;; conservative: don't insert the Tramp prefix if there
+ ;; are any of these operators behind us.
+ (not (looking-back (regexp-opt '("&&" "|" ";"))
+ eshell-last-output-end))
+ (or (= (point) eshell-last-output-end)
+ (and tilde-before
+ (= (1- (point)) eshell-last-output-end))
+ (and (or tilde-before
+ (eq ?\s (char-syntax (char-before))))
+ (or (eshell-find-alias-function command)
+ (and (fboundp (intern-soft command))
+ (or eshell-prefer-lisp-functions
+ (not (eshell-search-path command))))))))
+ (let ((map (make-sparse-keymap))
+ (start (if tilde-before (1- (point)) (point)))
+ (localname
+ (tramp-file-name-localname
+ (tramp-dissect-file-name default-directory))))
+ (when tilde-before (delete-char -1))
+ (insert
+ (substring default-directory 0
+ (string-search localname default-directory)))
+ (unless tilde-before (insert "/"))
+ ;; Typing a second slash undoes the insertion, for when
+ ;; you really do want to type a local absolute file name.
+ (define-key map "/" (lambda ()
+ (interactive)
+ (delete-region start (point))
+ (insert (if tilde-before "~/" "/"))))
+ (set-transient-map map))
+ (insert "/")))))
+
+(provide 'em-elecslash)
+
+;; Local Variables:
+;; generated-autoload-file: "esh-groups.el"
+;; End:
+
+;;; esh-elecslash.el ends here
diff --git a/lisp/eshell/em-extpipe.el b/lisp/eshell/em-extpipe.el
new file mode 100644
index 00000000000..7d81f27a18f
--- /dev/null
+++ b/lisp/eshell/em-extpipe.el
@@ -0,0 +1,204 @@
+;;; em-extpipe.el --- external shell pipelines -*- lexical-binding:t -*-
+
+;; Copyright (C) 2022 Free Software Foundation, Inc.
+
+;; Author: Sean Whitton <spwhitton@spwhitton.name>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; When constructing shell pipelines that will move a lot of data, it
+;; is a good idea to bypass Eshell's own pipelining support and use
+;; the operating system shell's instead. This module tries to make
+;; that easy to do.
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'esh-arg)
+(require 'esh-cmd)
+(require 'esh-io)
+(require 'esh-util)
+
+(eval-when-compile (require 'files-x))
+
+;;; Functions:
+
+(defun eshell-extpipe-initialize () ;Called from `eshell-mode' via intern-soft!
+ "Initialize external pipelines support."
+ (when (boundp 'eshell-special-chars-outside-quoting)
+ (setq-local
+ eshell-special-chars-outside-quoting
+ (append eshell-special-chars-outside-quoting (list ?\*))))
+ (add-hook 'eshell-parse-argument-hook
+ #'eshell-parse-external-pipeline -20 t)
+ (add-hook 'eshell-pre-rewrite-command-hook
+ #'eshell-rewrite-external-pipeline -20 t))
+
+(defmacro eshell-extpipe--or-with-catch (&rest disjuncts)
+ "Evaluate DISJUNCTS like `or' but catch `eshell-incomplete'.
+
+If `eshell-incomplete' is thrown during the evaluation of a
+disjunct, that disjunct yields nil."
+ (let ((result (gensym)))
+ `(let (,result)
+ (or ,@(cl-loop for disjunct in disjuncts collect
+ `(if (catch 'eshell-incomplete
+ (ignore (setq ,result ,disjunct)))
+ nil
+ ,result))))))
+
+(defun eshell-parse-external-pipeline ()
+ "Parse a pipeline intended for execution by the external shell.
+
+A sequence of arguments is rewritten to use the operating system
+shell when it contains `*|', `*<' or `*>', where the asterisk is
+preceded by whitespace or located at the start of input.
+
+The command extends to the next `|' character which is not
+preceded by an unescaped asterisk following whitespace, or the
+end of input, except that any Eshell-specific output redirections
+occurring at the end are excluded. Any other `<' or `>'
+appearing before the end of the command are treated as though
+preceded by (whitespace and) an asterisk.
+
+For example,
+
+ foo <bar *| baz >#<buffer quux>
+
+is equivalent to
+
+ sh -c \"foo <bar | baz\" >#<buffer quux>
+
+when `shell-file-name' is `sh' and `shell-command-switch' is
+`-c', but in
+
+ foo >#<buffer quux> *| baz
+
+and
+
+ foo *| baz >#<buffer quux> --some-argument
+
+the Eshell-specific redirect will be passed on to the operating
+system shell, probably leading to undesired results.
+
+This function must appear early in `eshell-parse-argument-hook'
+to ensure that operating system shell syntax is not interpreted
+as though it were Eshell syntax."
+ ;; Our goal is to wrap the external command to protect it from the
+ ;; other members of `eshell-parse-argument-hook'. We must avoid
+ ;; misinterpreting a quoted `*|', `*<' or `*>' as indicating an
+ ;; external pipeline, hence the structure of the loop in `findbeg1'.
+ (cl-flet
+ ((findbeg1 (pat &optional go (bound (point-max)))
+ (let* ((start (point))
+ (result
+ (catch 'found
+ (while (> bound (point))
+ (let* ((found
+ (save-excursion
+ (re-search-forward
+ "\\(?:#?'\\|\"\\|\\\\\\)" bound t)))
+ (next (or (and found (match-beginning 0))
+ bound)))
+ (if (re-search-forward pat next t)
+ (throw 'found (match-beginning 1))
+ (goto-char next)
+ (while (eshell-extpipe--or-with-catch
+ (eshell-parse-lisp-argument)
+ (eshell-parse-backslash)
+ (eshell-parse-double-quote)
+ (eshell-parse-literal-quote)))
+ ;; Guard against an infinite loop if none of
+ ;; the parsers moved us forward.
+ (unless (or (> (point) next) (eobp))
+ (forward-char 1))))))))
+ (goto-char (if (and result go) (match-end 0) start))
+ result)))
+ (unless (or eshell-current-argument eshell-current-quoted)
+ (let ((beg (point)) end
+ (next-marked (findbeg1 "\\(?:\\=\\|\\s-\\)\\(\\*[|<>]\\)"))
+ (next-unmarked
+ (or (findbeg1 "\\(?:\\=\\|[^*]\\|\\S-\\*\\)\\(|\\)")
+ (point-max))))
+ (when (and next-marked (> next-unmarked next-marked)
+ (or (> next-marked (point))
+ (looking-back "\\`\\|\\s-" nil)))
+ ;; Skip to the final segment of the external pipeline.
+ (while (findbeg1 "\\(?:\\=\\|\\s-\\)\\(\\*|\\)" t))
+ ;; Find output redirections.
+ (while (findbeg1
+ "\\([0-9]?>+&?[0-9]?\\s-*\\S-\\)" t next-unmarked)
+ ;; Is the output redirection Eshell-specific? We have our
+ ;; own logic, rather than calling `eshell-parse-argument',
+ ;; to avoid specifying here all the possible cars of
+ ;; parsed special references -- `get-buffer-create' etc.
+ (forward-char -1)
+ (let ((this-end
+ (save-match-data
+ (cond ((looking-at "#<")
+ (forward-char 1)
+ (1+ (eshell-find-delimiter ?\< ?\>)))
+ ((and (looking-at "/\\S-+")
+ (assoc (match-string 0)
+ eshell-virtual-targets))
+ (match-end 0))))))
+ (cond ((and this-end end)
+ (goto-char this-end))
+ (this-end
+ (goto-char this-end)
+ (setq end (match-beginning 0)))
+ (t
+ (setq end nil)))))
+ ;; We've moved past all Eshell-specific output redirections
+ ;; we could find. If there is only whitespace left, then
+ ;; `end' is right before redirections we should exclude;
+ ;; otherwise, we must include everything.
+ (unless (and end (skip-syntax-forward "\s" next-unmarked)
+ (= next-unmarked (point)))
+ (setq end next-unmarked))
+ (let ((cmd (string-trim
+ (buffer-substring-no-properties beg end))))
+ (goto-char end)
+ ;; We must now drop the asterisks, unless quoted/escaped.
+ (with-temp-buffer
+ (insert cmd)
+ (goto-char (point-min))
+ (cl-loop
+ for next = (findbeg1 "\\(?:\\=\\|\\s-\\)\\(\\*[|<>]\\)" t)
+ while next do (forward-char -2) (delete-char 1))
+ (eshell-finish-arg
+ `(eshell-external-pipeline ,(buffer-string))))))))))
+
+(defun eshell-rewrite-external-pipeline (terms)
+ "Rewrite an external pipeline in TERMS as parsed by
+`eshell-parse-external-pipeline', which see."
+ (while terms
+ (when (and (listp (car terms))
+ (eq (caar terms) 'eshell-external-pipeline))
+ (with-connection-local-variables
+ (setcdr terms (cl-list*
+ shell-command-switch (cadar terms) (cdr terms)))
+ (setcar terms shell-file-name)))
+ (setq terms (cdr terms))))
+
+(defsubst eshell-external-pipeline (&rest _args)
+ "Stub to generate an error if a pipeline is not rewritten."
+ (error "Unhandled external pipeline in input text"))
+
+(provide 'em-extpipe)
+;;; esh-extpipe.el ends here
diff --git a/lisp/eshell/em-glob.el b/lisp/eshell/em-glob.el
index 11d7ffcfc53..9722aeae18e 100644
--- a/lisp/eshell/em-glob.el
+++ b/lisp/eshell/em-glob.el
@@ -1,6 +1,6 @@
;;; em-glob.el --- extended file name globbing -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -91,7 +91,7 @@ This option slows down recursive glob processing by quite a bit."
(defcustom eshell-error-if-no-glob nil
"If non-nil, it is an error for a glob pattern not to match.
- This mimics the behavior of zsh if non-nil, but bash if nil."
+This mimics the behavior of zsh if non-nil, but bash if nil."
:type 'boolean
:group 'eshell-glob)
@@ -125,11 +125,11 @@ This option slows down recursive glob processing by quite a bit."
;;; Functions:
-(defun eshell-glob-initialize ()
+(defun eshell-glob-initialize () ;Called from `eshell-mode' via intern-soft!
"Initialize the extended globbing code."
;; it's important that `eshell-glob-chars-list' come first
(when (boundp 'eshell-special-chars-outside-quoting)
- (set (make-local-variable 'eshell-special-chars-outside-quoting)
+ (setq-local eshell-special-chars-outside-quoting
(append eshell-glob-chars-list eshell-special-chars-outside-quoting)))
(add-hook 'eshell-parse-argument-hook 'eshell-parse-glob-chars t t)
(add-hook 'eshell-pre-rewrite-command-hook
@@ -183,6 +183,10 @@ interpretation."
(defvar eshell-glob-matches)
(defvar message-shown)
+(defvar eshell-glob-recursive-alist
+ '(("**/" . recurse)
+ ("***/" . recurse-symlink)))
+
(defun eshell-glob-regexp (pattern)
"Convert glob-pattern PATTERN to a regular expression.
The basic syntax is:
@@ -198,14 +202,14 @@ The basic syntax is:
[a-b] [a-b] matches a character or range
[^a] [^a] excludes a character or range
-If any characters in PATTERN have the text property `eshell-escaped'
+If any characters in PATTERN have the text property `escaped'
set to true, then these characters will match themselves in the
resulting regular expression."
(let ((matched-in-pattern 0) ; How much of PATTERN handled
regexp)
(while (string-match
(or eshell-glob-chars-regexp
- (set (make-local-variable 'eshell-glob-chars-regexp)
+ (setq-local eshell-glob-chars-regexp
(format "[%s]+" (apply 'string eshell-glob-chars-list))))
pattern matched-in-pattern)
(let* ((op-begin (match-beginning 0))
@@ -232,10 +236,86 @@ resulting regular expression."
(regexp-quote (substring pattern matched-in-pattern))
"\\'")))
-(defvar ange-cache) ; XEmacs? See esh-util
+(defun eshell-glob-convert-1 (glob &optional last)
+ "Convert a GLOB matching a single element of a file name to regexps.
+If LAST is non-nil, this glob is the last element of a file name.
+
+The result is a pair of regexps, the first for file names to
+include, and the second for ones to exclude."
+ (let ((len (length glob)) (index 1) (incl glob) excl)
+ ;; We can't use `directory-file-name' because it strips away text
+ ;; properties in the string.
+ (let ((last (1- (length incl))))
+ (when (eq (aref incl last) ?/)
+ (setq incl (substring incl 0 last))))
+ ;; Split the glob if it contains a negation like x~y.
+ (while (and (eq incl glob)
+ (setq index (string-search "~" glob index)))
+ (if (or (get-text-property index 'escaped glob)
+ (or (= (1+ index) len)))
+ (setq index (1+ index))
+ (setq incl (substring glob 0 index)
+ excl (substring glob (1+ index)))))
+ (setq incl (eshell-glob-regexp incl)
+ excl (and excl (eshell-glob-regexp excl)))
+ ;; Exclude dot files if requested.
+ (if (or eshell-glob-include-dot-files
+ (eq (aref glob 0) ?.))
+ (unless (or eshell-glob-include-dot-dot
+ (not last))
+ (setq excl (if excl
+ (concat "\\(\\`\\.\\.?\\'\\|" excl "\\)")
+ "\\`\\.\\.?\\'")))
+ (setq excl (if excl
+ (concat "\\(\\`\\.\\|" excl "\\)")
+ "\\`\\.")))
+ (cons incl excl)))
+
+(defun eshell-glob-convert (glob)
+ "Convert an Eshell glob-pattern GLOB to regexps.
+The result is a list of three elements:
+
+1. The base directory to search in.
+
+2. A list containing elements of the following forms:
+
+ * Regexp pairs as generated by `eshell-glob-convert-1'.
+
+ * `recurse', indicating that searches should recurse into
+ subdirectories.
+
+ * `recurse-symlink', like `recurse', but also following
+ symlinks.
+
+3. A boolean indicating whether to match directories only."
+ (let ((globs (eshell-split-path glob))
+ (isdir (eq (aref glob (1- (length glob))) ?/))
+ start-dir result last-saw-recursion)
+ (if (and (cdr globs)
+ (file-name-absolute-p (car globs)))
+ (setq start-dir (car globs)
+ globs (cdr globs))
+ (setq start-dir "."))
+ (while globs
+ (if-let ((recurse (cdr (assoc (car globs)
+ eshell-glob-recursive-alist))))
+ (if last-saw-recursion
+ (setcar result recurse)
+ (push recurse result)
+ (setq last-saw-recursion t))
+ (push (eshell-glob-convert-1 (car globs) (null (cdr globs)))
+ result)
+ (setq last-saw-recursion nil))
+ (setq globs (cdr globs)))
+ (list (file-name-as-directory start-dir)
+ (nreverse result)
+ isdir)))
(defun eshell-extended-glob (glob)
- "Return a list of files generated from GLOB, perhaps looking for DIRS-ONLY.
+ "Return a list of files matched by GLOB.
+If no files match, signal an error (if `eshell-error-if-no-glob'
+is non-nil), or otherwise return GLOB itself.
+
This function almost fully supports zsh style filename generation
syntax. Things that are not supported are:
@@ -245,20 +325,11 @@ syntax. Things that are not supported are:
foo~x(a|b) (a|b) will be interpreted as a predicate/modifier list
Mainly they are not supported because file matching is done with Emacs
-regular expressions, and these cannot support the above constructs.
-
-If this routine fails, it returns nil. Otherwise, it returns a list
-the form:
-
- (INCLUDE-REGEXP EXCLUDE-REGEXP (PRED-FUNC-LIST) (MOD-FUNC-LIST))"
- (let ((paths (eshell-split-path glob))
- eshell-glob-matches message-shown ange-cache)
+regular expressions, and these cannot support the above constructs."
+ (let ((globs (eshell-glob-convert glob))
+ eshell-glob-matches message-shown)
(unwind-protect
- (if (and (cdr paths)
- (file-name-absolute-p (car paths)))
- (eshell-glob-entries (file-name-as-directory (car paths))
- (cdr paths))
- (eshell-glob-entries (file-name-as-directory ".") paths))
+ (apply #'eshell-glob-entries globs)
(if message-shown
(message nil)))
(or (and eshell-glob-matches (sort eshell-glob-matches #'string<))
@@ -267,94 +338,60 @@ the form:
glob))))
;; FIXME does this really need to abuse eshell-glob-matches, message-shown?
-(defun eshell-glob-entries (path globs &optional recurse-p)
- "Glob the entries in PATHS, possibly recursing if RECURSE-P is non-nil."
+(defun eshell-glob-entries (path globs only-dirs)
+ "Match the entries in PATH against GLOBS.
+GLOBS is a list of globs as converted by `eshell-glob-convert',
+which see.
+
+If ONLY-DIRS is non-nil, only match directories; otherwise, match
+directories and files."
(let* ((entries (ignore-errors
- (file-name-all-completions "" path)))
- (case-fold-search eshell-glob-case-insensitive)
- (glob (car globs))
- (len (length glob))
- dirs rdirs
- incl excl
- name isdir pathname)
- (while (cond
- ((and (= len 3) (equal glob "**/"))
- (setq recurse-p 2
- globs (cdr globs)
- glob (car globs)
- len (length glob)))
- ((and (= len 4) (equal glob "***/"))
- (setq recurse-p 3
- globs (cdr globs)
- glob (car globs)
- len (length glob)))))
- (if (and recurse-p (not glob))
- (error "`**' cannot end a globbing pattern"))
- (let ((index 1))
- (setq incl glob)
- (while (and (eq incl glob)
- (setq index (string-match "~" glob index)))
- (if (or (get-text-property index 'escaped glob)
- (or (= (1+ index) len)))
- (setq index (1+ index))
- (setq incl (substring glob 0 index)
- excl (substring glob (1+ index))))))
- ;; can't use `directory-file-name' because it strips away text
- ;; properties in the string
- (let ((len (1- (length incl))))
- (if (eq (aref incl len) ?/)
- (setq incl (substring incl 0 len)))
- (when excl
- (setq len (1- (length excl)))
- (if (eq (aref excl len) ?/)
- (setq excl (substring excl 0 len)))))
- (setq incl (eshell-glob-regexp incl)
- excl (and excl (eshell-glob-regexp excl)))
- (if (or eshell-glob-include-dot-files
- (eq (aref glob 0) ?.))
- (unless (or eshell-glob-include-dot-dot
- (cdr globs))
- (setq excl (if excl
- (concat "\\(\\`\\.\\.?\\'\\|" excl "\\)")
- "\\`\\.\\.?\\'")))
- (setq excl (if excl
- (concat "\\(\\`\\.\\|" excl "\\)")
- "\\`\\.")))
+ (file-name-all-completions "" path)))
+ (case-fold-search eshell-glob-case-insensitive)
+ glob glob-remainder recurse-p)
+ (if (rassq (car globs) eshell-glob-recursive-alist)
+ (setq recurse-p (car globs)
+ glob (or (cadr globs)
+ (eshell-glob-convert-1 "*" t))
+ glob-remainder (cddr globs))
+ (setq glob (car globs)
+ glob-remainder (cdr globs)))
(when (and recurse-p eshell-glob-show-progress)
(message "Building file list...%d so far: %s"
- (length eshell-glob-matches) path)
+ (length eshell-glob-matches) path)
(setq message-shown t))
- (if (equal path "./") (setq path ""))
- (while entries
- (setq name (car entries)
- len (length name)
- isdir (eq (aref name (1- len)) ?/))
- (if (let ((fname (directory-file-name name)))
- (and (not (and excl (string-match excl fname)))
- (string-match incl fname)))
- (if (cdr globs)
- (if isdir
- (setq dirs (cons (concat path name) dirs)))
- (setq eshell-glob-matches
- (cons (concat path name) eshell-glob-matches))))
- (if (and recurse-p isdir
- (or (> len 3)
- (not (or (and (= len 2) (equal name "./"))
- (and (= len 3) (equal name "../")))))
- (setq pathname (concat path name))
- (not (and (= recurse-p 2)
- (file-symlink-p
- (directory-file-name pathname)))))
- (setq rdirs (cons pathname rdirs)))
- (setq entries (cdr entries)))
- (setq dirs (nreverse dirs)
- rdirs (nreverse rdirs))
- (while dirs
- (eshell-glob-entries (car dirs) (cdr globs))
- (setq dirs (cdr dirs)))
- (while rdirs
- (eshell-glob-entries (car rdirs) globs recurse-p)
- (setq rdirs (cdr rdirs)))))
+ (when (equal path "./") (setq path ""))
+ (let ((incl (car glob))
+ (excl (cdr glob))
+ dirs rdirs)
+ (dolist (name entries)
+ (let* ((len (length name))
+ (isdir (eq (aref name (1- len)) ?/))
+ pathname)
+ (when (let ((fname (directory-file-name name)))
+ (and (not (and excl (string-match excl fname)))
+ (string-match incl fname)))
+ (if glob-remainder
+ (when isdir
+ (push (concat path name) dirs))
+ (when (or (not only-dirs)
+ (and isdir
+ (not (and (eq recurse-p 'recurse)
+ (file-symlink-p
+ (directory-file-name
+ (concat path name)))))))
+ (push (concat path name) eshell-glob-matches))))
+ (when (and recurse-p isdir
+ (not (member name '("./" "../")))
+ (setq pathname (concat path name))
+ (not (and (eq recurse-p 'recurse)
+ (file-symlink-p
+ (directory-file-name pathname)))))
+ (push pathname rdirs))))
+ (dolist (dir (nreverse dirs))
+ (eshell-glob-entries dir glob-remainder only-dirs))
+ (dolist (rdir (nreverse rdirs))
+ (eshell-glob-entries rdir globs only-dirs)))))
(provide 'em-glob)
diff --git a/lisp/eshell/em-hist.el b/lisp/eshell/em-hist.el
index 1ab3c60b2c7..1db239b9f72 100644
--- a/lisp/eshell/em-hist.el
+++ b/lisp/eshell/em-hist.el
@@ -1,6 +1,6 @@
;;; em-hist.el --- history list management -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -55,10 +55,10 @@
;;; Code:
(eval-when-compile (require 'cl-lib))
-(eval-when-compile (require 'subr-x)) ; `string-blank-p'
(require 'ring)
(require 'esh-opt)
+(require 'esh-mode)
(require 'em-pred)
(require 'eshell)
@@ -74,17 +74,14 @@
(defcustom eshell-hist-load-hook nil
"A list of functions to call when loading `eshell-hist'."
:version "24.1" ; removed eshell-hist-initialize
- :type 'hook
- :group 'eshell-hist)
+ :type 'hook)
(defcustom eshell-hist-unload-hook
(list
- (function
- (lambda ()
- (remove-hook 'kill-emacs-hook 'eshell-save-some-history))))
+ (lambda ()
+ (remove-hook 'kill-emacs-hook 'eshell-save-some-history)))
"A hook that gets run when `eshell-hist' is unloaded."
- :type 'hook
- :group 'eshell-hist)
+ :type 'hook)
(defcustom eshell-history-file-name
(expand-file-name "history" eshell-directory-name)
@@ -92,20 +89,21 @@
See also `eshell-read-history' and `eshell-write-history'.
If it is nil, Eshell will use the value of HISTFILE."
:type '(choice (const :tag "Use HISTFILE" nil)
- file)
- :group 'eshell-hist)
+ file))
(defcustom eshell-history-size 128
"Size of the input history ring. If nil, use envvar HISTSIZE."
:type '(choice (const :tag "Use HISTSIZE" nil)
- integer)
- :group 'eshell-hist)
+ integer))
(defcustom eshell-hist-ignoredups nil
"If non-nil, don't add input matching the last on the input ring.
-This mirrors the optional behavior of bash."
- :type 'boolean
- :group 'eshell-hist)
+The value `erase' mirrors the \"erasedups\" value of HISTCONTROL
+in bash, and any other non-nil value mirrors the \"ignoredups\"
+value."
+ :type '(choice (const :tag "Don't ignore anything" nil)
+ (const :tag "Ignore consecutive duplicates" t)
+ (const :tag "Only keep last duplicate" erase)))
(defcustom eshell-save-history-on-exit t
"Determine if history should be automatically saved.
@@ -117,8 +115,7 @@ If set to `ask', ask if any Eshell buffers are open at exit time.
If set to t, history will always be saved, silently."
:type '(choice (const :tag "Never" nil)
(const :tag "Ask" ask)
- (const :tag "Always save" t))
- :group 'eshell-hist)
+ (const :tag "Always save" t)))
(defcustom eshell-input-filter 'eshell-input-filter-default
"Predicate for filtering additions to input history.
@@ -128,40 +125,52 @@ whitespace."
:type '(radio (function-item eshell-input-filter-default)
(function-item eshell-input-filter-initial-space)
(function :tag "Other function"))
- :group 'eshell-hist)
-
-(put 'eshell-input-filter 'risky-local-variable t)
+ :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."
+\\`C-c M-r' and \\`C-c M-s' still offer that functionality."
:type 'boolean
- :group 'eshell-hist)
+ :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."
- :type 'boolean
- :group 'eshell-hist)
+ :type 'boolean)
(defcustom eshell-hist-event-designator
"^!\\(!\\|-?[0-9]+\\|\\??[^:^$%*?]+\\??\\|#\\)"
"The regexp used to identifier history event designators."
- :type 'regexp
- :group 'eshell-hist)
+ :type 'regexp)
(defcustom eshell-hist-word-designator
- "^:?\\([0-9]+\\|[$^%*]\\)?\\(\\*\\|-[0-9]*\\|[$^%*]\\)?"
+ "^:?\\([0-9]+\\|[$^%*]\\)?\\(-[0-9]*\\|[$^%*]\\)?"
"The regexp used to identify history word designators."
- :type 'regexp
- :group 'eshell-hist)
+ :type 'regexp)
(defcustom eshell-hist-modifier
"^\\(:\\([hretpqx&g]\\|s/\\([^/]*\\)/\\([^/]*\\)/\\)\\)*"
"The regexp used to identity history modifiers."
- :type 'regexp
- :group 'eshell-hist)
+ :type 'regexp)
(defcustom eshell-hist-rebind-keys-alist
'(([(control ?p)] . eshell-previous-input)
@@ -179,8 +188,7 @@ element, regardless of any text on the command line. In that case,
"History keys to bind differently if point is in input text."
:type '(repeat (cons (vector :tag "Keys to bind"
(repeat :inline t sexp))
- (function :tag "Command")))
- :group 'eshell-hist)
+ (function :tag "Command"))))
;;; Internal Variables:
@@ -189,18 +197,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 [return] '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-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)
@@ -216,57 +237,36 @@ Returns non-nil if INPUT is blank."
Returns nil if INPUT is prepended by blank space, otherwise non-nil."
(not (string-match-p "\\`\\s-+" input)))
-(defun eshell-hist-initialize ()
- "Initialize the history management code for one Eshell buffer."
- (add-hook 'eshell-expand-input-functions
- 'eshell-expand-history-references nil t)
+(define-minor-mode eshell-hist-mode
+ "Minor mode for the eshell-hist module.
+\\{eshell-hist-mode-map}"
+ :keymap eshell-hist-mode-map)
+
+(defun eshell-hist-initialize () ;Called from `eshell-mode' via intern-soft!
+ "Initialize the history management code for one Eshell buffer."
(when (eshell-using-module 'eshell-cmpl)
(add-hook 'pcomplete-try-first-hook
- 'eshell-complete-history-reference nil t))
+ #'eshell-complete-history-reference nil t))
(if (and (eshell-using-module 'eshell-rebind)
(not eshell-non-interactive-p))
(let ((rebind-alist eshell-rebind-keys-alist))
- (make-local-variable 'eshell-rebind-keys-alist)
- (setq eshell-rebind-keys-alist
+ (setq-local eshell-rebind-keys-alist
(append rebind-alist eshell-hist-rebind-keys-alist))
- (set (make-local-variable 'search-invisible) t)
- (set (make-local-variable 'search-exit-option) t)
+ (setq-local search-invisible t)
+ (setq-local search-exit-option t)
(add-hook 'isearch-mode-hook
- (function
- (lambda ()
- (if (>= (point) eshell-last-output-end)
- (setq overriding-terminal-local-map
- eshell-isearch-map)))) nil t)
+ (lambda ()
+ (if (>= (point) eshell-last-output-end)
+ (setq overriding-terminal-local-map
+ eshell-isearch-map)))
+ nil t)
(add-hook 'isearch-mode-end-hook
- (function
- (lambda ()
- (setq overriding-terminal-local-map nil))) nil t))
- (define-key eshell-mode-map [up] 'eshell-previous-matching-input-from-input)
- (define-key eshell-mode-map [down] 'eshell-next-matching-input-from-input)
- (define-key eshell-mode-map [(control up)] 'eshell-previous-input)
- (define-key eshell-mode-map [(control down)] 'eshell-next-input)
- (define-key eshell-mode-map [(meta ?r)] 'eshell-previous-matching-input)
- (define-key eshell-mode-map [(meta ?s)] 'eshell-next-matching-input)
- (define-key eshell-command-map [(meta ?r)]
- 'eshell-previous-matching-input-from-input)
- (define-key eshell-command-map [(meta ?s)]
- 'eshell-next-matching-input-from-input)
- (if eshell-hist-match-partial
- (progn
- (define-key eshell-mode-map [(meta ?p)]
- 'eshell-previous-matching-input-from-input)
- (define-key eshell-mode-map [(meta ?n)]
- 'eshell-next-matching-input-from-input)
- (define-key eshell-command-map [(meta ?p)] 'eshell-previous-input)
- (define-key eshell-command-map [(meta ?n)] 'eshell-next-input))
- (define-key eshell-mode-map [(meta ?p)] 'eshell-previous-input)
- (define-key eshell-mode-map [(meta ?n)] 'eshell-next-input)
- (define-key eshell-command-map [(meta ?p)]
- 'eshell-previous-matching-input-from-input)
- (define-key eshell-command-map [(meta ?n)]
- 'eshell-next-matching-input-from-input)))
+ (lambda ()
+ (setq overriding-terminal-local-map nil))
+ nil t))
+ (eshell-hist-mode))
(make-local-variable 'eshell-history-size)
(or eshell-history-size
@@ -286,25 +286,21 @@ Returns nil if INPUT is prepended by blank space, otherwise non-nil."
(make-local-variable 'eshell-save-history-index)
(if (minibuffer-window-active-p (selected-window))
- (set (make-local-variable 'eshell-save-history-on-exit) nil)
- (set (make-local-variable 'eshell-history-ring) nil)
+ (setq-local eshell-save-history-on-exit nil)
+ (setq-local eshell-history-ring nil)
(if eshell-history-file-name
(eshell-read-history nil t))
- (add-hook 'eshell-exit-hook 'eshell-write-history nil t))
+ (add-hook 'eshell-exit-hook #'eshell-write-history nil t))
(unless eshell-history-ring
(setq eshell-history-ring (make-ring eshell-history-size)))
- (add-hook 'eshell-exit-hook 'eshell-write-history nil t)
-
- (add-hook 'kill-emacs-hook 'eshell-save-some-history)
+ (add-hook 'eshell-exit-hook #'eshell-write-history nil t)
- (make-local-variable 'eshell-input-filter-functions)
- (add-hook 'eshell-input-filter-functions 'eshell-add-to-history nil t)
+ (add-hook 'kill-emacs-query-functions #'eshell-save-some-history)
- (define-key eshell-command-map [(control ?l)] 'eshell-list-history)
- (define-key eshell-command-map [(control ?x)] 'eshell-get-next-from-history))
+ (add-hook 'eshell-input-filter-functions #'eshell-add-to-history nil t))
(defun eshell-save-some-history ()
"Save the history for any open Eshell buffers."
@@ -319,7 +315,8 @@ Returns nil if INPUT is prepended by blank space, otherwise non-nil."
(format-message
"Save input history for Eshell buffer `%s'? "
(buffer-name buf)))))
- (eshell-write-history))))))
+ (eshell-write-history)))))
+ t)
(defun eshell/history (&rest args)
"List in help buffer the buffer's input history."
@@ -343,7 +340,7 @@ unless a different file is specified on the command line.")
(error "No history"))
(let (length file)
(when (and args (string-match "^[0-9]+$" (car args)))
- (setq length (min (eshell-convert (car args))
+ (setq length (min (string-to-number (car args))
(ring-length eshell-history-ring))
args (cdr args)))
(and length
@@ -384,12 +381,22 @@ unless a different file is specified on the command line.")
Input is entered into the input history ring, if the value of
variable `eshell-input-filter' returns non-nil when called on the
input."
- (if (and (funcall eshell-input-filter input)
- (or (null eshell-hist-ignoredups)
- (not (ring-p eshell-history-ring))
- (ring-empty-p eshell-history-ring)
- (not (string-equal (eshell-get-history 0) input))))
- (eshell-put-history input))
+ (when (and (funcall eshell-input-filter input)
+ (if (eq eshell-hist-ignoredups 'erase)
+ ;; Remove any old occurrences of the input, and put
+ ;; the new one at the end.
+ (unless (ring-empty-p eshell-history-ring)
+ (ring-remove eshell-history-ring
+ (ring-member eshell-history-ring input))
+ t)
+ ;; Always add...
+ (or (null eshell-hist-ignoredups)
+ ;; ... or add if it's not already present at the
+ ;; end.
+ (not (ring-p eshell-history-ring))
+ (ring-empty-p eshell-history-ring)
+ (not (string-equal (eshell-get-history 0) input)))))
+ (eshell-put-history input))
(setq eshell-save-history-index eshell-history-index)
(setq eshell-history-index nil))
@@ -400,7 +407,7 @@ variable `eshell-input-filter' returns non-nil when called on the
command.
This function is supposed to be called from the minibuffer, presumably
-as a minibuffer-exit-hook."
+as a `minibuffer-exit-hook'."
(eshell-add-input-to-history
(buffer-substring (minibuffer-prompt-end) (point-max))))
@@ -444,7 +451,6 @@ line, with the most recent command last. See also
(ignore-dups eshell-hist-ignoredups))
(with-temp-buffer
(insert-file-contents file)
- ;; Save restriction in case file is already visited...
;; Watch for those date stamps in history files!
(goto-char (point-max))
(while (and (< count size)
@@ -470,15 +476,16 @@ lost if `eshell-history-ring' is not empty. If
Useful within process sentinels.
See also `eshell-read-history'."
- (let ((file (or filename eshell-history-file-name)))
+ (let* ((file (or filename eshell-history-file-name))
+ (resolved-file (if (stringp file) (file-truename file))))
(cond
((or (null file)
(equal file "")
(null eshell-history-ring)
(ring-empty-p eshell-history-ring))
nil)
- ((not (file-writable-p file))
- (message "Cannot write history file %s" file))
+ ((not (file-writable-p resolved-file))
+ (message "Cannot write history file %s" resolved-file))
(t
(let* ((ring eshell-history-ring)
(index (ring-length ring)))
@@ -488,10 +495,12 @@ See also `eshell-read-history'."
(while (> index 0)
(setq index (1- index))
(let ((start (point)))
- (insert (ring-ref ring index) ?\n)
+ ;; Remove properties before inserting, to avoid trouble
+ ;; with read-only strings (Bug#28700).
+ (insert (substring-no-properties (ring-ref ring index)) ?\n)
(subst-char-in-region start (1- (point)) ?\n ?\177)))
(eshell-with-private-file-modes
- (write-region (point-min) (point-max) file append
+ (write-region (point-min) (point-max) resolved-file append
'no-message))))))))
(defun eshell-list-history ()
@@ -583,21 +592,30 @@ See also `eshell-read-history'."
(defun eshell-expand-history-references (beg end)
"Parse and expand any history references in current input."
- (let ((result (eshell-hist-parse-arguments beg end)))
+ (let ((result (eshell-hist-parse-arguments beg end))
+ (full-line (buffer-substring-no-properties beg end)))
(when result
(let ((textargs (nreverse (nth 0 result)))
(posb (nreverse (nth 1 result)))
- (pose (nreverse (nth 2 result))))
+ (pose (nreverse (nth 2 result)))
+ (full-line-subst (eshell-history-substitution full-line)))
(save-excursion
- (while textargs
- (let ((str (eshell-history-reference (car textargs))))
- (unless (eq str (car textargs))
- (goto-char (car posb))
- (insert-and-inherit str)
- (delete-char (- (car pose) (car posb)))))
- (setq textargs (cdr textargs)
- posb (cdr posb)
- pose (cdr pose))))))))
+ (if full-line-subst
+ ;; Found a ^foo^bar substitution
+ (progn
+ (goto-char beg)
+ (insert-and-inherit full-line-subst)
+ (delete-char (- end beg)))
+ ;; Try to expand other substitutions
+ (while textargs
+ (let ((str (eshell-history-reference (car textargs))))
+ (unless (eq str (car textargs))
+ (goto-char (car posb))
+ (insert-and-inherit str)
+ (delete-char (- (car pose) (car posb)))))
+ (setq textargs (cdr textargs)
+ posb (cdr posb)
+ pose (cdr pose)))))))))
(defvar pcomplete-stub)
(defvar pcomplete-last-completion-raw)
@@ -632,20 +650,31 @@ See also `eshell-read-history'."
(setq history (cdr history)))
(cdr fhist)))))))
+(defun eshell-history-substitution (line)
+ "Expand quick hist substitutions formatted as ^foo^bar^.
+Returns nil if string does not match quick substitution format,
+and acts like !!:s/foo/bar/ otherwise."
+ ;; `^string1^string2^'
+ ;; Quick Substitution. Repeat the last command, replacing
+ ;; STRING1 with STRING2. Equivalent to `!!:s/string1/string2/'
+ (when (and (eshell-using-module 'eshell-pred)
+ (string-match
+ "^\\^\\([^^]+\\)\\^\\([^^]+\\)\\(?:\\^\\(.*\\)\\)?$"
+ line))
+ ;; Save trailing match as `eshell-history-reference' runs string-match.
+ (let ((matched-end (match-string 3 line)))
+ (concat
+ (eshell-history-reference
+ (format "!!:s/%s/%s/"
+ (match-string 1 line)
+ (match-string 2 line)))
+ matched-end))))
+
(defun eshell-history-reference (reference)
"Expand directory stack REFERENCE.
The syntax used here was taken from the Bash info manual.
Returns the resultant reference, or the same string REFERENCE if none
matched."
- ;; `^string1^string2^'
- ;; Quick Substitution. Repeat the last command, replacing
- ;; STRING1 with STRING2. Equivalent to `!!:s/string1/string2/'
- (if (and (eshell-using-module 'eshell-pred)
- (string-match "\\^\\([^^]+\\)\\^\\([^^]+\\)\\^?\\s-*$"
- reference))
- (setq reference (format "!!:s/%s/%s/"
- (match-string 1 reference)
- (match-string 2 reference))))
;; `!'
;; Start a history substitution, except when followed by a
;; space, tab, the end of the line, = or (.
@@ -735,7 +764,7 @@ matched."
(setq nth (eshell-hist-word-reference nth)))
(unless (numberp mth)
(setq mth (eshell-hist-word-reference mth)))
- (cons (mapconcat 'identity (eshell-sublist textargs nth mth) " ")
+ (cons (mapconcat #'identity (seq-subseq textargs nth (1+ mth)) " ")
end))))
(defun eshell-hist-parse-modifier (hist reference)
@@ -835,7 +864,7 @@ Moves relative to START, or `eshell-history-index'."
(setq prev n
n (mod (+ n motion) len))
;; If we haven't reached a match, step some more.
- (while (and (< n len) (not tried-each-ring-item)
+ (while (and (not tried-each-ring-item)
(not (string-match regexp (eshell-get-history n))))
(setq n (mod (+ n motion) len)
;; If we have gone all the way around in this search.
diff --git a/lisp/eshell/em-ls.el b/lisp/eshell/em-ls.el
index bb087d2feba..bebb0d81b5b 100644
--- a/lisp/eshell/em-ls.el
+++ b/lisp/eshell/em-ls.el
@@ -1,6 +1,6 @@
;;; em-ls.el --- implementation of ls in Lisp -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -29,15 +29,16 @@
(require 'cl-lib)
(require 'esh-util)
(require 'esh-opt)
-(eval-when-compile (require 'eshell))
+(require 'esh-proc)
+(require 'esh-cmd)
;;;###autoload
(progn
(defgroup eshell-ls nil
- "This module implements the \"ls\" utility fully in Lisp. If it is
-passed any unrecognized command switches, it will revert to the
-operating system's version. This version of \"ls\" uses text
-properties to colorize its output based on the setting of
+ "This module implements the \"ls\" utility fully in Lisp.
+If it is passed any unrecognized command switches, it will revert
+to the operating system's version. This version of \"ls\" uses
+text properties to colorize its output based on the setting of
`eshell-ls-use-colors'."
:tag "Implementation of `ls' in Lisp"
:group 'eshell-module))
@@ -99,15 +100,14 @@ faster and conserves more memory."
:type 'boolean)
(defface eshell-ls-directory
- '((((class color) (background light)) (:foreground "Blue" :weight bold))
- (((class color) (background dark)) (:foreground "SkyBlue" :weight bold))
- (t (:weight bold)))
- "The face used for highlighting directories.")
+ '((t (:inherit font-lock-function-name-face)))
+ "The face used for highlighting directories."
+ :version "29.1")
(defface eshell-ls-symlink
- '((((class color) (background light)) (:foreground "Dark Cyan" :weight bold))
- (((class color) (background dark)) (:foreground "Cyan" :weight bold)))
- "The face used for highlighting symbolic links.")
+ '((t (:inherit font-lock-keyword-face)))
+ "The face used for highlighting symbolic links."
+ :version "29.1")
(defface eshell-ls-executable
'((((class color) (background light)) (:foreground "ForestGreen" :weight bold))
@@ -183,9 +183,9 @@ really need to stick around for very long."
"The face used for highlighting junk file names.")
(defsubst eshell-ls-filetype-p (attrs type)
- "Test whether ATTRS specifies a directory."
- (if (nth 8 attrs)
- (eq (aref (nth 8 attrs) 0) type)))
+ "Test whether ATTRS specifies a file of type TYPE."
+ (if (file-attribute-modes attrs)
+ (eq (aref (file-attribute-modes attrs) 0) type)))
(defmacro eshell-ls-applicable (attrs index func file)
"Test whether, for ATTRS, the user can do what corresponds to INDEX.
@@ -193,8 +193,8 @@ ATTRS is a string of file modes. See `file-attributes'.
If we cannot determine the answer using ATTRS (e.g., if we need
to know what group the user is in), compute the return value by
calling FUNC with FILE as an argument."
- `(let ((owner (nth 2 ,attrs))
- (modes (nth 8 ,attrs)))
+ `(let ((owner (file-attribute-user-id ,attrs))
+ (modes (file-attribute-modes ,attrs)))
(cond ((cond ((numberp owner)
(= owner (user-uid)))
((stringp owner)
@@ -238,7 +238,6 @@ scope during the evaluation of TEST-SEXP."
(defvar show-recursive)
(defvar show-size)
(defvar sort-method)
-(defvar ange-cache)
(defvar dired-flag)
;;; Functions:
@@ -270,14 +269,13 @@ instead."
eshell-current-subjob-p
font-lock-mode)
;; use the fancy highlighting in `eshell-ls' rather than font-lock
- (when (and eshell-ls-use-colors
- (featurep 'font-lock))
+ (when eshell-ls-use-colors
(font-lock-mode -1)
(setq font-lock-defaults nil)
(if (boundp 'font-lock-buffers)
- (set 'font-lock-buffers
- (delq (current-buffer)
- (symbol-value 'font-lock-buffers)))))
+ (setq font-lock-buffers
+ (delq (current-buffer)
+ (symbol-value 'font-lock-buffers)))))
(require 'em-glob)
(let* ((insert-func 'insert)
(error-func 'insert)
@@ -334,6 +332,7 @@ instead."
(apply 'eshell-do-ls args)))
(put 'eshell/ls 'eshell-no-numeric-conversions t)
+(put 'eshell/ls 'eshell-filename-arguments t)
(declare-function eshell-glob-regexp "em-glob" (pattern))
@@ -345,7 +344,7 @@ instead."
"ls" (if eshell-ls-initial-args
(list eshell-ls-initial-args args)
args)
- `((?a "all" nil show-all
+ '((?a "all" nil show-all
"do not ignore entries starting with .")
(?A "almost-all" nil show-almost-all
"do not list implied . and ..")
@@ -404,7 +403,7 @@ Sort entries alphabetically across.")
(setq listing-style 'by-columns))
(unless args
(setq args (list ".")))
- (let ((eshell-ls-exclude-regexp eshell-ls-exclude-regexp) ange-cache)
+ (let ((eshell-ls-exclude-regexp eshell-ls-exclude-regexp))
(when ignore-pattern
(unless (eshell-using-module 'eshell-glob)
(error (concat "-I option requires that `eshell-glob'"
@@ -436,7 +435,7 @@ Sort entries alphabetically across.")
(defsubst eshell-ls-size-string (attrs size-width)
"Return the size string for ATTRS length, using SIZE-WIDTH."
- (let* ((str (eshell-ls-printable-size (nth 7 attrs) t))
+ (let* ((str (eshell-ls-printable-size (file-attribute-size attrs) t))
(len (length str)))
(if (< len size-width)
(concat (make-string (- size-width len) ? ) str)
@@ -476,9 +475,9 @@ name should be displayed as, etc. Think of it as cooking a FILEINFO."
fileinfo)
(defun eshell-ls-file (fileinfo &optional size-width copy-fileinfo)
- "Output FILE in long format.
-FILE may be a string, or a cons cell whose car is the filename and
-whose cdr is the list of file attributes."
+ "Output FILEINFO in long format.
+FILEINFO may be a string, or a cons cell whose car is the
+filename and whose cdr is the list of file attributes."
(if (not (cdr fileinfo))
(funcall error-func (format "%s: No such file or directory\n"
(car fileinfo)))
@@ -502,19 +501,19 @@ whose cdr is the list of file attributes."
(if numeric-uid-gid
"%s%4d %-8s %-8s "
"%s%4d %-14s %-8s ")
- (or (nth 8 attrs) "??????????")
- (or (nth 1 attrs) 0)
- (or (let ((user (nth 2 attrs)))
+ (or (file-attribute-modes attrs) "??????????")
+ (or (file-attribute-link-number attrs) 0)
+ (or (let ((user (file-attribute-user-id attrs)))
(and (stringp user)
(eshell-substring user 14)))
- (nth 2 attrs)
+ (file-attribute-user-id attrs)
"")
- (or (let ((group (nth 3 attrs)))
+ (or (let ((group (file-attribute-group-id attrs)))
(and (stringp group)
(eshell-substring group 8)))
- (nth 3 attrs)
+ (file-attribute-group-id attrs)
""))
- (let* ((str (eshell-ls-printable-size (nth 7 attrs)))
+ (let* ((str (eshell-ls-printable-size (file-attribute-size attrs)))
(len (length str)))
;; Let file sizes shorter than 9 align neatly.
(if (< len (or size-width 8))
@@ -523,12 +522,14 @@ whose cdr is the list of file attributes."
" " (format-time-string
(concat
eshell-ls-date-format " "
- (if (= (nth 5 (decode-time))
- (nth 5 (decode-time
- (nth (cond
- ((eq sort-method 'by-atime) 4)
- ((eq sort-method 'by-ctime) 6)
- (t 5)) attrs))))
+ (if (= (decoded-time-year (decode-time))
+ (decoded-time-year
+ (decode-time
+ (nth (cond
+ ((eq sort-method 'by-atime) 4)
+ ((eq sort-method 'by-ctime) 6)
+ (t 5))
+ attrs))))
"%H:%M"
" %Y")) (nth (cond
((eq sort-method 'by-atime) 4)
@@ -584,12 +585,12 @@ relative to that directory."
(let ((total 0.0))
(setq size-width 0)
(dolist (e entries)
- (if (nth 7 (cdr e))
- (setq total (+ total (nth 7 (cdr e)))
+ (if (file-attribute-size (cdr e))
+ (setq total (+ total (file-attribute-size (cdr e)))
size-width
(max size-width
(length (eshell-ls-printable-size
- (nth 7 (cdr e))
+ (file-attribute-size (cdr e))
(not
;; If we are under -l, count length
;; of sizes in bytes, not in blocks.
@@ -628,38 +629,37 @@ In Eshell's implementation of ls, ENTRIES is always reversed."
(if (eq sort-method 'unsorted)
(nreverse entries)
(sort entries
- (function
- (lambda (l r)
- (let ((result
- (cond
- ((eq sort-method 'by-atime)
- (eshell-ls-compare-entries l r 4 'time-less-p))
- ((eq sort-method 'by-mtime)
- (eshell-ls-compare-entries l r 5 'time-less-p))
- ((eq sort-method 'by-ctime)
- (eshell-ls-compare-entries l r 6 'time-less-p))
- ((eq sort-method 'by-size)
- (eshell-ls-compare-entries l r 7 '<))
- ((eq sort-method 'by-extension)
- (let ((lx (file-name-extension
- (directory-file-name (car l))))
- (rx (file-name-extension
- (directory-file-name (car r)))))
- (cond
- ((or (and (not lx) (not rx))
- (equal lx rx))
- (string-lessp (directory-file-name (car l))
- (directory-file-name (car r))))
- ((not lx) t)
- ((not rx) nil)
- (t
- (string-lessp lx rx)))))
- (t
- (string-lessp (directory-file-name (car l))
- (directory-file-name (car r)))))))
- (if reverse-list
- (not result)
- result)))))))
+ (lambda (l r)
+ (let ((result
+ (cond
+ ((eq sort-method 'by-atime)
+ (eshell-ls-compare-entries l r 4 'time-less-p))
+ ((eq sort-method 'by-mtime)
+ (eshell-ls-compare-entries l r 5 'time-less-p))
+ ((eq sort-method 'by-ctime)
+ (eshell-ls-compare-entries l r 6 'time-less-p))
+ ((eq sort-method 'by-size)
+ (eshell-ls-compare-entries l r 7 '<))
+ ((eq sort-method 'by-extension)
+ (let ((lx (file-name-extension
+ (directory-file-name (car l))))
+ (rx (file-name-extension
+ (directory-file-name (car r)))))
+ (cond
+ ((or (and (not lx) (not rx))
+ (equal lx rx))
+ (string-lessp (directory-file-name (car l))
+ (directory-file-name (car r))))
+ ((not lx) t)
+ ((not rx) nil)
+ (t
+ (string-lessp lx rx)))))
+ (t
+ (string-lessp (directory-file-name (car l))
+ (directory-file-name (car r)))))))
+ (if reverse-list
+ (not result)
+ result))))))
(defun eshell-ls-files (files &optional size-width copy-fileinfo)
"Output a list of FILES.
@@ -679,12 +679,12 @@ Each member of FILES is either a string or a cons cell of the form
(let ((f files)
last-f
display-files
- ignore)
+ ) ;; ignore
(while f
(if (cdar f)
(setq last-f f
f (cdr f))
- (unless ignore
+ (unless nil ;; ignore
(funcall error-func
(format "%s: No such file or directory\n" (caar f))))
(if (eq f files)
@@ -697,9 +697,9 @@ Each member of FILES is either a string or a cons cell of the form
(setcar f (cadr f))
(setcdr f (cddr f))))))
(if (not show-size)
- (setq display-files (mapcar 'eshell-ls-annotate files))
+ (setq display-files (mapcar #'eshell-ls-annotate files))
(dolist (file files)
- (let* ((str (eshell-ls-printable-size (nth 7 (cdr file)) t))
+ (let* ((str (eshell-ls-printable-size (file-attribute-size (cdr file)) t))
(len (length str)))
(if (< len size-width)
(setq str (concat (make-string (- size-width len) ? ) str)))
@@ -765,14 +765,14 @@ need to be printed."
(if show-size
(max size-width
(length (eshell-ls-printable-size
- (nth 7 (cdr entry)) t))))))
+ (file-attribute-size (cdr entry)) t))))))
(setq dirs (cons entry dirs)))
(setq files (cons entry files)
size-width
(if show-size
(max size-width
(length (eshell-ls-printable-size
- (nth 7 (cdr entry)) t)))))))
+ (file-attribute-size (cdr entry)) t)))))))
(when files
(eshell-ls-files (eshell-ls-sort-entries files)
size-width show-recursive)
@@ -796,12 +796,11 @@ to use, and each member of which is the width of that column
(width 0)
(widths
(mapcar
- (function
- (lambda (file)
- (+ 2 (length (car file)))))
+ (lambda (file)
+ (+ 2 (length (car file))))
files))
;; must account for the added space...
- (max-width (+ (window-width) 2))
+ (max-width (+ (window-body-width nil 'remap) 2))
(best-width 0)
col-widths)
@@ -843,11 +842,10 @@ to use, and each member of which is the width of that column
(width 0)
(widths
(mapcar
- (function
- (lambda (file)
- (+ 2 (length (car file)))))
+ (lambda (file)
+ (+ 2 (length (car file))))
files))
- (max-width (+ (window-width) 2))
+ (max-width (+ (window-body-width nil 'remap) 2))
col-widths
colw)
diff --git a/lisp/eshell/em-pred.el b/lisp/eshell/em-pred.el
index 72a7bc4afcb..b4ef154f8c3 100644
--- a/lisp/eshell/em-pred.el
+++ b/lisp/eshell/em-pred.el
@@ -1,6 +1,6 @@
;;; em-pred.el --- argument predicates and modifiers (ala zsh) -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -46,9 +46,7 @@
;;; Code:
-(require 'esh-util)
-(require 'esh-arg)
-(eval-when-compile (require 'eshell))
+(require 'esh-mode)
;;;###autoload
(progn
@@ -65,41 +63,42 @@ ordinary strings."
(defcustom eshell-pred-load-hook nil
"A list of functions to run when `eshell-pred' is loaded."
:version "24.1" ; removed eshell-pred-initialize
- :type 'hook
- :group 'eshell-pred)
+ :type 'hook)
(defcustom eshell-predicate-alist
'((?/ . (eshell-pred-file-type ?d)) ; directories
(?. . (eshell-pred-file-type ?-)) ; regular files
- (?s . (eshell-pred-file-type ?s)) ; sockets
+ (?= . (eshell-pred-file-type ?s)) ; sockets
(?p . (eshell-pred-file-type ?p)) ; named pipes
(?@ . (eshell-pred-file-type ?l)) ; symbolic links
(?% . (eshell-pred-file-type ?%)) ; allow user to specify (c def.)
- (?r . (eshell-pred-file-mode 0400)) ; owner-readable
- (?w . (eshell-pred-file-mode 0200)) ; owner-writable
- (?x . (eshell-pred-file-mode 0100)) ; owner-executable
- (?A . (eshell-pred-file-mode 0040)) ; group-readable
- (?I . (eshell-pred-file-mode 0020)) ; group-writable
- (?E . (eshell-pred-file-mode 0010)) ; group-executable
- (?R . (eshell-pred-file-mode 0004)) ; world-readable
- (?W . (eshell-pred-file-mode 0002)) ; world-writable
- (?X . (eshell-pred-file-mode 0001)) ; world-executable
- (?s . (eshell-pred-file-mode 4000)) ; setuid
- (?S . (eshell-pred-file-mode 2000)) ; setgid
- (?t . (eshell-pred-file-mode 1000)) ; sticky bit
- (?U . #'(lambda (file) ; owned by effective uid
- (if (file-exists-p file)
- (= (nth 2 (file-attributes file)) (user-uid)))))
- ;; (?G . #'(lambda (file) ; owned by effective gid
- ;; (if (file-exists-p file)
- ;; (= (nth 2 (file-attributes file)) (user-uid)))))
- (?* . #'(lambda (file)
- (and (file-regular-p file)
- (not (file-symlink-p file))
- (file-executable-p file))))
+ (?r . (eshell-pred-file-mode #o0400)) ; owner-readable
+ (?w . (eshell-pred-file-mode #o0200)) ; owner-writable
+ (?x . (eshell-pred-file-mode #o0100)) ; owner-executable
+ (?A . (eshell-pred-file-mode #o0040)) ; group-readable
+ (?I . (eshell-pred-file-mode #o0020)) ; group-writable
+ (?E . (eshell-pred-file-mode #o0010)) ; group-executable
+ (?R . (eshell-pred-file-mode #o0004)) ; world-readable
+ (?W . (eshell-pred-file-mode #o0002)) ; world-writable
+ (?X . (eshell-pred-file-mode #o0001)) ; world-executable
+ (?s . (eshell-pred-file-mode #o4000)) ; setuid
+ (?S . (eshell-pred-file-mode #o2000)) ; setgid
+ (?t . (eshell-pred-file-mode #o1000)) ; sticky bit
+ (?U . (lambda (file) ; owned by effective uid
+ (if (file-exists-p file)
+ (= (file-attribute-user-id (file-attributes file))
+ (user-uid)))))
+ (?G . (lambda (file) ; owned by effective gid
+ (if (file-exists-p file)
+ (= (file-attribute-group-id (file-attributes file))
+ (group-gid)))))
+ (?* . (lambda (file)
+ (and (file-regular-p file)
+ (not (file-symlink-p file))
+ (file-executable-p file))))
(?l . (eshell-pred-file-links))
- (?u . (eshell-pred-user-or-group ?u "user" 2 'eshell-user-id))
- (?g . (eshell-pred-user-or-group ?g "group" 3 'eshell-group-id))
+ (?u . (eshell-pred-user-or-group ?u "user" 2 #'eshell-user-id))
+ (?g . (eshell-pred-user-or-group ?g "group" 3 #'eshell-group-id))
(?a . (eshell-pred-file-time ?a "access" 4))
(?m . (eshell-pred-file-time ?m "modification" 5))
(?c . (eshell-pred-file-time ?c "change" 6))
@@ -109,34 +108,26 @@ The format of each entry is
(CHAR . PREDICATE-FUNC-SEXP)"
:type '(repeat (cons character sexp))
- :group 'eshell-pred)
-
-(put 'eshell-predicate-alist 'risky-local-variable t)
+ :risky t)
(defcustom eshell-modifier-alist
- '((?E . #'(lambda (lst)
- (mapcar
- (function
- (lambda (str)
- (eshell-stringify
- (car (eshell-parse-argument str)))))
- lst)))
- (?L . #'(lambda (lst) (mapcar 'downcase lst)))
- (?U . #'(lambda (lst) (mapcar 'upcase lst)))
- (?C . #'(lambda (lst) (mapcar 'capitalize lst)))
- (?h . #'(lambda (lst) (mapcar 'file-name-directory lst)))
- (?i . (eshell-include-members))
- (?x . (eshell-include-members t))
- (?r . #'(lambda (lst) (mapcar 'file-name-sans-extension lst)))
- (?e . #'(lambda (lst) (mapcar 'file-name-extension lst)))
- (?t . #'(lambda (lst) (mapcar 'file-name-nondirectory lst)))
- (?q . #'(lambda (lst) (mapcar 'eshell-escape-arg lst)))
- (?u . #'(lambda (lst) (eshell-uniqify-list lst)))
- (?o . #'(lambda (lst) (sort lst 'string-lessp)))
- (?O . #'(lambda (lst) (nreverse (sort lst 'string-lessp))))
+ '((?E . (lambda (lst) (mapcar #'eshell-eval-argument lst)))
+ (?L . (lambda (lst) (mapcar #'downcase lst)))
+ (?U . (lambda (lst) (mapcar #'upcase lst)))
+ (?C . (lambda (lst) (mapcar #'capitalize lst)))
+ (?h . (lambda (lst) (mapcar #'file-name-directory lst)))
+ (?i . (eshell-include-members ?i))
+ (?x . (eshell-include-members ?x t))
+ (?r . (lambda (lst) (mapcar #'file-name-sans-extension lst)))
+ (?e . (lambda (lst) (mapcar #'file-name-extension lst)))
+ (?t . (lambda (lst) (mapcar #'file-name-nondirectory lst)))
+ (?q . (lambda (lst) (mapcar #'eshell-escape-arg lst)))
+ (?u . (lambda (lst) (seq-uniq lst)))
+ (?o . (lambda (lst) (sort lst #'string-lessp)))
+ (?O . (lambda (lst) (sort lst #'string-greaterp)))
(?j . (eshell-join-members))
(?S . (eshell-split-members))
- (?R . 'reverse)
+ (?R . #'reverse)
(?g . (progn
(forward-char)
(if (eq (char-before) ?s)
@@ -146,11 +137,9 @@ The format of each entry is
"A list of modifiers than can be applied to an argument expansion.
The format of each entry is
- (CHAR ENTRYWISE-P MODIFIER-FUNC-SEXP)"
+ (CHAR . MODIFIER-FUNC-SEXP)"
:type '(repeat (cons character sexp))
- :group 'eshell-pred)
-
-(put 'eshell-modifier-alist 'risky-local-variable t)
+ :risky t)
(defvar eshell-predicate-help-string
"Eshell predicate quick reference:
@@ -172,6 +161,7 @@ PERMISSION BITS (for owner/group/world):
OWNERSHIP:
U owned by effective uid
+ G owned by effective gid
u(UID|\\='user\\=') owned by UID/user
g(GID|\\='group\\=') owned by GID/group
@@ -223,43 +213,71 @@ FOR LISTS OF ARGUMENTS:
i/PAT/ exclude all members not matching PAT
x/PAT/ exclude all members matching PAT
- s/pat/match/ substitute PAT with MATCH
- g/pat/match/ substitute PAT with MATCH for all occurrences
+ s/pat/match/ substitute PAT with MATCH
+ gs/pat/match/ substitute PAT with MATCH for all occurrences
EXAMPLES:
*.c(:o) sorted list of .c files")
+(defvar eshell-pred-delimiter-pairs
+ '((?\( . ?\))
+ (?\[ . ?\])
+ (?\< . ?\>)
+ (?\{ . ?\})
+ (?\' . ?\')
+ (?\" . ?\")
+ (?/ . ?/)
+ (?| . ?|))
+ "A list of delimiter pairs that can be used in argument predicates/modifiers.
+Each element is of the form (OPEN . CLOSE), where OPEN and CLOSE
+are characters representing the opening and closing delimiter,
+respectively.")
+
+(defvar eshell-error-if-no-glob) ; Defined in em-glob.el.
+
+(defvar-keymap eshell-pred-mode-map
+ "C-c M-q" #'eshell-display-predicate-help
+ "C-c M-m" #'eshell-display-modifier-help)
+
;;; Functions:
(defun eshell-display-predicate-help ()
(interactive)
(with-electric-help
- (function
- (lambda ()
- (insert eshell-predicate-help-string)))))
+ (lambda ()
+ (insert eshell-predicate-help-string))))
(defun eshell-display-modifier-help ()
(interactive)
(with-electric-help
- (function
- (lambda ()
- (insert eshell-modifier-help-string)))))
+ (lambda ()
+ (insert eshell-modifier-help-string))))
-(defun eshell-pred-initialize ()
+(define-minor-mode eshell-pred-mode
+ "Minor mode for the eshell-pred module.
+
+\\{eshell-pred-mode-map}"
+ :keymap eshell-pred-mode-map)
+
+(defun eshell-pred-initialize () ;Called from `eshell-mode' via intern-soft!
"Initialize the predicate/modifier code."
(add-hook 'eshell-parse-argument-hook
- 'eshell-parse-arg-modifier t t)
- (define-key eshell-command-map [(meta ?q)] 'eshell-display-predicate-help)
- (define-key eshell-command-map [(meta ?m)] 'eshell-display-modifier-help))
+ #'eshell-parse-arg-modifier t t)
+ (eshell-pred-mode))
-(defun eshell-apply-modifiers (lst predicates modifiers)
- "Apply to LIST a series of PREDICATES and MODIFIERS."
+(defun eshell-apply-modifiers (lst predicates modifiers string-desc)
+ "Apply to list LST a series of PREDICATES and MODIFIERS.
+STRING-DESC is the original string defining these predicates and
+modifiers."
(let (stringified)
(if (stringp lst)
(setq lst (list lst)
stringified t))
(when (listp lst)
- (setq lst (eshell-winnow-list lst nil predicates))
+ (when lst
+ (setq lst (or (eshell-winnow-list lst nil predicates)
+ (when eshell-error-if-no-glob
+ (error "No matches found: (%s)" string-desc)))))
(while modifiers
(setq lst (funcall (car modifiers) lst)
modifiers (cdr modifiers)))
@@ -279,7 +297,8 @@ This function is specially for adding onto `eshell-parse-argument-hook'."
(when (eshell-arg-delimiter (1+ end))
(save-restriction
(narrow-to-region (point) end)
- (let* ((modifiers (eshell-parse-modifiers))
+ (let* ((modifier-string (buffer-string))
+ (modifiers (eshell-parse-modifiers))
(preds (car modifiers))
(mods (cdr modifiers)))
(if (or preds mods)
@@ -289,9 +308,9 @@ This function is specially for adding onto `eshell-parse-argument-hook'."
(append
eshell-current-modifiers
(list
- `(lambda (lst)
- (eshell-apply-modifiers
- lst (quote ,preds) (quote ,mods)))))))))
+ (lambda (lst)
+ (eshell-apply-modifiers
+ lst preds mods modifier-string))))))))
(goto-char (1+ end))
(eshell-finish-arg))))))
@@ -316,7 +335,7 @@ resultant list of strings."
(if (looking-at "[^|':]")
(let ((func (read (current-buffer))))
(if (and func (functionp func))
- (setq preds (eshell-add-pred-func func preds
+ (setq preds (eshell-add-pred-func (eval func t) preds
negate follow))
(error "Invalid function predicate `%s'"
(eshell-stringify func))))
@@ -333,8 +352,7 @@ resultant list of strings."
(let ((func (read (current-buffer))))
(if (and func (functionp func))
(setq mods
- (cons `(lambda (lst)
- (mapcar (function ,func) lst))
+ (cons (lambda (lst) (mapcar func lst))
mods))
(error "Invalid function modifier `%s'"
(eshell-stringify func))))
@@ -345,14 +363,14 @@ resultant list of strings."
(if (not mod)
(error "Unknown modifier character `%c'" (char-after))
(forward-char)
- (setq mods (cons (eval (cdr mod)) mods)))))
+ (setq mods (cons (eval (cdr mod) t) mods)))))
(t
(let ((pred (assq char eshell-predicate-alist)))
(if (not pred)
(error "Unknown predicate character `%c'" char)
(forward-char)
(setq preds
- (eshell-add-pred-func (eval (cdr pred)) preds
+ (eshell-add-pred-func (eval (cdr pred) t) preds
negate follow))))))))
(end-of-buffer
(error "Predicate or modifier ended prematurely")))
@@ -360,46 +378,78 @@ resultant list of strings."
(defun eshell-add-pred-func (pred funcs negate follow)
"Add the predicate function PRED to FUNCS."
- (if negate
- (setq pred `(lambda (file)
- (not (funcall ,pred file)))))
- (if follow
- (setq pred `(lambda (file)
- (funcall ,pred (file-truename file)))))
+ (when negate
+ (setq pred (let ((pred pred))
+ (lambda (file) (not (funcall pred file))))))
+ (when follow
+ (setq pred (let ((pred pred))
+ (lambda (file) (funcall pred (file-truename file))))))
(cons pred funcs))
+(defun eshell-get-comparison-modifier-argument (&optional functions)
+ "Starting at point, get the comparison modifier argument, if any.
+These are the -/+ characters, corresponding to `<' and `>',
+respectively. If no comparison modifier is at point, return `='.
+
+FUNCTIONS, if non-nil, is a list of comparison functions,
+specified as (LESS-THAN GREATER-THAN EQUAL-TO)."
+ (let ((functions (or functions (list #'< #'> #'=))))
+ (if (memq (char-after) '(?- ?+))
+ (prog1
+ (if (eq (char-after) ?-) (nth 0 functions) (nth 1 functions))
+ (forward-char))
+ (nth 2 functions))))
+
+(defun eshell-get-numeric-modifier-argument ()
+ "Starting at point, get the numeric modifier argument, if any.
+If a number is found, update point to just after the number."
+ (when (looking-at "[0-9]+")
+ (prog1
+ (string-to-number (match-string 0))
+ (goto-char (match-end 0)))))
+
+(defun eshell-get-delimited-modifier-argument (&optional chained-p)
+ "Starting at point, get the delimited modifier argument, if any.
+If the character after point is a predicate/modifier
+delimiter (see `eshell-pred-delimiter-pairs', read the value of
+the argument and update point to be just after the closing
+delimiter.
+
+If CHAINED-P is true, then another delimited modifier argument
+will immediately follow this one. In this case, when the opening
+and closing delimiters are the same, update point to be just
+before the closing delimiter. This allows modifiers like
+`:s/match/repl' to work as expected."
+ (when-let* ((open (char-after))
+ (close (cdr (assoc open eshell-pred-delimiter-pairs)))
+ (end (eshell-find-delimiter open close nil nil t)))
+ (prog1
+ (replace-regexp-in-string
+ (rx-to-string `(seq "\\" (group (or "\\" ,open ,close)))) "\\1"
+ (buffer-substring-no-properties (1+ (point)) end))
+ (goto-char (if (and chained-p (eq open close))
+ end
+ (1+ end))))))
+
(defun eshell-pred-user-or-group (mod-char mod-type attr-index get-id-func)
"Return a predicate to test whether a file match a given user/group id."
- (let (ugid open close end)
- (if (looking-at "[0-9]+")
- (progn
- (setq ugid (string-to-number (match-string 0)))
- (goto-char (match-end 0)))
- (setq open (char-after))
- (if (setq close (memq open '(?\( ?\[ ?\< ?\{)))
- (setq close (car (last '(?\) ?\] ?\> ?\})
- (length close))))
- (setq close open))
- (forward-char)
- (setq end (eshell-find-delimiter open close))
- (unless end
- (error "Malformed %s name string for modifier `%c'"
- mod-type mod-char))
- (setq ugid
- (funcall get-id-func (buffer-substring (point) end)))
- (goto-char (1+ end)))
+ (let ((ugid (eshell-get-numeric-modifier-argument)))
+ (unless ugid
+ (let ((ugname (or (eshell-get-delimited-modifier-argument)
+ (error "Malformed %s name string for modifier `%c'"
+ mod-type mod-char))))
+ (setq ugid (funcall get-id-func ugname))))
(unless ugid
(error "Unknown %s name specified for modifier `%c'"
mod-type mod-char))
- `(lambda (file)
- (let ((attrs (file-attributes file)))
- (if attrs
- (= (nth ,attr-index attrs) ,ugid))))))
+ (lambda (file)
+ (when-let ((attrs (file-attributes file)))
+ (= (nth attr-index attrs) ugid)))))
(defun eshell-pred-file-time (mod-char mod-type attr-index)
"Return a predicate to test whether a file matches a certain time."
(let* ((quantum 86400)
- qual when open close end)
+ qual when)
(when (memq (char-after) '(?M ?w ?h ?m ?s))
(setq quantum (char-after))
(cond
@@ -414,82 +464,52 @@ resultant list of strings."
((eq quantum ?s)
(setq quantum 1)))
(forward-char))
- (when (memq (char-after) '(?+ ?-))
- (setq qual (char-after))
- (forward-char))
- (if (looking-at "[0-9]+")
- (progn
- (setq when (- (float-time)
- (* (string-to-number (match-string 0))
- quantum)))
- (goto-char (match-end 0)))
- (setq open (char-after))
- (if (setq close (memq open '(?\( ?\[ ?\< ?\{)))
- (setq close (car (last '(?\) ?\] ?\> ?\})
- (length close))))
- (setq close open))
- (forward-char)
- (setq end (eshell-find-delimiter open close))
- (unless end
- (error "Malformed %s time modifier `%c'" mod-type mod-char))
- (let* ((file (buffer-substring (point) end))
- (attrs (file-attributes file)))
- (unless attrs
- (error "Cannot stat file `%s'" file))
- (setq when (float-time (nth attr-index attrs))))
- (goto-char (1+ end)))
- `(lambda (file)
- (let ((attrs (file-attributes file)))
- (if attrs
- (,(if (eq qual ?-)
- '<
- (if (eq qual ?+)
- '>
- '=)) ,when (float-time
- (nth ,attr-index attrs))))))))
+ (setq qual (eshell-get-comparison-modifier-argument
+ (list #'time-less-p
+ (lambda (a b) (time-less-p b a))
+ #'time-equal-p)))
+ (if-let ((number (eshell-get-numeric-modifier-argument)))
+ (setq when (time-since (* number quantum)))
+ (let* ((file (or (eshell-get-delimited-modifier-argument)
+ (error "Malformed %s time modifier `%c'"
+ mod-type mod-char)))
+ (attrs (or (file-attributes file)
+ (error "Cannot stat file `%s'" file))))
+ (setq when (nth attr-index attrs))))
+ (lambda (file)
+ (when-let ((attrs (file-attributes file)))
+ (funcall qual when (nth attr-index attrs))))))
(defun eshell-pred-file-type (type)
"Return a test which tests that the file is of a certain TYPE.
TYPE must be a character, and should be one of the possible options
-that `ls -l' will show in the first column of its display. "
+that `ls -l' will show in the first column of its display."
(when (eq type ?%)
(setq type (char-after))
(if (memq type '(?b ?c))
(forward-char)
(setq type ?%)))
- `(lambda (file)
- (let ((attrs (eshell-file-attributes (directory-file-name file))))
- (if attrs
- (memq (aref (nth 8 attrs) 0)
- ,(if (eq type ?%)
- '(?b ?c)
- (list 'quote (list type))))))))
+ (let ((set (if (eq type ?%)
+ '(?b ?c)
+ (list type))))
+ (lambda (file)
+ (when-let ((attrs (eshell-file-attributes (directory-file-name file))))
+ (memq (aref (file-attribute-modes attrs) 0) set)))))
(defsubst eshell-pred-file-mode (mode)
"Return a test which tests that MODE pertains to the file."
- `(lambda (file)
- (let ((modes (file-modes file)))
- (if modes
- (logand ,mode modes)))))
+ (lambda (file)
+ (when-let ((modes (file-modes file 'nofollow)))
+ (not (zerop (logand mode modes))))))
(defun eshell-pred-file-links ()
"Return a predicate to test whether a file has a given number of links."
- (let (qual amount)
- (when (memq (char-after) '(?- ?+))
- (setq qual (char-after))
- (forward-char))
- (unless (looking-at "[0-9]+")
- (error "Invalid file link count modifier `l'"))
- (setq amount (string-to-number (match-string 0)))
- (goto-char (match-end 0))
- `(lambda (file)
- (let ((attrs (eshell-file-attributes file)))
- (if attrs
- (,(if (eq qual ?-)
- '<
- (if (eq qual ?+)
- '>
- '=)) (nth 1 attrs) ,amount))))))
+ (let ((qual (eshell-get-comparison-modifier-argument))
+ (amount (or (eshell-get-numeric-modifier-argument)
+ (error "Invalid file link count modifier `l'"))))
+ (lambda (file)
+ (when-let ((attrs (eshell-file-attributes file)))
+ (funcall qual (file-attribute-link-number attrs) amount)))))
(defun eshell-pred-file-size ()
"Return a predicate to test whether a file is of a given size."
@@ -504,92 +524,57 @@ that `ls -l' will show in the first column of its display. "
((eq qual ?p)
(setq quantum 512)))
(forward-char))
- (when (memq (char-after) '(?- ?+))
- (setq qual (char-after))
- (forward-char))
- (unless (looking-at "[0-9]+")
- (error "Invalid file size modifier `L'"))
- (setq amount (* (string-to-number (match-string 0)) quantum))
- (goto-char (match-end 0))
- `(lambda (file)
- (let ((attrs (eshell-file-attributes file)))
- (if attrs
- (,(if (eq qual ?-)
- '<
- (if (eq qual ?+)
- '>
- '=)) (nth 7 attrs) ,amount))))))
+ (setq qual (eshell-get-comparison-modifier-argument))
+ (setq amount (* (or (eshell-get-numeric-modifier-argument)
+ (error "Invalid file size modifier `L'"))
+ quantum))
+ (lambda (file)
+ (when-let ((attrs (eshell-file-attributes file)))
+ (funcall qual (file-attribute-size attrs) amount)))))
(defun eshell-pred-substitute (&optional repeat)
"Return a modifier function that will substitute matches."
- (let ((delim (char-after))
- match replace end)
- (forward-char)
- (setq end (eshell-find-delimiter delim delim nil nil t)
- match (buffer-substring-no-properties (point) end))
- (goto-char (1+ end))
- (setq end (eshell-find-delimiter delim delim nil nil t)
- replace (buffer-substring-no-properties (point) end))
- (goto-char (1+ end))
- (if repeat
- `(lambda (lst)
- (mapcar
- (function
- (lambda (str)
- (let ((i 0))
- (while (setq i (string-match ,match str i))
- (setq str (replace-match ,replace t nil str))))
- str)) lst))
- `(lambda (lst)
- (mapcar
- (function
- (lambda (str)
- (if (string-match ,match str)
- (setq str (replace-match ,replace t nil str)))
- str)) lst)))))
-
-(defun eshell-include-members (&optional invert-p)
- "Include only lisp members matching a regexp."
- (let ((delim (char-after))
- regexp end)
- (forward-char)
- (setq end (eshell-find-delimiter delim delim nil nil t)
- regexp (buffer-substring-no-properties (point) end))
- (goto-char (1+ end))
- `(lambda (lst)
- (eshell-winnow-list
- lst nil '((lambda (elem)
- ,(if invert-p
- `(not (string-match ,regexp elem))
- `(string-match ,regexp elem))))))))
+ (let* ((match (or (eshell-get-delimited-modifier-argument t)
+ (error "Malformed pattern string for modifier `s'")))
+ (replace (or (eshell-get-delimited-modifier-argument)
+ (error "Malformed replace string for modifier `s'")))
+ (function (if repeat
+ (lambda (str)
+ (replace-regexp-in-string match replace str t))
+ (lambda (str)
+ (if (string-match match str)
+ (replace-match replace t nil str)
+ (error (concat str ": substitution failed")))))))
+ (lambda (lst) (mapcar function lst))))
+
+(defun eshell-include-members (mod-char &optional invert-p)
+ "Include only Lisp members matching a regexp.
+If INVERT-P is non-nil, include only members not matching a regexp."
+ (let* ((regexp (or (eshell-get-delimited-modifier-argument)
+ (error "Malformed pattern string for modifier `%c'"
+ mod-char)))
+ (predicates
+ (list (if invert-p
+ (lambda (elem) (not (string-match regexp elem)))
+ (lambda (elem) (string-match regexp elem))))))
+ (lambda (lst)
+ (eshell-winnow-list lst nil predicates))))
(defun eshell-join-members ()
"Return a modifier function that join matches."
- (let ((delim (char-after))
- str end)
- (if (not (memq delim '(?' ?/)))
- (setq delim " ")
- (forward-char)
- (setq end (eshell-find-delimiter delim delim nil nil t)
- str (buffer-substring-no-properties (point) end))
- (goto-char (1+ end)))
- `(lambda (lst)
- (mapconcat 'identity lst ,str))))
+ (let ((str (or (eshell-get-delimited-modifier-argument)
+ " ")))
+ (lambda (lst)
+ (mapconcat #'identity lst str))))
(defun eshell-split-members ()
"Return a modifier function that splits members."
- (let ((delim (char-after))
- sep end)
- (when (memq delim '(?' ?/))
- (forward-char)
- (setq end (eshell-find-delimiter delim delim nil nil t)
- sep (buffer-substring-no-properties (point) end))
- (goto-char (1+ end)))
- `(lambda (lst)
- (mapcar
- (function
- (lambda (str)
- (split-string str ,sep))) lst))))
+ (let ((sep (eshell-get-delimited-modifier-argument)))
+ (lambda (lst)
+ (mapcar
+ (lambda (str)
+ (split-string str sep))
+ lst))))
(provide 'em-pred)
diff --git a/lisp/eshell/em-prompt.el b/lisp/eshell/em-prompt.el
index 76dd13ff842..a1a91e7d634 100644
--- a/lisp/eshell/em-prompt.el
+++ b/lisp/eshell/em-prompt.el
@@ -1,6 +1,6 @@
;;; em-prompt.el --- command prompts -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -48,10 +48,9 @@ as is common with most shells."
(autoload 'eshell/pwd "em-dirs")
(defcustom eshell-prompt-function
- (function
- (lambda ()
- (concat (abbreviate-file-name (eshell/pwd))
- (if (= (user-uid) 0) " # " " $ "))))
+ (lambda ()
+ (concat (abbreviate-file-name (eshell/pwd))
+ (if (= (user-uid) 0) " # " " $ ")))
"A function that returns the Eshell prompt string.
Make sure to update `eshell-prompt-regexp' so that it will match your
prompt."
@@ -80,7 +79,6 @@ re-entered for it to take effect."
For highlighting other kinds of strings -- similar to shell mode's
behavior -- simply use an output filer which changes text properties."
:group 'eshell-prompt)
-(define-obsolete-face-alias 'eshell-prompt-face 'eshell-prompt "22.1")
(defcustom eshell-before-prompt-hook nil
"A list of functions to call before outputting the prompt."
@@ -98,22 +96,29 @@ arriving, or after."
:options '(eshell-show-maximum-output)
:group 'eshell-prompt)
+(defvar-keymap eshell-prompt-mode-map
+ "C-c C-n" #'eshell-next-prompt
+ "C-c C-p" #'eshell-previous-prompt)
+
;;; Functions:
-(defun eshell-prompt-initialize ()
+(define-minor-mode eshell-prompt-mode
+ "Minor mode for eshell-prompt module.
+
+\\{eshell-prompt-mode-map}"
+ :keymap eshell-prompt-mode-map)
+
+(defun eshell-prompt-initialize () ;Called from `eshell-mode' via intern-soft!
"Initialize the prompting code."
(unless eshell-non-interactive-p
(add-hook 'eshell-post-command-hook 'eshell-emit-prompt nil t)
(make-local-variable 'eshell-prompt-regexp)
(if eshell-prompt-regexp
- (set (make-local-variable 'paragraph-start) eshell-prompt-regexp))
-
- (set (make-local-variable 'eshell-skip-prompt-function)
- 'eshell-skip-prompt)
+ (setq-local paragraph-start eshell-prompt-regexp))
- (define-key eshell-command-map [(control ?n)] 'eshell-next-prompt)
- (define-key eshell-command-map [(control ?p)] 'eshell-previous-prompt)))
+ (setq-local eshell-skip-prompt-function #'eshell-skip-prompt)
+ (eshell-prompt-mode)))
(defun eshell-emit-prompt ()
"Emit a prompt if eshell is being used interactively."
@@ -178,7 +183,7 @@ See `eshell-prompt-regexp'."
"Move to end of Nth previous prompt in the buffer.
See `eshell-prompt-regexp'."
(interactive "p")
- (beginning-of-line) ; Don't count prompt on current line.
+ (forward-line 0) ; Don't count prompt on current line.
(eshell-next-prompt (- n)))
(defun eshell-skip-prompt ()
diff --git a/lisp/eshell/em-rebind.el b/lisp/eshell/em-rebind.el
index 07f4318e58c..2b56c9e8444 100644
--- a/lisp/eshell/em-rebind.el
+++ b/lisp/eshell/em-rebind.el
@@ -1,6 +1,6 @@
;;; em-rebind.el --- rebind keys when point is at current input -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -114,7 +114,6 @@ This is default behavior of shells like bash."
backward-list
forward-page
backward-page
- forward-point
forward-paragraph
backward-paragraph
backward-prefix-chars
@@ -137,6 +136,9 @@ This is default behavior of shells like bash."
:type '(repeat function)
:group 'eshell-rebind)
+(defvar-keymap eshell-rebind-mode-map
+ "C-c M-l" #'eshell-lock-local-map)
+
;; Internal Variables:
(defvar eshell-input-keymap)
@@ -145,7 +147,13 @@ This is default behavior of shells like bash."
;;; Functions:
-(defun eshell-rebind-initialize ()
+(define-minor-mode eshell-rebind-mode
+ "Minor mode for the eshell-rebind module.
+
+\\{eshell-rebind-mode-map}"
+ :keymap eshell-rebind-mode-map)
+
+(defun eshell-rebind-initialize () ;Called from `eshell-mode' via intern-soft!
"Initialize the inputting code."
(unless eshell-non-interactive-p
(add-hook 'eshell-mode-hook 'eshell-setup-input-keymap nil t)
@@ -153,12 +161,12 @@ This is default behavior of shells like bash."
(add-hook 'pre-command-hook 'eshell-save-previous-point nil t)
(make-local-variable 'overriding-local-map)
(add-hook 'post-command-hook 'eshell-rebind-input-map nil t)
- (set (make-local-variable 'eshell-lock-keymap) nil)
- (define-key eshell-command-map [(meta ?l)] 'eshell-lock-local-map)))
+ (setq-local eshell-lock-keymap nil)
+ (eshell-rebind-mode)))
(defun eshell-lock-local-map (&optional arg)
"Lock or unlock the current local keymap.
-Within a prefix arg, set the local keymap to its normal value, and
+With prefix ARG, set the local keymap to its normal value, and
lock it at that."
(interactive "P")
(if (or arg (not eshell-lock-keymap))
@@ -209,8 +217,7 @@ lock it at that."
(defun eshell-setup-input-keymap ()
"Setup the input keymap to be used during input editing."
- (make-local-variable 'eshell-input-keymap)
- (setq eshell-input-keymap (make-sparse-keymap))
+ (setq-local eshell-input-keymap (make-sparse-keymap))
(set-keymap-parent eshell-input-keymap eshell-mode-map)
(let ((bindings eshell-rebind-keys-alist))
(while bindings
@@ -223,7 +230,7 @@ lock it at that."
(interactive "P")
(let ((count (prefix-numeric-value n)))
(if (eshell-point-within-input-p (- (point) count))
- (delete-backward-char count n)
+ (delete-char (- count) n)
(beep))))
(defun eshell-delchar-or-maybe-eof (arg)
@@ -231,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 bbc2f9acf6b..e0bcd8b099f 100644
--- a/lisp/eshell/em-script.el
+++ b/lisp/eshell/em-script.el
@@ -1,6 +1,6 @@
;;; em-script.el --- Eshell script files -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -23,8 +23,7 @@
;;; Code:
-(require 'eshell)
-(require 'esh-opt)
+(require 'esh-mode)
;;;###autoload
(progn
@@ -57,29 +56,29 @@ This includes when running `eshell-command'."
;;; Functions:
-(defun eshell-script-initialize ()
+(defun eshell-script-initialize () ;Called from `eshell-mode' via intern-soft!
"Initialize the script parsing code."
- (make-local-variable 'eshell-interpreter-alist)
- (setq eshell-interpreter-alist
- (cons (cons #'(lambda (file args)
- (string= (file-name-nondirectory file)
- "eshell"))
- 'eshell/source)
- eshell-interpreter-alist))
- (make-local-variable 'eshell-complex-commands)
- (setq eshell-complex-commands
+ (setq-local eshell-interpreter-alist
+ (cons (cons (lambda (file _args)
+ (and (file-regular-p file)
+ (string= (file-name-nondirectory file)
+ "eshell")))
+ 'eshell/source)
+ eshell-interpreter-alist))
+ (setq-local eshell-complex-commands
(append '("source" ".") eshell-complex-commands))
;; these two variables are changed through usage, but we don't want
;; to ruin it for other modules
(let (eshell-inside-quote-regexp
eshell-outside-quote-regexp)
- (and (not eshell-non-interactive-p)
+ (and (not (bound-and-true-p eshell-non-interactive-p))
eshell-login-script
(file-readable-p eshell-login-script)
(eshell-do-eval
(list 'eshell-commands
(catch 'eshell-replace-command
- (eshell-source-file eshell-login-script))) t))
+ (eshell-source-file eshell-login-script)))
+ t))
(and eshell-rc-script
(file-readable-p eshell-rc-script)
(eshell-do-eval
@@ -90,7 +89,6 @@ This includes when running `eshell-command'."
(defun eshell-source-file (file &optional args subcommand-p)
"Execute a series of Eshell commands in FILE, passing ARGS.
Comments begin with `#'."
- (interactive "f")
(let ((orig (point))
(here (point-max))
(inhibit-point-motion-hooks t))
@@ -115,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-smart.el b/lisp/eshell/em-smart.el
index f79f46387b7..6768cee4c34 100644
--- a/lisp/eshell/em-smart.el
+++ b/lisp/eshell/em-smart.el
@@ -1,6 +1,6 @@
;;; em-smart.el --- smart display of output -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -94,10 +94,9 @@ it to get a real sense of how it works."
(defcustom eshell-smart-unload-hook
(list
- (function
- (lambda ()
- (remove-hook 'window-configuration-change-hook
- 'eshell-refresh-windows))))
+ (lambda ()
+ (remove-hook 'window-configuration-change-hook
+ 'eshell-refresh-windows)))
"A hook that gets run when `eshell-smart' is unloaded."
:type 'hook
:group 'eshell-smart)
@@ -132,7 +131,7 @@ only if that output can be presented in its entirely in the Eshell window."
:group 'eshell-smart)
(defcustom eshell-smart-space-goes-to-end t
- "If non-nil, space will go to end of buffer when point-max is visible.
+ "If non-nil, space will go to end of buffer when `point-max' is visible.
That is, if a command is running and the user presses SPACE at a time
when the end of the buffer is visible, point will go to the end of the
buffer and smart-display will be turned off (that is, subsequently
@@ -166,14 +165,14 @@ The options are `begin', `after' or `end'."
;;; Functions:
-(defun eshell-smart-initialize ()
+(defun eshell-smart-initialize () ;Called from `eshell-mode' via intern-soft!
"Setup Eshell smart display."
(unless eshell-non-interactive-p
;; override a few variables, since they would interfere with the
;; smart display functionality.
- (set (make-local-variable 'eshell-scroll-to-bottom-on-output) nil)
- (set (make-local-variable 'eshell-scroll-to-bottom-on-input) nil)
- (set (make-local-variable 'eshell-scroll-show-maximum-output) t)
+ (setq-local eshell-scroll-to-bottom-on-output nil)
+ (setq-local eshell-scroll-to-bottom-on-input nil)
+ (setq-local eshell-scroll-show-maximum-output t)
(add-hook 'window-scroll-functions 'eshell-smart-scroll-window nil t)
(add-hook 'window-configuration-change-hook 'eshell-refresh-windows)
@@ -186,9 +185,8 @@ The options are `begin', `after' or `end'."
(make-local-variable 'eshell-smart-command-done)
(add-hook 'eshell-post-command-hook
- (function
- (lambda ()
- (setq eshell-smart-command-done t)))
+ (lambda ()
+ (setq eshell-smart-command-done t))
t t)
(unless (eq eshell-review-quick-commands t)
@@ -197,7 +195,7 @@ The options are `begin', `after' or `end'."
;; This is called by window-scroll-functions with two arguments.
(defun eshell-smart-scroll-window (wind _start)
- "Scroll the given Eshell window accordingly."
+ "Scroll the given Eshell window WIND accordingly."
(unless eshell-currently-handling-window
(let ((inhibit-point-motion-hooks t)
(eshell-currently-handling-window t))
@@ -208,13 +206,12 @@ The options are `begin', `after' or `end'."
"Refresh all visible Eshell buffers."
(let (affected)
(walk-windows
- (function
- (lambda (wind)
- (with-current-buffer (window-buffer wind)
- (if eshell-mode
- (let (window-scroll-functions) ;;FIXME: Why?
- (eshell-smart-scroll-window wind (window-start))
- (setq affected t))))))
+ (lambda (wind)
+ (with-current-buffer (window-buffer wind)
+ (if eshell-mode
+ (let (window-scroll-functions) ;;FIXME: Why?
+ (eshell-smart-scroll-window wind (window-start))
+ (setq affected t)))))
0 frame)
(if affected
(let (window-scroll-functions) ;;FIXME: Why?
diff --git a/lisp/eshell/em-term.el b/lisp/eshell/em-term.el
index 261a32e97cf..6811e703137 100644
--- a/lisp/eshell/em-term.el
+++ b/lisp/eshell/em-term.el
@@ -1,6 +1,6 @@
;;; em-term.el --- running visual commands -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -53,22 +53,21 @@ which commands are considered visual in nature."
(defcustom eshell-term-load-hook nil
"A list of functions to call when loading `eshell-term'."
:version "24.1" ; removed eshell-term-initialize
- :type 'hook
- :group 'eshell-term)
+ :type 'hook)
(defcustom eshell-visual-commands
- '("vi" ; what is going on??
- "screen" "top" ; ok, a valid program...
+ '("vi" "vim" ; what is going on??
+ "screen" "tmux" "top" "htop" ; ok, a valid program...
"less" "more" ; M-x view-file
- "lynx" "ncftp" ; w3.el, ange-ftp
- "pine" "tin" "trn" "elm") ; GNUS!!
+ "lynx" "links" "ncftp" ; eww, ange-ftp
+ "mutt" "pine" "tin" "trn" "elm") ; GNUS!!
"A list of commands that present their output in a visual fashion.
Commands listed here are run in a term buffer.
See also `eshell-visual-subcommands' and `eshell-visual-options'."
:type '(repeat string)
- :group 'eshell-term)
+ :version "29.1")
(defcustom eshell-visual-subcommands
nil
@@ -89,18 +88,17 @@ because git shows logs and diffs using a pager by default.
See also `eshell-visual-commands' and `eshell-visual-options'."
:type '(repeat (cons (string :tag "Command")
(repeat (string :tag "Subcommand"))))
- :version "24.4"
- :group 'eshell-term)
+ :version "24.4")
(defcustom eshell-visual-options
nil
- "An alist of the form
+ "An alist of commands that present their output in a visual fashion.
+It has this form:
((COMMAND1 OPTION1 OPTION2...)
(COMMAND2 OPTION1 ...))
-of commands with options that present their output in a visual
-fashion. For example, a sensible entry would be
+For example, a sensible entry would be
(\"git\" \"--help\" \"--paginate\")
@@ -111,8 +109,7 @@ always uses a pager for output.
See also `eshell-visual-commands' and `eshell-visual-subcommands'."
:type '(repeat (cons (string :tag "Command")
(repeat (string :tag "Option"))))
- :version "24.4"
- :group 'eshell-term)
+ :version "24.4")
;; If you change this from term-term-name, you need to ensure that the
;; value you choose exists in the system's terminfo database. (Bug#12485)
@@ -121,8 +118,7 @@ See also `eshell-visual-commands' and `eshell-visual-subcommands'."
See `term-term-name' in term.el for more information on how this is
used."
:version "24.3" ; eterm -> term-term-name = eterm-color
- :type 'string
- :group 'eshell-term)
+ :type 'string)
(defcustom eshell-escape-control-x t
"If non-nil, allow <C-x> to be handled by Emacs key in visual buffers.
@@ -130,16 +126,14 @@ See the variables `eshell-visual-commands',
`eshell-visual-subcommands', and `eshell-visual-options'. If
this variable is set to nil, <C-x> will send that control
character to the invoked process."
- :type 'boolean
- :group 'eshell-term)
+ :type 'boolean)
(defcustom eshell-destroy-buffer-when-process-dies nil
"If non-nil, term buffers are destroyed after their processes die.
WARNING: Setting this to non-nil may result in unexpected
behavior for short-lived processes, see bug#18108."
:version "25.1"
- :type 'boolean
- :group 'eshell-term)
+ :type 'boolean)
;;; Internal Variables:
@@ -147,20 +141,19 @@ behavior for short-lived processes, see bug#18108."
;;; Functions:
-(defun eshell-term-initialize ()
+(defun eshell-term-initialize () ;Called from `eshell-mode' via intern-soft!
"Initialize the `term' interface code."
- (make-local-variable 'eshell-interpreter-alist)
- (setq eshell-interpreter-alist
+ (setq-local eshell-interpreter-alist
(cons (cons #'eshell-visual-command-p
'eshell-exec-visual)
eshell-interpreter-alist)))
(defun eshell-visual-command-p (command args)
- "Returns non-nil when given a visual command.
+ "Return non-nil when given a visual command.
If either COMMAND or a subcommand in ARGS (e.g. git log) is a
visual command, returns non-nil."
(let ((command (file-name-nondirectory command)))
- (and (eshell-interactive-output-p)
+ (and (eshell-interactive-output-p 'all)
(or (member command eshell-visual-commands)
(member (car args)
(cdr (assoc command eshell-visual-subcommands)))
@@ -175,7 +168,7 @@ allowed."
(let* (eshell-interpreter-alist
(interp (eshell-find-interpreter (car args) (cdr args)))
(program (car interp))
- (args (eshell-flatten-list
+ (args (flatten-tree
(eshell-stringify-list (append (cdr interp)
(cdr args)))))
(term-buf
@@ -185,17 +178,18 @@ allowed."
(save-current-buffer
(switch-to-buffer term-buf)
(term-mode)
- (set (make-local-variable 'term-term-name) eshell-term-name)
- (make-local-variable 'eshell-parent-buffer)
- (setq eshell-parent-buffer eshell-buf)
+ (setq-local term-term-name eshell-term-name)
+ (setq-local eshell-parent-buffer eshell-buf)
(term-exec term-buf program program nil args)
(let ((proc (get-buffer-process term-buf)))
(if (and proc (eq 'run (process-status proc)))
- (set-process-sentinel proc 'eshell-term-sentinel)
+ (set-process-sentinel proc #'eshell-term-sentinel)
(error "Failed to invoke visual command")))
(term-char-mode)
- (if eshell-escape-control-x
- (term-set-escape-char ?\C-x))))
+ (when eshell-escape-control-x
+ ;; Don't drop existing escape char.
+ (let (term-escape-char)
+ (term-set-escape-char ?\C-x)))))
nil)
;; Process sentinels receive two arguments.
@@ -232,7 +226,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
@@ -264,17 +258,13 @@ the buffer."
; (defun eshell-term-mouse-paste (click arg)
; "Insert the last stretch of killed text at the position clicked on."
; (interactive "e\nP")
-; (if (boundp 'xemacs-logo)
-; (eshell-term-send-raw-string
-; (or (condition-case () (x-get-selection) (error ()))
-; (error "No selection available")))
-; ;; Give temporary modes such as isearch a chance to turn off.
-; (run-hooks 'mouse-leave-buffer-hook)
-; (setq this-command 'yank)
-; (eshell-term-send-raw-string
-; (current-kill (cond ((listp arg) 0)
-; ((eq arg '-) -1)
-; (t (1- arg)))))))
+; ;; Give temporary modes such as isearch a chance to turn off.
+; (run-hooks 'mouse-leave-buffer-hook)
+; (setq this-command 'yank)
+; (eshell-term-send-raw-string
+; (current-kill (cond ((listp arg) 0)
+; ((eq arg '-) -1)
+; (t (1- arg))))))
; ;; Which would be better: "\e[A" or "\eOA"? readline accepts either.
; ;; For my configuration it's definitely better \eOA but YMMV. -mm
@@ -322,9 +312,7 @@ the buffer."
; (setq eshell-term-raw-map map)
; (setq eshell-term-raw-escape-map
; (copy-keymap (lookup-key (current-global-map) "\C-x")))
-; (if (boundp 'xemacs-logo)
-; (define-key eshell-term-raw-map [button2] 'eshell-term-mouse-paste)
-; (define-key eshell-term-raw-map [mouse-2] 'eshell-term-mouse-paste))
+; (define-key eshell-term-raw-map [mouse-2] 'eshell-term-mouse-paste)
; (define-key eshell-term-raw-map [up] 'eshell-term-send-up)
; (define-key eshell-term-raw-map [down] 'eshell-term-send-down)
; (define-key eshell-term-raw-map [right] 'eshell-term-send-right)
diff --git a/lisp/eshell/em-tramp.el b/lisp/eshell/em-tramp.el
index e322cea1e21..aebbc36e71d 100644
--- a/lisp/eshell/em-tramp.el
+++ b/lisp/eshell/em-tramp.el
@@ -1,6 +1,6 @@
;;; em-tramp.el --- Eshell features that require TRAMP -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: Aidan Gauland <aidalgol@no8wireless.co.nz>
@@ -26,12 +26,15 @@
;;; Code:
(require 'esh-util)
+(require 'esh-cmd)
(eval-when-compile
(require 'esh-mode)
(require 'eshell)
(require 'tramp))
+;; There are no items in this custom group, but eshell modules (ab)use
+;; custom groups.
;;;###autoload
(progn
(defgroup eshell-tramp nil
@@ -43,15 +46,14 @@
:tag "TRAMP Eshell features"
:group 'eshell-module))
-(defun eshell-tramp-initialize ()
+(defun eshell-tramp-initialize () ;Called from `eshell-mode' via intern-soft!
"Initialize the TRAMP-using commands code."
(when (eshell-using-module 'eshell-cmpl)
(add-hook 'pcomplete-try-first-hook
'eshell-complete-host-reference nil t))
- (make-local-variable 'eshell-complex-commands)
- (setq eshell-complex-commands
- (append '("su" "sudo")
- eshell-complex-commands)))
+ (setq-local eshell-complex-commands
+ (append '("su" "sudo")
+ eshell-complex-commands)))
(autoload 'eshell-parse-command "esh-cmd")
@@ -59,37 +61,33 @@
"Alias \"su\" to call TRAMP.
Uses the system su through TRAMP's su method."
- (setq args (eshell-stringify-list (eshell-flatten-list 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)
@@ -97,40 +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 (eshell-flatten-list 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
- :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/em-unix.el b/lisp/eshell/em-unix.el
index c486d2c51d1..40b83010f94 100644
--- a/lisp/eshell/em-unix.el
+++ b/lisp/eshell/em-unix.el
@@ -1,6 +1,6 @@
;;; em-unix.el --- UNIX command aliases -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -35,8 +35,7 @@
;;; Code:
-(require 'eshell)
-(require 'esh-opt)
+(require 'esh-mode)
(require 'pcomplete)
;;;###autoload
@@ -140,13 +139,12 @@ Otherwise, Emacs will attempt to use rsh to invoke du on the remote machine."
;;; Functions:
-(defun eshell-unix-initialize ()
+(defun eshell-unix-initialize () ;Called from `eshell-mode' via intern-soft!
"Initialize the UNIX support/emulation code."
(when (eshell-using-module 'eshell-cmpl)
(add-hook 'pcomplete-try-first-hook
'eshell-complete-host-reference nil t))
- (make-local-variable 'eshell-complex-commands)
- (setq eshell-complex-commands
+ (setq-local eshell-complex-commands
(append '("grep" "egrep" "fgrep" "agrep" "glimpse" "locate"
"cat" "time" "cp" "mv" "make" "du" "diff")
eshell-complex-commands)))
@@ -167,11 +165,13 @@ Otherwise, Emacs will attempt to use rsh to invoke du on the remote machine."
(put 'eshell/man 'eshell-no-numeric-conversions t)
(defun eshell/info (&rest args)
- "Run the info command in-frame with the same behavior as command-line `info', ie:
+ "Run the info command in-frame with the same behavior as command-line `info'.
+For example:
`info' => goes to top info window
`info arg1' => IF arg1 is a file, then visits arg1
`info arg1' => OTHERWISE goes to top info window and then menu item arg1
- `info arg1 arg2' => does action for arg1 (either visit-file or menu-item) and then menu item arg2
+ `info arg1 arg2' => does action for arg1 (either visit-file or menu-item) and
+ then menu item arg2
etc."
(eval-and-compile (require 'info))
(let ((file (cond
@@ -231,7 +231,7 @@ Otherwise, Emacs will attempt to use rsh to invoke du on the remote machine."
This is implemented to call either `delete-file', `kill-buffer',
`kill-process', or `unintern', depending on the nature of the
argument."
- (setq args (eshell-flatten-list args))
+ (setq args (flatten-tree args))
(eshell-eval-using-options
"rm" args
'((?h "help" nil nil "show this usage screen")
@@ -307,6 +307,7 @@ Remove (unlink) the FILE(s).")
nil))
(put 'eshell/rm 'eshell-no-numeric-conversions t)
+(put 'eshell/rm 'eshell-filename-arguments t)
(defun eshell/mkdir (&rest args)
"Implementation of mkdir in Lisp."
@@ -324,6 +325,7 @@ Create the DIRECTORY(ies), if they do not already exist.")
nil))
(put 'eshell/mkdir 'eshell-no-numeric-conversions t)
+(put 'eshell/mkdir 'eshell-filename-arguments t)
(defun eshell/rmdir (&rest args)
"Implementation of rmdir in Lisp."
@@ -340,6 +342,7 @@ Remove the DIRECTORY(ies), if they are empty.")
nil))
(put 'eshell/rmdir 'eshell-no-numeric-conversions t)
+(put 'eshell/rmdir 'eshell-filename-arguments t)
(defvar no-dereference)
@@ -367,12 +370,14 @@ Remove the DIRECTORY(ies), if they are empty.")
(or (not (eshell-under-windows-p))
(eq system-type 'ms-dos))
(setq attr (eshell-file-attributes (car files)))
- (nth 10 attr-target) (nth 10 attr)
- ;; Use equal, not -, since the inode and the device could
- ;; cons cells.
- (equal (nth 10 attr-target) (nth 10 attr))
- (nth 11 attr-target) (nth 11 attr)
- (equal (nth 11 attr-target) (nth 11 attr)))
+ (file-attribute-inode-number attr-target)
+ (file-attribute-inode-number attr)
+ (equal (file-attribute-inode-number attr-target)
+ (file-attribute-inode-number attr))
+ (file-attribute-device-number attr-target)
+ (file-attribute-device-number attr)
+ (equal (file-attribute-device-number attr-target)
+ (file-attribute-device-number attr)))
(eshell-error (format-message "%s: `%s' and `%s' are the same file\n"
command (car files) target)))
(t
@@ -394,16 +399,16 @@ Remove the DIRECTORY(ies), if they are empty.")
(let (eshell-warn-dot-directories)
(if (and (not deep)
(eq func 'rename-file)
- ;; Use equal, since the device might be a
- ;; cons cell.
- (equal (nth 11 (eshell-file-attributes
- (file-name-directory
- (directory-file-name
- (expand-file-name source)))))
- (nth 11 (eshell-file-attributes
- (file-name-directory
- (directory-file-name
- (expand-file-name target)))))))
+ (equal (file-attribute-device-number
+ (eshell-file-attributes
+ (file-name-directory
+ (directory-file-name
+ (expand-file-name source)))))
+ (file-attribute-device-number
+ (eshell-file-attributes
+ (file-name-directory
+ (directory-file-name
+ (expand-file-name target)))))))
(apply 'eshell-funcalln func source target args)
(unless (file-directory-p target)
(if em-verbose
@@ -415,9 +420,8 @@ Remove the DIRECTORY(ies), if they are empty.")
(apply 'eshell-shuffle-files
command action
(mapcar
- (function
- (lambda (file)
- (concat source "/" file)))
+ (lambda (file)
+ (concat source "/" file))
(directory-files source))
target func t args)
(when (eq func 'rename-file)
@@ -435,7 +439,10 @@ Remove the DIRECTORY(ies), if they are empty.")
(setq link (file-symlink-p source)))
(progn
(apply 'eshell-funcalln 'make-symbolic-link
- link target args)
+ link target
+ ;; `make-symbolic-link' doesn't have
+ ;; KEEP-TIME; just OK-IF-ALREADY-EXISTS.
+ (list (car args)))
(if (eq func 'rename-file)
(if (and (file-directory-p source)
(not (file-symlink-p source)))
@@ -465,8 +472,6 @@ Remove the DIRECTORY(ies), if they are empty.")
(eshell-parse-command
(format "tar %s %s" tar-args archive) args))))
-(defvar ange-cache) ; XEmacs? See esh-util
-
;; this is to avoid duplicating code...
(defmacro eshell-mvcpln-template (command action func query-var
force-var &optional preserve)
@@ -476,7 +481,7 @@ Remove the DIRECTORY(ies), if they are empty.")
(error "%s: missing destination file or directory" ,command))
(if (= len 1)
(nconc args '(".")))
- (setq args (eshell-stringify-list (eshell-flatten-list args)))
+ (setq args (eshell-stringify-list (flatten-tree args)))
(if (and ,(not (equal command "ln"))
(string-match eshell-tar-regexp (car (last args)))
(or (> (length args) 2)
@@ -484,8 +489,7 @@ Remove the DIRECTORY(ies), if they are empty.")
(or (not no-dereference)
(not (file-symlink-p (car args)))))))
(eshell-shorthand-tar-command ,command args)
- (let ((target (car (last args)))
- ange-cache)
+ (let ((target (car (last args))))
(setcdr (last args 2) nil)
(eshell-shuffle-files
,command ,action args target ,func nil
@@ -524,6 +528,7 @@ Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.
eshell-mv-overwrite-files))))
(put 'eshell/mv 'eshell-no-numeric-conversions t)
+(put 'eshell/mv 'eshell-filename-arguments t)
(defun eshell/cp (&rest args)
"Implementation of cp in Lisp."
@@ -561,6 +566,7 @@ Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.")
eshell-cp-overwrite-files preserve)))
(put 'eshell/cp 'eshell-no-numeric-conversions t)
+(put 'eshell/cp 'eshell-filename-arguments t)
(defun eshell/ln (&rest args)
"Implementation of ln in Lisp."
@@ -593,12 +599,13 @@ with `--symbolic'. When creating hard links, each TARGET must exist.")
eshell-ln-overwrite-files))))
(put 'eshell/ln 'eshell-no-numeric-conversions t)
+(put 'eshell/ln 'eshell-filename-arguments t)
(defun eshell/cat (&rest args)
"Implementation of cat in Lisp.
If in a pipeline, or the file is not a regular file, directory or
symlink, then revert to the system's definition of cat."
- (setq args (eshell-stringify-list (eshell-flatten-list args)))
+ (setq args (eshell-stringify-list (flatten-tree args)))
(if (or eshell-in-pipeline-p
(catch 'special
(dolist (arg args)
@@ -606,7 +613,8 @@ symlink, then revert to the system's definition of cat."
(> (length arg) 0)
(eq (aref arg 0) ?-))
(let ((attrs (eshell-file-attributes arg)))
- (and attrs (memq (aref (nth 8 attrs) 0)
+ (and attrs
+ (memq (aref (file-attribute-modes attrs) 0)
'(?d ?l ?-)))))
(throw 'special t)))))
(let ((ext-cat (eshell-search-path "cat")))
@@ -645,11 +653,13 @@ Concatenate FILE(s), or standard input, to standard output.")
(setq eshell-ensure-newline-p nil))))
(put 'eshell/cat 'eshell-no-numeric-conversions t)
+(put 'eshell/cat 'eshell-filename-arguments t)
;; special front-end functions for compilation-mode buffers
(defun eshell/make (&rest args)
- "Use `compile' to do background makes."
+ "Use `compile' to do background makes.
+Fallback to standard make when called synchronously."
(if (and eshell-current-subjob-p
(eshell-interactive-output-p))
(let ((compilation-process-setup-function
@@ -659,7 +669,7 @@ Concatenate FILE(s), or standard input, to standard output.")
(compile (concat "make " (eshell-flatten-and-stringify args))))
(throw 'eshell-replace-command
(eshell-parse-command "*make" (eshell-stringify-list
- (eshell-flatten-list args))))))
+ (flatten-tree args))))))
(put 'eshell/make 'eshell-no-numeric-conversions t)
@@ -694,7 +704,7 @@ available..."
(erase-buffer)
(occur-mode)
(let ((files (eshell-stringify-list
- (eshell-flatten-list (cdr args))))
+ (flatten-tree (cdr args))))
(inhibit-redisplay t)
string)
(when (car args)
@@ -739,35 +749,27 @@ external command."
(throw 'eshell-replace-command
(eshell-parse-command (concat "*" command)
(eshell-stringify-list
- (eshell-flatten-list args))))
+ (flatten-tree args))))
(let* ((args (mapconcat 'identity
(mapcar 'shell-quote-argument
(eshell-stringify-list
- (eshell-flatten-list args)))
+ (flatten-tree args)))
" "))
- (cmd (progn
- (set-text-properties 0 (length args)
- '(invisible t) args)
- (format "%s -n %s"
- (pcase command
- ("egrep" "grep -E")
- ("fgrep" "grep -F")
- (x x))
- args)))
+ (cmd (format "%s -n %s" command args))
compilation-scroll-output)
(grep cmd)))))
(defun eshell/grep (&rest args)
"Use Emacs grep facility instead of calling external grep."
- (eshell-grep "grep" args t))
+ (eshell-grep "grep" (append '("-H") args) t))
(defun eshell/egrep (&rest args)
"Use Emacs grep facility instead of calling external grep -E."
- (eshell-grep "egrep" args t))
+ (eshell-grep "grep" (append '("-EH") args) t))
(defun eshell/fgrep (&rest args)
"Use Emacs grep facility instead of calling external grep -F."
- (eshell-grep "fgrep" args t))
+ (eshell-grep "grep" (append '("-FH") args) t))
(defun eshell/agrep (&rest args)
"Use Emacs grep facility instead of calling external agrep."
@@ -780,9 +782,9 @@ external command."
;; completions rules for some common UNIX commands
-(defsubst eshell-complete-hostname ()
- "Complete a command that wants a hostname for an argument."
- (pcomplete-here (eshell-read-host-names)))
+(autoload 'pcmpl-unix-complete-hostname "pcmpl-unix")
+(define-obsolete-function-alias 'eshell-complete-hostname
+ #'pcmpl-unix-complete-hostname "28.1")
(defun eshell-complete-host-reference ()
"If there is a host reference, complete it."
@@ -791,26 +793,7 @@ external command."
(when (setq index (string-match "@[a-z.]*\\'" arg))
(setq pcomplete-stub (substring arg (1+ index))
pcomplete-last-completion-raw t)
- (throw 'pcomplete-completions (eshell-read-host-names)))))
-
-(defalias 'pcomplete/ftp 'eshell-complete-hostname)
-(defalias 'pcomplete/ncftp 'eshell-complete-hostname)
-(defalias 'pcomplete/ping 'eshell-complete-hostname)
-(defalias 'pcomplete/rlogin 'eshell-complete-hostname)
-
-(defun pcomplete/telnet ()
- (require 'pcmpl-unix)
- (pcomplete-opt "xl(pcmpl-unix-user-names)")
- (eshell-complete-hostname))
-
-(defun pcomplete/rsh ()
- "Complete `rsh', which, after the user and hostname, is like xargs."
- (require 'pcmpl-unix)
- (pcomplete-opt "l(pcmpl-unix-user-names)")
- (eshell-complete-hostname)
- (pcomplete-here (funcall pcomplete-command-completion-function))
- (funcall (or (pcomplete-find-completion-function (pcomplete-arg 1))
- pcomplete-default-completion-function)))
+ (throw 'pcomplete-completions (pcomplete-read-host-names)))))
(defvar block-size)
(defvar by-bytes)
@@ -835,19 +818,19 @@ external command."
(unless (string-match "\\`\\.\\.?\\'" (caar entries))
(let* ((entry (concat path "/"
(caar entries)))
- (symlink (and (stringp (cadr (car entries)))
- (cadr (car entries)))))
+ (symlink (and (stringp (file-attribute-type (cdar entries)))
+ (file-attribute-type (cdar entries)))))
(unless (or (and symlink (not dereference-links))
(and only-one-filesystem
(/= only-one-filesystem
- (nth 12 (car entries)))))
+ (file-attribute-device-number (cdar entries)))))
(if symlink
(setq entry symlink))
(setq size
(+ size
- (if (eq t (cadr (car entries)))
+ (if (eq t (car (cdar entries)))
(eshell-du-sum-directory entry (1+ depth))
- (let ((file-size (nth 8 (car entries))))
+ (let ((file-size (file-attribute-size (cdar entries))))
(prog1
file-size
(if show-all
@@ -865,7 +848,7 @@ external command."
(defun eshell/du (&rest args)
"Implementation of \"du\" in Lisp, passing ARGS."
(setq args (if args
- (eshell-stringify-list (eshell-flatten-list args))
+ (eshell-stringify-list (flatten-tree args))
'(".")))
(let ((ext-du (eshell-search-path "du")))
(if (and ext-du
@@ -914,11 +897,11 @@ Summarize disk usage of each FILE, recursively for directories.")
;; filesystem support means nothing under Windows
(if (eshell-under-windows-p)
(setq only-one-filesystem nil))
- (let ((size 0.0) ange-cache)
+ (let ((size 0.0))
(while args
(if only-one-filesystem
(setq only-one-filesystem
- (nth 11 (eshell-file-attributes
+ (file-attribute-device-number (eshell-file-attributes
(file-name-as-directory (car args))))))
(setq size (+ size (eshell-du-sum-directory
(directory-file-name (car args)) 0)))
@@ -927,10 +910,13 @@ Summarize disk usage of each FILE, recursively for directories.")
(eshell-print (concat (eshell-du-size-string size)
"total\n"))))))))
+(put 'eshell/du 'eshell-filename-arguments t)
+
(defvar eshell-time-start nil)
(defun eshell-show-elapsed-time ()
- (let ((elapsed (format "%.3f secs\n" (- (float-time) eshell-time-start))))
+ (let ((elapsed (format "%.3f secs\n"
+ (float-time (time-since eshell-time-start)))))
(set-text-properties 0 (length elapsed) '(face bold) elapsed)
(eshell-interactive-print elapsed))
(remove-hook 'eshell-post-command-hook 'eshell-show-elapsed-time t))
@@ -961,11 +947,11 @@ Show wall-clock time elapsed during execution of COMMAND.")
;; after setting
(throw 'eshell-replace-command
(eshell-parse-command (car time-args)
-;;; https://lists.gnu.org/archive/html/bug-gnu-emacs/2007-08/msg00205.html
+;;; https://lists.gnu.org/r/bug-gnu-emacs/2007-08/msg00205.html
(eshell-stringify-list
- (eshell-flatten-list (cdr time-args))))))))
+ (flatten-tree (cdr time-args))))))))
-(defun eshell/whoami (&rest args)
+(defun eshell/whoami (&rest _args)
"Make \"whoami\" Tramp aware."
(or (file-remote-p default-directory 'user) (user-login-name)))
@@ -977,8 +963,8 @@ Show wall-clock time elapsed during execution of COMMAND.")
(if eshell-diff-window-config
(set-window-configuration eshell-diff-window-config)))
-(defun nil-blank-string (string)
- "Return STRING, or nil if STRING contains only non-blank characters."
+(defun eshell-nil-blank-string (string)
+ "Return STRING, or nil if STRING contains only blank characters."
(cond
((string-match "[^[:blank:]]" string) string)
(nil)))
@@ -987,7 +973,7 @@ Show wall-clock time elapsed during execution of COMMAND.")
(defun eshell/diff (&rest args)
"Alias \"diff\" to call Emacs `diff' function."
- (let ((orig-args (eshell-stringify-list (eshell-flatten-list args))))
+ (let ((orig-args (eshell-stringify-list (flatten-tree args))))
(if (or eshell-plain-diff-behavior
(not (and (eshell-interactive-output-p)
(not eshell-in-pipeline-p)
@@ -1008,27 +994,27 @@ Show wall-clock time elapsed during execution of COMMAND.")
(condition-case nil
(diff-no-select
old new
- (nil-blank-string (eshell-flatten-and-stringify args)))
+ (eshell-nil-blank-string (eshell-flatten-and-stringify args)))
(error
(throw 'eshell-replace-command
(eshell-parse-command "*diff" orig-args))))
(when (fboundp 'diff-mode)
- (make-local-variable 'compilation-finish-functions)
(add-hook
'compilation-finish-functions
- `(lambda (buff msg)
+ (lambda (buff _msg)
(with-current-buffer buff
(diff-mode)
- (set (make-local-variable 'eshell-diff-window-config)
- ,config)
- (local-set-key [?q] 'eshell-diff-quit)
+ (setq-local eshell-diff-window-config config)
+ (local-set-key [?q] #'eshell-diff-quit)
(if (fboundp 'turn-on-font-lock-if-enabled)
(turn-on-font-lock-if-enabled))
- (goto-char (point-min))))))
+ (goto-char (point-min))))
+ nil t))
(pop-to-buffer (current-buffer))))))
nil)
(put 'eshell/diff 'eshell-no-numeric-conversions t)
+(put 'eshell/diff 'eshell-filename-arguments t)
(defvar locate-history-list)
@@ -1042,7 +1028,7 @@ Show wall-clock time elapsed during execution of COMMAND.")
(string-match "^-" (car args))))
(throw 'eshell-replace-command
(eshell-parse-command "*locate" (eshell-stringify-list
- (eshell-flatten-list args))))
+ (flatten-tree args))))
(save-selected-window
(let ((locate-history-list (list (car args))))
(locate-with-filter (car args) (cadr args))))))
@@ -1058,6 +1044,8 @@ Show wall-clock time elapsed during execution of COMMAND.")
(put 'eshell/occur 'eshell-no-numeric-conversions t)
+(define-obsolete-function-alias 'nil-blank-string #'eshell-nil-blank-string "29.1")
+
(provide 'em-unix)
;; Local Variables:
diff --git a/lisp/eshell/em-xtra.el b/lisp/eshell/em-xtra.el
index 7b80f64d629..2583738d55b 100644
--- a/lisp/eshell/em-xtra.el
+++ b/lisp/eshell/em-xtra.el
@@ -1,6 +1,6 @@
;;; em-xtra.el --- extra alias functions -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -23,12 +23,13 @@
;;; Code:
+(require 'cl-lib)
(require 'esh-util)
(eval-when-compile
- (require 'eshell)
- (require 'pcomplete))
-(require 'compile)
+ (require 'eshell))
+;; There are no items in this custom group, but eshell modules (ab)use
+;; custom groups.
;;;###autoload
(progn
(defgroup eshell-xtra nil
@@ -45,80 +46,45 @@ naturally accessible within Emacs."
(defun eshell/expr (&rest args)
"Implementation of expr, using the calc package."
- (if (not (fboundp 'calc-eval))
- (throw 'eshell-replace-command
- (eshell-parse-command "*expr" (eshell-flatten-list args)))
- ;; to fool the byte-compiler...
- (let ((func 'calc-eval))
- (funcall func (eshell-flatten-and-stringify args)))))
+ (calc-eval (eshell-flatten-and-stringify args)))
(defun eshell/substitute (&rest args)
- "Easy front-end to `intersection', for comparing lists of strings."
- (apply 'substitute (car args) (cadr args) :test 'equal
+ "Easy front-end to `cl-substitute', for comparing lists of strings."
+ (apply #'cl-substitute (car args) (cadr args) :test #'equal
(cddr args)))
(defun eshell/count (&rest args)
- "Easy front-end to `intersection', for comparing lists of strings."
- (apply 'count (car args) (cadr args) :test 'equal
+ "Easy front-end to `cl-count', for comparing lists of strings."
+ (apply #'cl-count (car args) (cadr args) :test #'equal
(cddr args)))
(defun eshell/mismatch (&rest args)
- "Easy front-end to `intersection', for comparing lists of strings."
- (apply 'mismatch (car args) (cadr args) :test 'equal
+ "Easy front-end to `cl-mismatch', for comparing lists of strings."
+ (apply #'cl-mismatch (car args) (cadr args) :test #'equal
(cddr args)))
(defun eshell/union (&rest args)
- "Easy front-end to `intersection', for comparing lists of strings."
- (apply 'union (car args) (cadr args) :test 'equal
+ "Easy front-end to `cl-union', for comparing lists of strings."
+ (apply #'cl-union (car args) (cadr args) :test #'equal
(cddr args)))
(defun eshell/intersection (&rest args)
- "Easy front-end to `intersection', for comparing lists of strings."
- (apply 'intersection (car args) (cadr args) :test 'equal
+ "Easy front-end to `cl-intersection', for comparing lists of strings."
+ (apply #'cl-intersection (car args) (cadr args) :test #'equal
(cddr args)))
(defun eshell/set-difference (&rest args)
- "Easy front-end to `intersection', for comparing lists of strings."
- (apply 'set-difference (car args) (cadr args) :test 'equal
+ "Easy front-end to `cl-set-difference', for comparing lists of strings."
+ (apply #'cl-set-difference (car args) (cadr args) :test #'equal
(cddr args)))
(defun eshell/set-exclusive-or (&rest args)
- "Easy front-end to `intersection', for comparing lists of strings."
- (apply 'set-exclusive-or (car args) (cadr args) :test 'equal
+ "Easy front-end to `cl-set-exclusive-or', for comparing lists of strings."
+ (apply #'cl-set-exclusive-or (car args) (cadr args) :test #'equal
(cddr args)))
-(defalias 'eshell/ff 'find-name-dired)
-(defalias 'eshell/gf 'find-grep-dired)
-
-(defun pcomplete/bcc32 ()
- "Completion function for Borland's C++ compiler."
- (let ((cur (pcomplete-arg 0)))
- (cond
- ((string-match "\\`-w\\([^;]+;\\)*\\([^;]*\\)\\'" cur)
- (pcomplete-here
- '("ali" "amb" "amp" "asc" "asm" "aus" "bbf" "bei" "big" "ccc"
- "cln" "cod" "com" "cpt" "csu" "def" "dig" "dpu" "dsz" "dup"
- "eas" "eff" "ext" "hch" "hid" "ias" "ibc" "ifr" "ill" "nil"
- "lin" "lvc" "mcs" "mes" "mpc" "mpd" "msg" "nak" "ncf" "nci"
- "ncl" "nfd" "ngu" "nin" "nma" "nmu" "nod" "nop" "npp" "nsf"
- "nst" "ntd" "nto" "nvf" "obi" "obs" "ofp" "osh" "ovf" "par"
- "pch" "pck" "pia" "pin" "pow" "prc" "pre" "pro" "rch" "ret"
- "rng" "rpt" "rvl" "sig" "spa" "stl" "stu" "stv" "sus" "tai"
- "tes" "thr" "ucp" "use" "voi" "zdi") (match-string 2 cur)))
- ((string-match "\\`-[LIn]\\([^;]+;\\)*\\([^;]*\\)\\'" cur)
- (pcomplete-here (pcomplete-dirs) (match-string 2 cur)))
- ((string-match "\\`-[Ee]\\(.*\\)\\'" cur)
- (pcomplete-here (pcomplete-dirs-or-entries "\\.[Ee][Xx][Ee]\\'")
- (match-string 1 cur)))
- ((string-match "\\`-o\\(.*\\)\\'" cur)
- (pcomplete-here (pcomplete-dirs-or-entries "\\.[Oo][Bb][Jj]\\'")
- (match-string 1 cur)))
- (t
- (pcomplete-opt "3456ABCDEHIKLMNOPRSTUVXabcdefgijklnoptuvwxyz"))))
- (while (pcomplete-here
- (pcomplete-dirs-or-entries "\\.[iCc]\\([Pp][Pp]\\)?\\'"))))
-
-(defalias 'pcomplete/bcc 'pcomplete/bcc32)
+(defalias 'eshell/ff #'find-name-dired)
+(defalias 'eshell/gf #'find-grep-dired)
(provide 'em-xtra)
diff --git a/lisp/eshell/esh-arg.el b/lisp/eshell/esh-arg.el
index b317f4e1d2a..576d32b8c5d 100644
--- a/lisp/eshell/esh-arg.el
+++ b/lisp/eshell/esh-arg.el
@@ -1,6 +1,6 @@
;;; esh-arg.el --- argument processing -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -25,9 +25,12 @@
;; hook `eshell-parse-argument-hook'. For a good example of this, see
;; `eshell-parse-drive-letter', defined in eshell-dirs.el.
-(provide 'esh-arg)
+;;; Code:
+
+(require 'esh-util)
-(require 'esh-mode)
+(eval-when-compile
+ (require 'cl-lib))
(defgroup eshell-arg nil
"Argument parsing involves transforming the arguments passed on the
@@ -36,6 +39,48 @@ yield the values intended."
:tag "Argument parsing"
:group 'eshell)
+;;; Internal Variables:
+
+(defvar eshell-current-argument nil)
+(defvar eshell-current-modifiers nil)
+(defvar eshell-arg-listified nil)
+(defvar eshell-nested-argument nil)
+(defvar eshell-current-quoted nil)
+(defvar eshell-inside-quote-regexp nil)
+(defvar eshell-outside-quote-regexp nil)
+
+;;; User Variables:
+
+(defcustom eshell-arg-load-hook nil
+ "A hook that gets run when `eshell-arg' is loaded."
+ :version "24.1" ; removed eshell-arg-initialize
+ :type 'hook
+ :group 'eshell-arg)
+
+(defcustom eshell-delimiter-argument-list '(?\; ?& ?\| ?\> ?\s ?\t ?\n)
+ "List of characters to recognize as argument separators."
+ :type '(repeat character)
+ :group 'eshell-arg)
+
+(defcustom eshell-special-chars-inside-quoting '(?\\ ?\")
+ "Characters which are still special inside double quotes."
+ :type '(repeat character)
+ :group 'eshell-arg)
+
+(defcustom eshell-special-chars-outside-quoting
+ (append eshell-delimiter-argument-list '(?# ?! ?\\ ?\" ?\'))
+ "Characters that require escaping outside of double quotes.
+Without escaping them, they will introduce a change in the argument."
+ :type '(repeat character)
+ :group 'eshell-arg)
+
+(defsubst eshell-arg-delimiter (&optional pos)
+ "Return non-nil if POS is an argument delimiter.
+If POS is nil, the location of point is checked."
+ (let ((pos (or pos (point))))
+ (or (= pos (point-max))
+ (memq (char-after pos) eshell-delimiter-argument-list))))
+
(defcustom eshell-parse-argument-hook
(list
;; a term such as #<buffer NAME>, or #<process NAME> is a buffer
@@ -43,51 +88,48 @@ yield the values intended."
'eshell-parse-special-reference
;; numbers convert to numbers if they stand alone
- (function
- (lambda ()
- (when (and (not eshell-current-argument)
- (not eshell-current-quoted)
- (looking-at eshell-number-regexp)
- (eshell-arg-delimiter (match-end 0)))
- (goto-char (match-end 0))
- (let ((str (match-string 0)))
- (if (> (length str) 0)
- (add-text-properties 0 (length str) '(number t) str))
- str))))
+ (lambda ()
+ (when (and (not eshell-current-argument)
+ (not eshell-current-quoted)
+ (looking-at eshell-number-regexp)
+ (eshell-arg-delimiter (match-end 0)))
+ (goto-char (match-end 0))
+ (let ((str (match-string 0)))
+ (if (> (length str) 0)
+ (add-text-properties 0 (length str) '(number t) str))
+ str)))
;; parse any non-special characters, based on the current context
- (function
- (lambda ()
- (unless eshell-inside-quote-regexp
- (setq eshell-inside-quote-regexp
- (format "[^%s]+"
- (apply 'string eshell-special-chars-inside-quoting))))
- (unless eshell-outside-quote-regexp
- (setq eshell-outside-quote-regexp
- (format "[^%s]+"
- (apply 'string eshell-special-chars-outside-quoting))))
- (when (looking-at (if eshell-current-quoted
- eshell-inside-quote-regexp
- eshell-outside-quote-regexp))
- (goto-char (match-end 0))
- (let ((str (match-string 0)))
- (if str
- (set-text-properties 0 (length str) nil str))
- str))))
+ (lambda ()
+ (unless eshell-inside-quote-regexp
+ (setq eshell-inside-quote-regexp
+ (format "[^%s]+"
+ (apply 'string eshell-special-chars-inside-quoting))))
+ (unless eshell-outside-quote-regexp
+ (setq eshell-outside-quote-regexp
+ (format "[^%s]+"
+ (apply 'string eshell-special-chars-outside-quoting))))
+ (when (looking-at (if eshell-current-quoted
+ eshell-inside-quote-regexp
+ eshell-outside-quote-regexp))
+ (goto-char (match-end 0))
+ (let ((str (match-string 0)))
+ (if str
+ (set-text-properties 0 (length str) nil str))
+ str)))
;; whitespace or a comment is an argument delimiter
- (function
- (lambda ()
- (let (comment-p)
- (when (or (looking-at "[ \t]+")
- (and (not eshell-current-argument)
- (looking-at "#\\([^<'].*\\|$\\)")
- (setq comment-p t)))
- (if comment-p
- (add-text-properties (match-beginning 0) (match-end 0)
- '(comment t)))
- (goto-char (match-end 0))
- (eshell-finish-arg)))))
+ (lambda ()
+ (let (comment-p)
+ (when (or (looking-at "[ \t]+")
+ (and (not eshell-current-argument)
+ (looking-at "#\\([^<'].*\\|$\\)")
+ (setq comment-p t)))
+ (if comment-p
+ (add-text-properties (match-beginning 0) (match-end 0)
+ '(comment t)))
+ (goto-char (match-end 0))
+ (eshell-finish-arg))))
;; parse backslash and the character after
'eshell-parse-backslash
@@ -108,55 +150,31 @@ return the result of the parse as a sexp. It is also responsible for
moving the point forward to reflect the amount of input text that was
parsed.
+If the hook determines that it has reached the end of an argument, it
+should call `eshell-finish-arg' to complete processing of the current
+argument and proceed to the next.
+
If no function handles the current character at point, it will be
treated as a literal character."
:type 'hook
:group 'eshell-arg)
-;;; Code:
+(defvar-keymap eshell-arg-mode-map
+ "C-c M-b" #'eshell-insert-buffer-name)
-;;; User Variables:
-
-(defcustom eshell-arg-load-hook nil
- "A hook that gets run when `eshell-arg' is loaded."
- :version "24.1" ; removed eshell-arg-initialize
- :type 'hook
- :group 'eshell-arg)
-
-(defcustom eshell-delimiter-argument-list '(?\; ?& ?\| ?\> ?\s ?\t ?\n)
- "List of characters to recognize as argument separators."
- :type '(repeat character)
- :group 'eshell-arg)
-
-(defcustom eshell-special-chars-inside-quoting '(?\\ ?\")
- "Characters which are still special inside double quotes."
- :type '(repeat character)
- :group 'eshell-arg)
-
-(defcustom eshell-special-chars-outside-quoting
- (append eshell-delimiter-argument-list '(?# ?! ?\\ ?\" ?\'))
- "Characters that require escaping outside of double quotes.
-Without escaping them, they will introduce a change in the argument."
- :type '(repeat character)
- :group 'eshell-arg)
-
-;;; Internal Variables:
+;;; Functions:
-(defvar eshell-current-argument nil)
-(defvar eshell-current-modifiers nil)
-(defvar eshell-arg-listified nil)
-(defvar eshell-nested-argument nil)
-(defvar eshell-current-quoted nil)
-(defvar eshell-inside-quote-regexp nil)
-(defvar eshell-outside-quote-regexp nil)
+(define-minor-mode eshell-arg-mode
+ "Minor mode for the arg eshell module.
-;;; Functions:
+\\{eshell-arg-mode-map}"
+ :keymap eshell-arg-mode-map)
-(defun eshell-arg-initialize ()
+(defun eshell-arg-initialize () ;Called from `eshell-mode' via intern-soft!
"Initialize the argument parsing code."
- (define-key eshell-command-map [(meta ?b)] 'eshell-insert-buffer-name)
- (set (make-local-variable 'eshell-inside-quote-regexp) nil)
- (set (make-local-variable 'eshell-outside-quote-regexp) nil))
+ (eshell-arg-mode)
+ (setq-local eshell-inside-quote-regexp nil)
+ (setq-local eshell-outside-quote-regexp nil))
(defun eshell-insert-buffer-name (buffer-name)
"Insert BUFFER-NAME into the current buffer at point."
@@ -169,19 +187,63 @@ Without escaping them, they will introduce a change in the argument."
(add-text-properties 0 (length string) '(escaped t) string))
string)
+(defun eshell-concat (quoted &rest rest)
+ "Concatenate all the arguments in REST and return the result.
+If QUOTED is nil, the resulting value(s) may be converted to
+numbers (see `eshell-concat-1').
+
+If each argument in REST is a non-list value, the result will be
+a single value, as if (mapconcat #\\='eshell-stringify REST) had been
+called, possibly converted to a number.
+
+If there is at least one (non-nil) list argument, the result will
+be a list, with \"adjacent\" elements of consecutive arguments
+concatenated as strings (again, possibly converted to numbers).
+For example, concatenating \"a\", (\"b\"), and (\"c\" \"d\")
+would produce (\"abc\" \"d\")."
+ (let (result)
+ (dolist (i rest result)
+ (when i
+ (cond
+ ((null result)
+ (setq result i))
+ ((listp result)
+ (let (curr-head curr-tail)
+ (if (listp i)
+ (setq curr-head (car i)
+ curr-tail (cdr i))
+ (setq curr-head i
+ curr-tail nil))
+ (setq result
+ (append
+ (butlast result 1)
+ (list (eshell-concat-1 quoted (car (last result))
+ curr-head))
+ curr-tail))))
+ ((listp i)
+ (setq result
+ (cons (eshell-concat-1 quoted result (car i))
+ (cdr i))))
+ (t
+ (setq result (eshell-concat-1 quoted result i))))))))
+
+(defun eshell-concat-1 (quoted first second)
+ "Concatenate FIRST and SECOND.
+If QUOTED is nil and either FIRST or SECOND are numbers, try to
+convert the result to a number as well."
+ (let ((result (concat (eshell-stringify first) (eshell-stringify second))))
+ (if (and (not quoted)
+ (or (numberp first) (numberp second)))
+ (eshell-convert-to-number result)
+ result)))
+
(defun eshell-resolve-current-argument ()
"If there are pending modifications to be made, make them now."
(when eshell-current-argument
(when eshell-arg-listified
- (let ((parts eshell-current-argument))
- (while parts
- (unless (stringp (car parts))
- (setcar parts
- (list 'eshell-to-flat-string (car parts))))
- (setq parts (cdr parts)))
- (setq eshell-current-argument
- (list 'eshell-convert
- (append (list 'concat) eshell-current-argument))))
+ (setq eshell-current-argument
+ (append (list 'eshell-concat eshell-current-quoted)
+ eshell-current-argument))
(setq eshell-arg-listified nil))
(while eshell-current-modifiers
(setq eshell-current-argument
@@ -189,19 +251,18 @@ Without escaping them, they will introduce a change in the argument."
eshell-current-modifiers (cdr eshell-current-modifiers))))
(setq eshell-current-modifiers nil))
-(defun eshell-finish-arg (&optional argument)
- "Finish the current argument being processed."
- (if argument
- (setq eshell-current-argument argument))
+(defun eshell-finish-arg (&rest arguments)
+ "Finish the current argument being processed.
+If any ARGUMENTS are specified, they will be added to the final
+argument list in place of the value of the current argument."
+ (when arguments
+ (if (= (length arguments) 1)
+ (setq eshell-current-argument (car arguments))
+ (cl-assert (and (not eshell-arg-listified)
+ (not eshell-current-modifiers)))
+ (setq eshell-current-argument (cons 'eshell-flatten-args arguments))))
(throw 'eshell-arg-done t))
-(defsubst eshell-arg-delimiter (&optional pos)
- "Return non-nil if POS is an argument delimiter.
-If POS is nil, the location of point is checked."
- (let ((pos (or pos (point))))
- (or (= pos (point-max))
- (memq (char-after pos) eshell-delimiter-argument-list))))
-
(defun eshell-quote-argument (string)
"Return STRING with magic characters quoted.
Magic characters are those in `eshell-special-chars-outside-quoting'."
@@ -239,7 +300,11 @@ Point is left at the end of the arguments."
(if (= (point) here)
(error "Failed to parse argument `%s'"
(buffer-substring here (point-max))))
- (and arg (nconc args (list arg)))))))
+ (when arg
+ (nconc args
+ (if (eq (car-safe arg) 'eshell-flatten-args)
+ (cdr arg)
+ (list arg))))))))
(throw 'eshell-incomplete (if (listp delim)
delim
(list delim (point) (cdr args)))))
@@ -293,13 +358,7 @@ Point is left at the end of the arguments."
"Intelligently backslash the character occurring in STRING at INDEX.
If the character is itself a backslash, it needs no escaping."
(let ((char (aref string index)))
- (if (and (eq char ?\\)
- ;; In Emacs directory-sep-char is always ?/, so this does nothing.
- (not (and (featurep 'xemacs)
- (featurep 'mswindows)
- (eq directory-sep-char ?\\)
- (eq (1- (string-width string))
- index))))
+ (if (eq char ?\\)
(char-to-string char)
(if (memq char eshell-special-chars-outside-quoting)
(string ?\\ char)))))
@@ -356,6 +415,30 @@ after are both returned."
(list 'eshell-escape-arg arg))))
(goto-char (1+ end)))))))
+(defun eshell-unescape-inner-double-quote (bound)
+ "Unescape escaped characters inside a double-quoted string.
+The string to parse starts at point and ends at BOUND.
+
+If Eshell is currently parsing a quoted string and there are any
+backslash-escaped characters, this will return the unescaped
+string, updating point to BOUND. Otherwise, this returns nil and
+leaves point where it was."
+ (when eshell-current-quoted
+ (let (strings
+ (start (point))
+ (special-char
+ (rx-to-string
+ `(seq "\\" (group (any ,@eshell-special-chars-inside-quoting))))))
+ (while (re-search-forward special-char bound t)
+ (push (concat (buffer-substring start (match-beginning 0))
+ (match-string 1))
+ strings)
+ (setq start (match-end 0)))
+ (when strings
+ (push (buffer-substring start bound) strings)
+ (goto-char bound)
+ (apply #'concat (nreverse strings))))))
+
(defun eshell-parse-special-reference ()
"Parse a special syntax reference, of the form `#<args>'.
@@ -379,7 +462,9 @@ If the form has no `type', the syntax is parsed as if `type' were
(if (eshell-arg-delimiter (1+ end))
(prog1
(list (if buffer-p 'get-buffer-create 'get-process)
- (buffer-substring-no-properties (point) end))
+ (replace-regexp-in-string
+ (rx "\\" (group (or "\\" "<" ">"))) "\\1"
+ (buffer-substring-no-properties (point) end)))
(goto-char (1+ end)))
(ignore (goto-char here)))))))
@@ -405,4 +490,5 @@ If the form has no `type', the syntax is parsed as if `type' were
(char-to-string (char-after)))))
(goto-char end)))))))
+(provide 'esh-arg)
;;; esh-arg.el ends here
diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el
index 6c26af8999f..413336e3eb5 100644
--- a/lisp/eshell/esh-cmd.el
+++ b/lisp/eshell/esh-cmd.el
@@ -1,6 +1,6 @@
;;; esh-cmd.el --- command invocation -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -80,7 +80,7 @@
;;
;; echo x$(+ 1 2) "String $(+ 1 2)"
;;
-;; To pass a Lisp symbol as a argument, use the alternate quoting
+;; To pass a Lisp symbol as an argument, use the alternate quoting
;; syntax, since the single quote character is far too overused in
;; shell syntax:
;;
@@ -101,45 +101,49 @@
;;; Code:
(require 'esh-util)
-(unless (featurep 'xemacs)
- (require 'eldoc))
+(require 'eldoc)
(require 'esh-arg)
(require 'esh-proc)
+(require 'esh-module)
+(require 'esh-io)
(require 'esh-ext)
+(require 'generator)
(eval-when-compile
(require 'cl-lib)
(require 'pcomplete))
+(declare-function pcomplete--here "pcomplete"
+ (&optional form stub paring form-only))
(defgroup eshell-cmd nil
- "Executing an Eshell command is as simple as typing it in and
-pressing <RET>. There are several different kinds of commands,
-however."
+ "Executing an Eshell command is as simple as typing it in and \
+pressing \\<eshell-mode-map>\\[eshell-send-input].
+There are several different kinds of commands, however."
:tag "Command invocation"
;; :link '(info-link "(eshell)Command invocation")
:group 'eshell)
(defcustom eshell-prefer-lisp-functions nil
"If non-nil, prefer Lisp functions to external commands."
- :type 'boolean
- :group 'eshell-cmd)
+ :type 'boolean)
(defcustom eshell-lisp-regexp "\\([(`]\\|#'\\)"
"A regexp which, if matched at beginning of an argument, means Lisp.
Such arguments will be passed to `read', and then evaluated."
- :type 'regexp
- :group 'eshell-cmd)
+ :type 'regexp)
+
+(defcustom eshell-lisp-form-nil-is-failure t
+ "If non-nil, Lisp forms like (COMMAND ARGS) treat a nil result as failure."
+ :type 'boolean)
(defcustom eshell-pre-command-hook nil
"A hook run before each interactive command is invoked."
- :type 'hook
- :group 'eshell-cmd)
+ :type 'hook)
(defcustom eshell-post-command-hook nil
"A hook run after each interactive command is invoked."
- :type 'hook
- :group 'eshell-cmd)
+ :type 'hook)
(defcustom eshell-prepare-command-hook nil
"A set of functions called to prepare a named command.
@@ -149,8 +153,7 @@ the value of these symbols if necessary.
To prevent a command from executing at all, set
`eshell-last-command-name' to nil."
- :type 'hook
- :group 'eshell-cmd)
+ :type 'hook)
(defcustom eshell-named-command-hook nil
"A set of functions called before a named command is invoked.
@@ -165,7 +168,7 @@ In order to substitute an alternate command form for execution, the
hook function should throw it using the tag `eshell-replace-command'.
For example:
- (add-hook \\='eshell-named-command-hook \\='subst-with-cd)
+ (add-hook \\='eshell-named-command-hook #\\='subst-with-cd)
(defun subst-with-cd (command args)
(throw \\='eshell-replace-command
(eshell-parse-command \"cd\" args)))
@@ -173,8 +176,7 @@ For example:
Although useless, the above code will cause any non-glob, non-Lisp
command (i.e., `ls' as opposed to `*ls' or `(ls)') to be replaced by a
call to `cd' using the arguments that were passed to the function."
- :type 'hook
- :group 'eshell-cmd)
+ :type 'hook)
(defcustom eshell-pre-rewrite-command-hook
'(eshell-no-command-conversion
@@ -182,8 +184,7 @@ call to `cd' using the arguments that were passed to the function."
"A hook run before command rewriting begins.
The terms of the command to be rewritten is passed as arguments, and
may be modified in place. Any return value is ignored."
- :type 'hook
- :group 'eshell-cmd)
+ :type 'hook)
(defcustom eshell-rewrite-command-hook
'(eshell-rewrite-for-command
@@ -202,8 +203,7 @@ so by adding a function to this hook. The first function to return a
substitute command form is the one used. Each function is passed the
command's full argument list, which is a list of sexps (typically
forms or strings)."
- :type 'hook
- :group 'eshell-cmd)
+ :type 'hook)
(defvar eshell-post-rewrite-command-function #'identity
"Function run after command rewriting is finished.
@@ -228,16 +228,14 @@ If an entry is a function, it will be called with the name, and should
return non-nil if the command is complex."
:type '(repeat :tag "Commands"
(choice (string :tag "Name")
- (function :tag "Predicate")))
- :group 'eshell-cmd)
+ (function :tag "Predicate"))))
;;; User Variables:
(defcustom eshell-cmd-load-hook nil
"A hook that gets run when `eshell-cmd' is loaded."
:version "24.1" ; removed eshell-cmd-initialize
- :type 'hook
- :group 'eshell-cmd)
+ :type 'hook)
(defcustom eshell-debug-command nil
"If non-nil, enable Eshell debugging code.
@@ -247,9 +245,8 @@ you must re-load `esh-cmd.el'."
:initialize 'custom-initialize-default
:set (lambda (symbol value)
(set symbol value)
- (load-library "esh-cmd"))
- :type 'boolean
- :group 'eshell-cmd)
+ (load "esh-cmd"))
+ :type 'boolean)
(defcustom eshell-deferrable-commands
'(eshell-named-command
@@ -259,18 +256,16 @@ you must re-load `esh-cmd.el'."
If they return a process object, execution of the calling Eshell
command will wait for completion (in the background) before finishing
the command."
- :type '(repeat function)
- :group 'eshell-cmd)
+ :type '(repeat function))
(defcustom eshell-subcommand-bindings
'((eshell-in-subcommand-p t)
+ (eshell-in-pipeline-p nil)
(default-directory default-directory)
(process-environment (eshell-copy-environment)))
"A list of `let' bindings for subcommand environments."
:type 'sexp
- :group 'eshell-cmd)
-
-(put 'risky-local-variable 'eshell-subcommand-bindings t)
+ :risky t)
(defvar eshell-ensure-newline-p nil
"If non-nil, ensure that a newline is emitted after a Lisp form.
@@ -289,44 +284,63 @@ 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))
-(defun eshell-cmd-initialize ()
+(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."
- (set (make-local-variable 'eshell-current-command) nil)
- (set (make-local-variable 'eshell-command-name) nil)
- (set (make-local-variable 'eshell-command-arguments) nil)
- (set (make-local-variable 'eshell-last-arguments) nil)
- (set (make-local-variable 'eshell-last-command-name) nil)
- (set (make-local-variable 'eshell-last-async-proc) nil)
+ (setq-local eshell-current-command nil)
+ (setq-local eshell-command-name nil)
+ (setq-local eshell-command-arguments nil)
+ (setq-local eshell-last-arguments nil)
+ (setq-local eshell-last-command-name nil)
+ (setq-local eshell-last-async-procs nil)
- (add-hook 'eshell-kill-hook 'eshell-resume-command nil t)
+ (add-hook 'eshell-kill-hook #'eshell-resume-command nil t)
;; make sure that if a command is over, and no process is being
;; waited for, that `eshell-current-command' is set to nil. This
;; situation can occur, for example, if a Lisp function results in
;; `debug' being called, and the user then types \\[top-level]
(add-hook 'eshell-post-command-hook
- (function
- (lambda ()
- (setq eshell-current-command nil
- eshell-last-async-proc nil))) nil t)
+ (lambda ()
+ (setq eshell-current-command nil
+ eshell-last-async-procs nil))
+ nil t)
(add-hook 'eshell-parse-argument-hook
- 'eshell-parse-subcommand-argument nil t)
+ #'eshell-parse-subcommand-argument nil t)
(add-hook 'eshell-parse-argument-hook
- 'eshell-parse-lisp-argument nil t)
+ #'eshell-parse-lisp-argument nil t)
(when (eshell-using-module 'eshell-cmpl)
(add-hook 'pcomplete-try-first-hook
- 'eshell-complete-lisp-symbols nil t)))
+ #'eshell-complete-lisp-symbols nil t)))
(defun eshell-complete-lisp-symbols ()
"If there is a user reference, complete it."
@@ -341,6 +355,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
@@ -352,30 +399,23 @@ 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
- (function
- (lambda (cmd)
- (setq cmd
- (if (or (not (car eshell--sep-terms))
- (string= (car eshell--sep-terms) ";"))
- (eshell-parse-pipeline cmd)
- `(eshell-do-subjob
- (list ,(eshell-parse-pipeline cmd)))))
- (setq eshell--sep-terms (cdr eshell--sep-terms))
- (if eshell-in-pipeline-p
- cmd
- `(eshell-trap-errors ,cmd))))
+ (lambda (cmd)
+ (setq cmd
+ (if (or (not (car eshell--sep-terms))
+ (string= (car eshell--sep-terms) ";"))
+ (eshell-parse-pipeline cmd)
+ `(eshell-do-subjob
+ (list ,(eshell-parse-pipeline cmd)))))
+ (setq eshell--sep-terms (cdr eshell--sep-terms))
+ (if eshell-in-pipeline-p
+ cmd
+ `(eshell-trap-errors ,cmd)))
(eshell-separate-commands terms "[&;]" nil 'eshell--sep-terms))))
(let ((cmd commands))
(while cmd
@@ -505,9 +545,7 @@ implemented via rewriting, rather than as a function."
,(eshell-invokify-arg body t)))
(setcar for-items (cadr for-items))
(setcdr for-items (cddr for-items)))
- (eshell-close-handles
- eshell-last-command-status
- (list 'quote eshell-last-command-result))))))
+ (eshell-close-handles)))))
(defun eshell-structure-basic-command (func names keyword test body
&optional else)
@@ -515,10 +553,11 @@ implemented via rewriting, rather than as a function."
The first of NAMES should be the positive form, and the second the
negative. It's not likely that users should ever need to call this
function."
- ;; If the test form begins with `eshell-convert', it means
- ;; something data-wise will be returned, and we should let
- ;; that determine the truth of the statement.
- (unless (eq (car test) 'eshell-convert)
+ ;; If the test form begins with `eshell-convert' or
+ ;; `eshell-escape-arg', it means something data-wise will be
+ ;; returned, and we should let that determine the truth of the
+ ;; statement.
+ (unless (memq (car test) '(eshell-convert eshell-escape-arg))
(setq test
`(progn ,test
(eshell-exit-success-p))))
@@ -538,9 +577,7 @@ function."
`(let ((eshell-command-body '(nil))
(eshell-test-body '(nil)))
(,func ,test ,body ,else)
- (eshell-close-handles
- eshell-last-command-status
- (list 'quote eshell-last-command-result))))
+ (eshell-close-handles)))
(defun eshell-rewrite-while-command (terms)
"Rewrite a `while' command into its equivalent Eshell command form.
@@ -570,7 +607,7 @@ must be implemented via rewriting, rather than as a function."
t))
(if (= (length terms) 4)
`(eshell-protect
- ,(eshell-invokify-arg (car (last terms)))) t))))
+ ,(eshell-invokify-arg (car (last terms)) t))))))
(defvar eshell-last-command-result) ;Defined in esh-io.el.
@@ -724,6 +761,8 @@ ensconced in a list."
eshell-current-subjob-p)
,object))
+(defvar eshell-this-command-hook nil)
+
(defmacro eshell-trap-errors (object)
"Trap any errors that occur, so they are not entirely fatal.
Also, the variable `eshell-this-command-hook' is available for the
@@ -736,9 +775,9 @@ this grossness will be made to disappear by using `call/cc'..."
(eshell-condition-case err
(prog1
,object
- (run-hooks 'eshell-this-command-hook))
+ (mapc #'funcall eshell-this-command-hook))
(error
- (run-hooks 'eshell-this-command-hook)
+ (mapc #'funcall eshell-this-command-hook)
(eshell-errorn (error-message-string err))
(eshell-close-handles 1)))))
@@ -771,10 +810,7 @@ This macro calls itself recursively, with NOTFIRST non-nil."
`(let ((nextproc
(eshell-do-pipelines (quote ,(cdr pipeline)) t)))
(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))))
@@ -790,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)))
+ (set headproc (or proc (symbol-value headproc)))
+ (set tailproc (or (symbol-value tailproc) proc))
+ proc))))))
(defmacro eshell-do-pipelines-synchronously (pipeline)
"Execute the commands in PIPELINE in sequence synchronously.
@@ -801,8 +840,6 @@ This is used on systems where async subprocesses are not supported."
,(when (cdr pipeline)
`(let ((output-marker ,(point-marker)))
(eshell-set-output-handle ,eshell-output-handle
- 'append output-marker)
- (eshell-set-output-handle ,eshell-error-handle
'append output-marker)))
,(let ((head (car pipeline)))
(if (memq (car head) '(let progn))
@@ -816,13 +853,13 @@ This is used on systems where async subprocesses are not supported."
;; The last process in the pipe should get its handles
;; redirected as we found them before running the pipe.
,(if (null (cdr pipeline))
- `(progn
+ '(progn
(setq eshell-current-handles tail-handles)
(setq eshell-in-pipeline-p nil)))
(let ((result ,(car pipeline)))
;; tailproc gets the result of the last successful process in
;; the pipeline.
- (setq tailproc (or result tailproc))
+ (set tailproc (or result (symbol-value tailproc)))
,(if (cdr pipeline)
`(eshell-do-pipelines-synchronously (quote ,(cdr pipeline))))
result))))
@@ -831,7 +868,11 @@ This is used on systems where async subprocesses are not supported."
(defmacro eshell-execute-pipeline (pipeline)
"Execute the commands in PIPELINE, connecting each to one another."
- `(let ((eshell-in-pipeline-p t) tailproc)
+ `(let ((eshell-in-pipeline-p t)
+ (headproc (make-symbol "headproc"))
+ (tailproc (make-symbol "tailproc")))
+ (set headproc nil)
+ (set tailproc nil)
(progn
,(if (fboundp 'make-process)
`(eshell-do-pipelines ,pipeline)
@@ -841,7 +882,8 @@ This is used on systems where async subprocesses are not supported."
(car (aref eshell-current-handles
,eshell-error-handle)) nil)))
(eshell-do-pipelines-synchronously ,pipeline)))
- (eshell-process-identity tailproc))))
+ (eshell-process-identity (cons (symbol-value headproc)
+ (symbol-value tailproc))))))
(defmacro eshell-as-subcommand (command)
"Execute COMMAND using a temp buffer.
@@ -863,7 +905,8 @@ This avoids the need to use `let*'."
(defmacro eshell-command-to-value (object)
"Run OBJECT synchronously, returning its result as a string.
Returns a string comprising the output from the command."
- `(let ((value (make-symbol "eshell-temp")))
+ `(let ((value (make-symbol "eshell-temp"))
+ (eshell-in-pipeline-p nil))
(eshell-do-command-to-value ,object)))
;;;_* Iterative evaluation
@@ -913,29 +956,71 @@ 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))
- (fboundp (intern-soft (concat "eshell/" name))))))
+ "Determine whether the given COMMAND can be invoked directly.
+COMMAND should be a top-level Eshell command in parsed form, as
+produced by `eshell-parse-command'."
+ (let ((base (cadr (nth 2 (nth 2 (cadr command))))))
+ (eshell--invoke-command-directly base)))
+
+(defun eshell-eval-argument (argument)
+ "Evaluate a single Eshell ARGUMENT and return the result."
+ (let* ((form (eshell-with-temp-command argument
+ (eshell-parse-argument)))
+ (result (eshell-do-eval form t)))
+ (cl-assert (eq (car result) 'quote))
+ (cadr result)))
(defun eshell-eval-command (command &optional input)
"Evaluate the given COMMAND iteratively."
(if eshell-current-command
- ;; we can just stick the new command at the end of the current
- ;; one, and everything will happen as it should
+ ;; We can just stick the new command at the end of the current
+ ;; one, and everything will happen as it should.
(setcdr (last (cdr eshell-current-command))
- (list `(let ((here (and (eobp) (point))))
+ (list `(let ((here (and (eobp) (point))))
,(and input
`(insert-and-inherit ,(concat input "\n")))
(if here
@@ -946,14 +1031,20 @@ at the moment are:
(erase-buffer)
(insert "command: \"" input "\"\n")))
(setq eshell-current-command command)
- (let ((delim (catch 'eshell-incomplete
- (eshell-resume-eval))))
- ;; On systems that don't support async subprocesses, eshell-resume
- ;; can return t. Don't treat that as an error.
- (if (listp delim)
- (setq delim (car delim)))
- (if (and delim (not (eq delim t)))
- (error "Unmatched delimiter: %c" delim)))))
+ (let* ((delim (catch 'eshell-incomplete
+ (eshell-resume-eval)))
+ (val (car-safe delim)))
+ ;; If the return value of `eshell-resume-eval' is wrapped in a
+ ;; list, it indicates that the command was run asynchronously.
+ ;; In that case, unwrap the value before checking the delimiter
+ ;; value.
+ (if (and val
+ (not (eshell-processp val))
+ (not (eq val t)))
+ (error "Unmatched delimiter: %S" val)
+ ;; Eshell-command expect a list like (<process>) to know if the
+ ;; command should be async or not.
+ (or (and (eshell-processp val) delim) val)))))
(defun eshell-resume-command (proc status)
"Resume the current command when a process ends."
@@ -961,24 +1052,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)))))
@@ -1010,7 +1101,7 @@ be finished later after the completion of an asynchronous subprocess."
;; expand any macros directly into the form. This is done so that
;; we can modify any `let' forms to evaluate only once.
(if (macrop (car form))
- (let ((exp (eshell-copy-tree (macroexpand form))))
+ (let ((exp (copy-tree (macroexpand form))))
(eshell-manipulate (format-message "expanding macro `%s'"
(symbol-name (car form)))
(setcar form (car exp))
@@ -1018,7 +1109,7 @@ be finished later after the completion of an asynchronous subprocess."
(let ((args (cdr form)))
(cond
((eq (car form) 'while)
- ;; `eshell-copy-tree' is needed here so that the test argument
+ ;; `copy-tree' is needed here so that the test argument
;; doesn't get modified and thus always yield the same result.
(when (car eshell-command-body)
(cl-assert (not synchronous-p))
@@ -1026,28 +1117,29 @@ be finished later after the completion of an asynchronous subprocess."
(setcar eshell-command-body nil)
(setcar eshell-test-body nil))
(unless (car eshell-test-body)
- (setcar eshell-test-body (eshell-copy-tree (car args))))
- (while (cadr (eshell-do-eval (car eshell-test-body)))
+ (setcar eshell-test-body (copy-tree (car args))))
+ (while (cadr (eshell-do-eval (car eshell-test-body) synchronous-p))
(setcar eshell-command-body
(if (cddr args)
- `(progn ,@(eshell-copy-tree (cdr args)))
- (eshell-copy-tree (cadr args))))
+ `(progn ,@(copy-tree (cdr args)))
+ (copy-tree (cadr args))))
(eshell-do-eval (car eshell-command-body) synchronous-p)
(setcar eshell-command-body nil)
- (setcar eshell-test-body (eshell-copy-tree (car args))))
+ (setcar eshell-test-body (copy-tree (car args))))
(setcar eshell-command-body nil))
((eq (car form) 'if)
- ;; `eshell-copy-tree' is needed here so that the test argument
+ ;; `copy-tree' is needed here so that the test argument
;; doesn't get modified and thus always yield the same result.
(if (car eshell-command-body)
(progn
(cl-assert (not synchronous-p))
(eshell-do-eval (car eshell-command-body)))
(unless (car eshell-test-body)
- (setcar eshell-test-body (eshell-copy-tree (car args))))
+ (setcar eshell-test-body (copy-tree (car args))))
(setcar eshell-command-body
- (eshell-copy-tree
- (if (cadr (eshell-do-eval (car eshell-test-body)))
+ (copy-tree
+ (if (cadr (eshell-do-eval (car eshell-test-body)
+ synchronous-p))
(cadr args)
(car (cddr args)))))
(eshell-do-eval (car eshell-command-body) synchronous-p))
@@ -1059,16 +1151,8 @@ be finished later after the completion of an asynchronous subprocess."
((eq (car form) 'setcdr)
(setcar (cdr args) (eshell-do-eval (cadr args) synchronous-p))
(eval form))
- ((memq (car form) '(let catch condition-case unwind-protect))
- ;; `let', `condition-case' and `unwind-protect' have to be
- ;; handled specially, because we only want to call
- ;; `eshell-do-eval' on their first form.
- ;;
- ;; NOTE: This requires obedience by all forms which this
- ;; function might encounter, that they do not contain
- ;; other special forms.
- (if (and (eq (car form) 'let)
- (not (eq (car (cadr args)) 'eshell-do-eval)))
+ ((eq (car form) 'let)
+ (if (not (eq (car (cadr args)) 'eshell-do-eval))
(eshell-manipulate "evaluating let args"
(dolist (letarg (car args))
(if (and (listp letarg)
@@ -1076,6 +1160,21 @@ be finished later after the completion of an asynchronous subprocess."
(setcdr letarg
(list (eshell-do-eval
(cadr letarg) synchronous-p)))))))
+ (cl-progv
+ (mapcar (lambda (binding) (if (consp binding) (car binding) binding))
+ (car args))
+ ;; These expressions should all be constants now.
+ (mapcar (lambda (binding) (if (consp binding) (eval (cadr binding))))
+ (car args))
+ (eshell-do-eval (macroexp-progn (cdr args)) synchronous-p)))
+ ((memq (car form) '(catch condition-case unwind-protect))
+ ;; `condition-case' and `unwind-protect' have to be
+ ;; handled specially, because we only want to call
+ ;; `eshell-do-eval' on their first form.
+ ;;
+ ;; NOTE: This requires obedience by all forms which this
+ ;; function might encounter, that they do not contain
+ ;; other special forms.
(unless (eq (car form) 'unwind-protect)
(setq args (cdr args)))
(unless (eq (caar args) 'eshell-do-eval)
@@ -1134,17 +1233,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
@@ -1158,10 +1256,9 @@ be finished later after the completion of an asynchronous subprocess."
(setq name (substring name 1)
direct t))
(if (and (not direct)
- (eshell-using-module 'eshell-alias)
+ (fboundp 'eshell-lookup-alias)
(setq alias
- (funcall (symbol-function 'eshell-lookup-alias)
- name)))
+ (eshell-lookup-alias name)))
(setq program
(concat name " is an alias, defined as \""
(cadr alias) "\"")))
@@ -1233,10 +1330,11 @@ or an external command."
(eshell-external-command command args))))
(defun eshell-exec-lisp (printer errprint func-or-form args form-p)
- "Execute a lisp FUNC-OR-FORM, maybe passing ARGS.
+ "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
@@ -1245,6 +1343,15 @@ represent a lisp form; ARGS will be ignored in that case."
(apply func-or-form args)))))
(and result (funcall printer result))
result)
+ (eshell-pipe-broken
+ ;; If FUNC-OR-FORM tried and failed to write some output to a
+ ;; process, it will raise an `eshell-pipe-broken' signal (this is
+ ;; analogous to SIGPIPE on POSIX systems). In this case, set the
+ ;; command status to some non-zero value to indicate an error; to
+ ;; match GNU/Linux, we use 141, which the numeric value of
+ ;; SIGPIPE on GNU/Linux (13) with the high bit (2^7) set.
+ (setq eshell-last-command-status 141)
+ nil)
(error
(setq eshell-last-command-status 1)
(let ((msg (error-message-string err)))
@@ -1259,80 +1366,111 @@ 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.
(defun eshell-lisp-command (object &optional args)
"Insert Lisp OBJECT, using ARGS if a function."
(catch 'eshell-external ; deferred to an external command
+ (setq eshell-last-command-status 0
+ eshell-last-arguments args)
(let* ((eshell-ensure-newline-p (eshell-interactive-output-p))
- (result
- (if (functionp object)
- (progn
- (setq eshell-last-arguments args
- eshell-last-command-name
- (concat "#<function " (symbol-name object) ">"))
- ;; if any of the arguments are flagged as numbers
- ;; waiting for conversion, convert them now
- (unless (get object 'eshell-no-numeric-conversions)
- (while args
- (let ((arg (car args)))
- (if (and (stringp arg)
- (> (length arg) 0)
- (not (text-property-not-all
- 0 (length arg) 'number t arg)))
- (setcar args (string-to-number arg))))
- (setq args (cdr args))))
- (eshell-apply object eshell-last-arguments))
- (setq eshell-last-arguments args
- eshell-last-command-name "#<Lisp object>")
- (eshell-eval object))))
+ (command-form-p (functionp object))
+ (result
+ (if command-form-p
+ (let ((numeric (not (get object
+ 'eshell-no-numeric-conversions)))
+ (fname-args (get object 'eshell-filename-arguments)))
+ (when (or numeric fname-args)
+ (while args
+ (let ((arg (car args)))
+ (cond
+ ((and numeric (stringp arg) (> (length arg) 0)
+ (text-property-any 0 (length arg)
+ 'number t arg))
+ ;; If any of the arguments are flagged as
+ ;; numbers waiting for conversion, convert
+ ;; them now.
+ (setcar args (string-to-number arg)))
+ ((and fname-args (stringp arg)
+ (string-equal arg "~"))
+ ;; If any of the arguments match "~",
+ ;; prepend "./" to treat it as a regular
+ ;; file name.
+ (setcar args (concat "./" arg)))))
+ (setq args (cdr args))))
+ (setq eshell-last-command-name
+ (concat "#<function " (symbol-name object) ">"))
+ (eshell-apply object eshell-last-arguments))
+ (setq eshell-last-command-name "#<Lisp object>")
+ (eshell-eval object))))
(if (and eshell-ensure-newline-p
(save-excursion
(goto-char eshell-last-output-end)
(not (bolp))))
(eshell-print "\n"))
- (eshell-close-handles 0 (list 'quote result)))))
-
-(defalias 'eshell-lisp-command* 'eshell-lisp-command)
+ (eshell-close-handles
+ ;; If `eshell-lisp-form-nil-is-failure' is non-nil, Lisp forms
+ ;; that succeeded but have a nil result should have an exit
+ ;; status of 2.
+ (when (and eshell-lisp-form-nil-is-failure
+ (not command-form-p)
+ (= eshell-last-command-status 0)
+ (not result))
+ 2)
+ (list 'quote result)))))
+
+(defalias 'eshell-lisp-command* #'eshell-lisp-command)
(provide 'esh-cmd)
diff --git a/lisp/eshell/esh-ext.el b/lisp/eshell/esh-ext.el
index 14ae6b4ae1d..98902fc6f23 100644
--- a/lisp/eshell/esh-ext.el
+++ b/lisp/eshell/esh-ext.el
@@ -1,6 +1,6 @@
;;; esh-ext.el --- commands external to Eshell -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -31,17 +31,12 @@
;;; Code:
-(provide 'esh-ext)
-
(require 'esh-util)
-(eval-when-compile
- (require 'cl-lib)
- (require 'esh-io)
- (require 'esh-cmd))
+(eval-when-compile (require 'cl-lib))
+(require 'esh-io)
(require 'esh-arg)
(require 'esh-opt)
-(require 'esh-proc)
(defgroup eshell-ext nil
"External commands are invoked when operating system executables are
@@ -79,10 +74,8 @@ but Eshell will be able to understand
"Search the environment path for NAME."
(if (file-name-absolute-p name)
name
- (let ((list (eshell-parse-colon-path eshell-path-env))
+ (let ((list (eshell-get-path))
suffixes n1 n2 file)
- (if (eshell-under-windows-p)
- (push "." list))
(while list
(setq n1 (concat (car list) name))
(setq suffixes eshell-binary-suffixes)
@@ -117,7 +110,7 @@ wholly ignored."
(autoload 'eshell-parse-command "esh-cmd")
(defsubst eshell-invoke-batch-file (&rest args)
- "Invoke a .BAT or .CMD file on DOS/Windows systems."
+ "Invoke a .BAT or .CMD file on MS-DOS/MS-Windows systems."
;; since CMD.EXE can't handle forward slashes in the initial
;; argument...
(setcar args (subst-char-in-string ?/ ?\\ (car args)))
@@ -170,16 +163,16 @@ by the user on the command line."
(defcustom eshell-explicit-command-char ?*
"If this char occurs before a command name, call it externally.
-That is, although `vi' may be an alias, `\vi' will always call the
+That is, although `vi' may be an alias, `*vi' will always call the
external version."
:type 'character
:group 'eshell-ext)
;;; Functions:
-(defun eshell-ext-initialize ()
+(defun eshell-ext-initialize () ;Called from `eshell-mode' via intern-soft!
"Initialize the external command handling code."
- (add-hook 'eshell-named-command-hook 'eshell-explicit-command nil t))
+ (add-hook 'eshell-named-command-hook #'eshell-explicit-command nil t))
(defun eshell-explicit-command (command args)
"If a command name begins with `*', call it externally always.
@@ -193,8 +186,6 @@ This bypasses all Lisp functions and aliases."
(error "%s: external command not found"
(substring command 1))))))
-(autoload 'eshell-close-handles "esh-io")
-
(defun eshell-remote-command (command args)
"Insert output from a remote COMMAND, using ARGS.
A remote command is something that executes on a different machine.
@@ -211,7 +202,7 @@ causing the user to wonder if anything's really going on..."
(progn
(setq exitcode
(shell-command
- (mapconcat 'shell-quote-argument
+ (mapconcat #'shell-quote-argument
(append (list command) args) " ")
outbuf errbuf))
(eshell-print (with-current-buffer outbuf (buffer-string)))
@@ -222,7 +213,7 @@ causing the user to wonder if anything's really going on..."
(defun eshell-external-command (command args)
"Insert output from an external COMMAND, using ARGS."
- (setq args (eshell-stringify-list (eshell-flatten-list args)))
+ (setq args (eshell-stringify-list (flatten-tree args)))
(let ((interp (eshell-find-interpreter
command
args
@@ -235,6 +226,8 @@ causing the user to wonder if anything's really going on..."
(cl-assert interp)
(if (functionp (car interp))
(apply (car interp) (append (cdr interp) args))
+ (require 'esh-proc)
+ (declare-function eshell-gather-process-output "esh-proc" (command args))
(eshell-gather-process-output
(car interp) (append (cdr interp) args)))))
@@ -249,7 +242,7 @@ Adds the given PATH to $PATH.")
(if args
(progn
(setq eshell-path-env (getenv "PATH")
- args (mapconcat 'identity args path-separator)
+ args (mapconcat #'identity args path-separator)
eshell-path-env
(if prepend
(concat args path-separator eshell-path-env)
@@ -259,6 +252,7 @@ Adds the given PATH to $PATH.")
(eshell-printn dir)))))
(put 'eshell/addpath 'eshell-no-numeric-conversions t)
+(put 'eshell/addpath 'eshell-filename-arguments t)
(defun eshell-script-interpreter (file)
"Extract the script to run from FILE, if it has #!<interp> in it.
@@ -335,4 +329,5 @@ line of the form #!<interp>."
(cdr interp)))))
(or interp (list fullname)))))))
+(provide 'esh-ext)
;;; esh-ext.el ends here
diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el
index ca791982f56..4620565f857 100644
--- a/lisp/eshell/esh-io.el
+++ b/lisp/eshell/esh-io.el
@@ -1,6 +1,6 @@
;;; esh-io.el --- I/O management -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -34,7 +34,7 @@
;;;_* Redirect to a Buffer or Process
;;
;; Buffers and processes can be named with '#<buffer buffer-name>' and
-;; '#<process process-name>', respectively. As a shorthand,
+;; '#<process process-name>', respectively. As a shorthand,
;; '#<buffer-name>' without the explicit "buffer" arg is equivalent to
;; '#<buffer buffer-name>'.
;;
@@ -68,8 +68,6 @@
;;; Code:
-(provide 'esh-io)
-
(require 'esh-arg)
(require 'esh-util)
@@ -96,7 +94,7 @@ though they were files."
Currently this is standard input, output and error. But even all of
these Emacs does not currently support with asynchronous processes
\(which is what eshell uses so that you can continue doing work in
-other buffers) ."
+other buffers)."
:type 'integer
:group 'eshell-io)
@@ -149,12 +147,21 @@ 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:
+(defconst eshell-redirection-operators-alist
+ '(("<" . input) ; FIXME: Not supported yet.
+ (">" . overwrite)
+ (">>" . append)
+ (">>>" . insert))
+ "An association list of redirection operators to symbols
+describing the mode, e.g. for using with `eshell-get-target'.")
+
(defvar eshell-current-handles nil)
(defvar eshell-last-command-status 0
@@ -171,56 +178,107 @@ not be added to this variable."
;;; Functions:
-(defun eshell-io-initialize ()
+(defun eshell-io-initialize () ;Called from `eshell-mode' via intern-soft!
"Initialize the I/O subsystem code."
(add-hook 'eshell-parse-argument-hook
- 'eshell-parse-redirection nil t)
+ #'eshell-parse-redirection nil t)
(make-local-variable 'eshell-current-redirections)
(add-hook 'eshell-pre-rewrite-command-hook
- 'eshell-strip-redirections nil t)
+ #'eshell-strip-redirections nil t)
(add-function :filter-return (local 'eshell-post-rewrite-command-function)
#'eshell--apply-redirections))
(defun eshell-parse-redirection ()
- "Parse an output redirection, such as `2>'."
- (if (and (not eshell-current-quoted)
- (looking-at "\\([0-9]\\)?\\(<\\|>+\\)&?\\([0-9]\\)?\\s-*"))
+ "Parse an output redirection, such as `2>' or `>&'."
+ (when (not eshell-current-quoted)
+ (cond
+ ;; Copying a handle (e.g. `2>&1').
+ ((looking-at (rx (? (group digit))
+ (group (or "<" ">"))
+ "&" (group digit)
+ (* (syntax whitespace))))
+ (let ((source (string-to-number (or (match-string 1) "1")))
+ (mode (cdr (assoc (match-string 2)
+ eshell-redirection-operators-alist)))
+ (target (string-to-number (match-string 3))))
+ (when (eq mode 'input)
+ (error "Eshell does not support input redirection"))
+ (goto-char (match-end 0))
+ (eshell-finish-arg (list 'eshell-copy-output-handle
+ source target))))
+ ;; Shorthand for redirecting both stdout and stderr (e.g. `&>').
+ ((looking-at (rx (or (seq (group (** 1 3 ">")) "&")
+ (seq "&" (group-n 1 (** 1 3 ">"))))
+ (* (syntax whitespace))))
(if eshell-current-argument
- (eshell-finish-arg)
- (let ((sh (match-string 1))
- (oper (match-string 2))
-; (th (match-string 3))
- )
- (if (string= oper "<")
- (error "Eshell does not support input redirection"))
- (eshell-finish-arg
- (prog1
- (list 'eshell-set-output-handle
- (or (and sh (string-to-number sh)) 1)
- (list 'quote
- (aref [overwrite append insert]
- (1- (length oper)))))
- (goto-char (match-end 0))))))))
+ (eshell-finish-arg)
+ (goto-char (match-end 0))
+ (eshell-finish-arg
+ (let ((mode (cdr (assoc (match-string 1)
+ eshell-redirection-operators-alist))))
+ (list 'eshell-set-all-output-handles
+ (list 'quote mode))))))
+ ;; Shorthand for piping both stdout and stderr (i.e. `|&').
+ ((looking-at (rx "|&" (* (syntax whitespace))))
+ (if eshell-current-argument
+ (eshell-finish-arg)
+ (goto-char (match-end 0))
+ (eshell-finish-arg
+ '(eshell-copy-output-handle eshell-error-handle
+ eshell-output-handle)
+ '(eshell-operator "|"))))
+ ;; Regular redirecting (e.g. `2>').
+ ((looking-at (rx (? (group digit))
+ (group (or "<" (** 1 3 ">")))
+ (* (syntax whitespace))))
+ (if eshell-current-argument
+ (eshell-finish-arg)
+ (let ((source (if (match-string 1)
+ (string-to-number (match-string 1))
+ eshell-output-handle))
+ (mode (cdr (assoc (match-string 2)
+ eshell-redirection-operators-alist))))
+ (when (eq mode 'input)
+ (error "Eshell does not support input redirection"))
+ (goto-char (match-end 0))
+ (eshell-finish-arg
+ ;; Note: the target will be set later by
+ ;; `eshell-strip-redirections'.
+ (list 'eshell-set-output-handle
+ source (list 'quote mode)))))))))
(defun eshell-strip-redirections (terms)
"Rewrite any output redirections in TERMS."
(setq eshell-current-redirections (list t))
(let ((tl terms)
- (tt (cdr terms)))
+ (tt (cdr terms)))
(while tt
- (if (not (and (consp (car tt))
- (eq (caar tt) 'eshell-set-output-handle)))
- (setq tt (cdr tt)
- tl (cdr tl))
- (unless (cdr tt)
- (error "Missing redirection target"))
- (nconc eshell-current-redirections
- (list (list 'ignore
- (append (car tt) (list (cadr tt))))))
- (setcdr tl (cddr tt))
- (setq tt (cddr tt))))
+ (cond
+ ;; Strip `eshell-copy-output-handle'.
+ ((and (consp (car tt))
+ (eq (caar tt) 'eshell-copy-output-handle))
+ (nconc eshell-current-redirections
+ (list (car tt)))
+ (setcdr tl (cddr tt))
+ (setq tt (cdr tt)))
+ ;; Strip `eshell-set-output-handle' or
+ ;; `eshell-set-all-output-handles' and the term immediately
+ ;; after (the redirection target).
+ ((and (consp (car tt))
+ (memq (caar tt) '(eshell-set-output-handle
+ eshell-set-all-output-handles)))
+ (unless (cdr tt)
+ (error "Missing redirection target"))
+ (nconc eshell-current-redirections
+ (list (list 'ignore
+ (append (car tt) (list (cadr tt))))))
+ (setcdr tl (cddr tt))
+ (setq tt (cddr tt)))
+ (t
+ (setq tt (cdr tt)
+ tl (cdr tl)))))
(setq eshell-current-redirections
- (cdr eshell-current-redirections))))
+ (cdr eshell-current-redirections))))
(defun eshell--apply-redirections (cmd)
"Apply any redirection which were specified for COMMAND."
@@ -237,24 +295,81 @@ The default location for standard output and standard error will go to
STDOUT and STDERR, respectively.
OUTPUT-MODE and ERROR-MODE are either `overwrite', `append' or `insert';
a nil value of mode defaults to `insert'."
- (let ((handles (make-vector eshell-number-of-handles nil))
- (output-target (eshell-get-target stdout output-mode))
- (error-target (eshell-get-target stderr error-mode)))
+ (let* ((handles (make-vector eshell-number-of-handles nil))
+ (output-target (eshell-get-target stdout output-mode))
+ (error-target (if stderr
+ (eshell-get-target stderr error-mode)
+ output-target)))
(aset handles eshell-output-handle (cons output-target 1))
- (aset handles eshell-error-handle
- (cons (if stderr error-target output-target) 1))
+ (aset handles eshell-error-handle (cons error-target 1))
handles))
(defun eshell-protect-handles (handles)
"Protect the handles in HANDLES from a being closed."
- (let ((idx 0))
- (while (< idx eshell-number-of-handles)
- (if (aref handles idx)
- (setcdr (aref handles idx)
- (1+ (cdr (aref handles idx)))))
- (setq idx (1+ idx))))
+ (dotimes (idx eshell-number-of-handles)
+ (when (aref handles idx)
+ (setcdr (aref handles idx)
+ (1+ (cdr (aref handles idx))))))
handles)
+(defun eshell-close-handles (&optional exit-code result handles)
+ "Close all of the current HANDLES, taking refcounts into account.
+If HANDLES is nil, use `eshell-current-handles'.
+
+EXIT-CODE is the process exit code (zero, if the command
+completed successfully). If nil, then use the exit code already
+set in `eshell-last-command-status'.
+
+RESULT is the quoted value of the last command. If nil, then use
+the value already set in `eshell-last-command-result'."
+ (when exit-code
+ (setq eshell-last-command-status exit-code))
+ (when result
+ (cl-assert (eq (car result) 'quote))
+ (setq eshell-last-command-result (cadr result)))
+ (let ((handles (or handles eshell-current-handles)))
+ (dotimes (idx eshell-number-of-handles)
+ (when-let ((handle (aref handles idx)))
+ (setcdr handle (1- (cdr handle)))
+ (when (= (cdr handle) 0)
+ (dolist (target (ensure-list (car (aref handles idx))))
+ (eshell-close-target target (= eshell-last-command-status 0)))
+ (setcar handle nil))))))
+
+(defun eshell-set-output-handle (index mode &optional target handles)
+ "Set handle INDEX for the current HANDLES to point to TARGET using MODE.
+If HANDLES is nil, use `eshell-current-handles'."
+ (when target
+ (let ((handles (or handles eshell-current-handles)))
+ (if (and (stringp target)
+ (string= target (null-device)))
+ (aset handles index nil)
+ (let ((where (eshell-get-target target mode))
+ (current (car (aref handles index))))
+ (if (listp current)
+ (unless (member where current)
+ (setq current (append current (list where))))
+ (setq current (list where)))
+ (if (not (aref handles index))
+ (aset handles index (cons nil 1)))
+ (setcar (aref handles index) current))))))
+
+(defun eshell-copy-output-handle (index index-to-copy &optional handles)
+ "Copy the handle INDEX-TO-COPY to INDEX for the current HANDLES.
+If HANDLES is nil, use `eshell-current-handles'."
+ (let* ((handles (or handles eshell-current-handles))
+ (handle-to-copy (car (aref handles index-to-copy))))
+ (setcar (aref handles index)
+ (if (listp handle-to-copy)
+ (copy-sequence handle-to-copy)
+ handle-to-copy))))
+
+(defun eshell-set-all-output-handles (mode &optional target handles)
+ "Set output and error HANDLES to point to TARGET using MODE.
+If HANDLES is nil, use `eshell-current-handles'."
+ (eshell-set-output-handle eshell-output-handle mode target handles)
+ (eshell-copy-output-handle eshell-error-handle eshell-output-handle handles))
+
(defun eshell-close-target (target status)
"Close an output TARGET, passing STATUS as the result.
STATUS should be non-nil on successful termination of the output."
@@ -277,8 +392,23 @@ STATUS should be non-nil on successful termination of the output."
;; If we're redirecting to a process (via a pipe, or process
;; redirection), send it EOF so that it knows we're finished.
((eshell-processp target)
- (if (eq (process-status target) 'run)
- (process-send-eof target)))
+ ;; According to POSIX.1-2017, section 11.1.9, when communicating
+ ;; via terminal, sending EOF causes all bytes waiting to be read
+ ;; to be sent to the process immediately. Thus, if there are any
+ ;; bytes waiting, we need to send EOF twice: once to flush the
+ ;; buffer, and a second time to cause the next read() to return a
+ ;; size of 0, indicating end-of-file to the reading process.
+ ;; However, some platforms (e.g. Solaris) actually require sending
+ ;; a *third* EOF. Since sending extra EOFs while the process is
+ ;; running are a no-op, we'll just send the maximum we'd ever
+ ;; need. See bug#56025 for further details.
+ (let ((i 0)
+ ;; Only call `process-send-eof' once if communicating via a
+ ;; pipe (in truth, this just closes the pipe).
+ (max-attempts (if (process-tty-name target 'stdin) 3 1)))
+ (while (and (<= (cl-incf i) max-attempts)
+ (eq (process-status target) 'run))
+ (process-send-eof target))))
;; A plain function redirection needs no additional arguments
;; passed.
@@ -291,32 +421,6 @@ STATUS should be non-nil on successful termination of the output."
((consp target)
(apply (car target) status (cdr target)))))
-(defun eshell-close-handles (exit-code &optional result handles)
- "Close all of the current handles, taking refcounts into account.
-EXIT-CODE is the process exit code; mainly, it is zero, if the command
-completed successfully. RESULT is the quoted value of the last
-command. If nil, then the meta variables for keeping track of the
-last execution result should not be changed."
- (let ((idx 0))
- (cl-assert (or (not result) (eq (car result) 'quote)))
- (setq eshell-last-command-status exit-code
- eshell-last-command-result (cadr result))
- (while (< idx eshell-number-of-handles)
- (let ((handles (or handles eshell-current-handles)))
- (when (aref handles idx)
- (setcdr (aref handles idx)
- (1- (cdr (aref handles idx))))
- (when (= (cdr (aref handles idx)) 0)
- (let ((target (car (aref handles idx))))
- (if (not (listp target))
- (eshell-close-target target (= exit-code 0))
- (while target
- (eshell-close-target (car target) (= exit-code 0))
- (setq target (cdr target)))))
- (setcar (aref handles idx) nil))))
- (setq idx (1+ idx)))
- nil))
-
(defun eshell-kill-append (string)
"Call `kill-append' with STRING, if it is indeed a string."
(if (stringp string)
@@ -346,8 +450,8 @@ it defaults to `insert'."
(if buffer-file-read-only
(error "Cannot write to read-only file `%s'" target))
(setq buffer-read-only nil)
- (set (make-local-variable 'eshell-output-file-buffer)
- (if (eq exists buf) 0 t))
+ (setq-local eshell-output-file-buffer
+ (if (eq exists buf) 0 t))
(cond ((eq mode 'overwrite)
(erase-buffer))
((eq mode 'append)
@@ -378,36 +482,20 @@ 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
- (if (and (stringp target)
- (or (cond
- ((boundp 'null-device)
- (string= target null-device))
- ((boundp 'grep-null-device)
- (string= target grep-null-device))
- (t nil))
- (string= target "/dev/null")))
- (aset eshell-current-handles index nil)
- (let ((where (eshell-get-target target mode))
- (current (car (aref eshell-current-handles index))))
- (if (and (listp current)
- (not (member where current)))
- (setq current (append current (list where)))
- (setq current (list where)))
- (if (not (aref eshell-current-handles index))
- (aset eshell-current-handles index (cons nil 1)))
- (setcar (aref eshell-current-handles index) current)))))
-
-(defun eshell-interactive-output-p ()
- "Return non-nil if current handles are bound for interactive display."
- (and (eq (car (aref eshell-current-handles
- eshell-output-handle)) t)
- (eq (car (aref eshell-current-handles
- eshell-error-handle)) t)))
+(defun eshell-interactive-output-p (&optional index handles)
+ "Return non-nil if the specified handle is bound for interactive display.
+HANDLES is the set of handles to check; if nil, use
+`eshell-current-handles'.
+
+INDEX is the handle index to check. If nil, check
+`eshell-output-handle'. If `all', check both
+`eshell-output-handle' and `eshell-error-handle'."
+ (let ((handles (or handles eshell-current-handles))
+ (index (or index eshell-output-handle)))
+ (if (eq index 'all)
+ (and (eq (car (aref handles eshell-output-handle)) t)
+ (eq (car (aref handles eshell-error-handle)) t))
+ (eq (car (aref handles index)) t))))
(defvar eshell-print-queue nil)
(defvar eshell-print-queue-count -1)
@@ -492,23 +580,37 @@ 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 err
+ (process-send-string target object)
+ (error
+ ;; If `process-send-string' raises an error and the process has
+ ;; finished, treat it as a broken pipe. Otherwise, just
+ ;; re-throw the signal.
+ (if (memq (process-status target)
+ '(run stop open closed))
+ (signal (car err) (cdr err))
+ (signal 'eshell-pipe-broken (list 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 ea2fe1a6c26..8f11e6f04a4 100644
--- a/lisp/eshell/esh-mode.el
+++ b/lisp/eshell/esh-mode.el
@@ -1,6 +1,6 @@
;;; esh-mode.el --- user interface -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -58,13 +58,10 @@
;;; Code:
-(provide 'esh-mode)
-
(require 'esh-util)
(require 'esh-module)
(require 'esh-cmd)
-(require 'esh-io)
-(require 'esh-var)
+(require 'esh-arg) ;For eshell-parse-arguments
(defgroup eshell-mode nil
"This module contains code for handling input from the user."
@@ -75,51 +72,43 @@
(defcustom eshell-mode-unload-hook nil
"A hook that gets run when `eshell-mode' is unloaded."
- :type 'hook
- :group 'eshell-mode)
+ :type 'hook)
(defcustom eshell-mode-hook nil
"A hook that gets run when `eshell-mode' is entered."
- :type 'hook
- :group 'eshell-mode)
+ :type 'hook)
(defcustom eshell-first-time-mode-hook nil
"A hook that gets run the first time `eshell-mode' is entered.
That is to say, the first time during an Emacs session."
- :type 'hook
- :group 'eshell-mode)
+ :type 'hook)
(defcustom eshell-exit-hook nil
"A hook that is run whenever `eshell' is exited.
This hook is only run if exiting actually kills the buffer."
:version "24.1" ; removed eshell-query-kill-processes
- :type 'hook
- :group 'eshell-mode)
+ :type 'hook)
(defcustom eshell-kill-on-exit t
"If non-nil, kill the Eshell buffer on the `exit' command.
Otherwise, the buffer will simply be buried."
- :type 'boolean
- :group 'eshell-mode)
+ :type 'boolean)
(defcustom eshell-input-filter-functions nil
"Functions to call before input is processed.
The input is contained in the region from `eshell-last-input-start' to
`eshell-last-input-end'."
- :type 'hook
- :group 'eshell-mode)
+ :type 'hook)
(defcustom eshell-send-direct-to-subprocesses nil
"If t, send any input immediately to a subprocess."
- :type 'boolean
- :group 'eshell-mode)
+ :type 'boolean)
(defcustom eshell-expand-input-functions nil
"Functions to call before input is parsed.
Each function is passed two arguments, which bounds the region of the
current input text."
- :type 'hook
- :group 'eshell-mode)
+ :type 'hook)
(defcustom eshell-scroll-to-bottom-on-input nil
"Controls whether input to interpreter causes window to scroll.
@@ -129,8 +118,7 @@ buffer. If `this', scroll only the selected window.
See `eshell-preinput-scroll-to-bottom'."
:type '(radio (const :tag "Do not scroll Eshell windows" nil)
(const :tag "Scroll all windows showing the buffer" all)
- (const :tag "Scroll only the selected window" this))
- :group 'eshell-mode)
+ (const :tag "Scroll only the selected window" this)))
(defcustom eshell-scroll-to-bottom-on-output nil
"Controls whether interpreter output causes window to scroll.
@@ -143,8 +131,7 @@ See variable `eshell-scroll-show-maximum-output' and function
:type '(radio (const :tag "Do not scroll Eshell windows" nil)
(const :tag "Scroll all windows showing the buffer" all)
(const :tag "Scroll only the selected window" this)
- (const :tag "Scroll all windows other than selected" others))
- :group 'eshell-mode)
+ (const :tag "Scroll all windows other than selected" others)))
(defcustom eshell-scroll-show-maximum-output t
"Controls how interpreter output causes window to scroll.
@@ -152,16 +139,14 @@ If non-nil, then show the maximum output when the window is scrolled.
See variable `eshell-scroll-to-bottom-on-output' and function
`eshell-postoutput-scroll-to-bottom'."
- :type 'boolean
- :group 'eshell-mode)
+ :type 'boolean)
(defcustom eshell-buffer-maximum-lines 1024
"The maximum size in lines for eshell buffers.
Eshell buffers are truncated from the top to be no greater than this
number, if the function `eshell-truncate-buffer' is on
`eshell-output-filter-functions'."
- :type 'integer
- :group 'eshell-mode)
+ :type 'natnum)
(defcustom eshell-output-filter-functions
'(eshell-postoutput-scroll-to-bottom
@@ -171,35 +156,34 @@ number, if the function `eshell-truncate-buffer' is on
"Functions to call before output is displayed.
These functions are only called for output that is displayed
interactively, and not for output which is redirected."
- :type 'hook
- :group 'eshell-mode)
+ :type 'hook)
(defcustom eshell-preoutput-filter-functions nil
"Functions to call before output is inserted into the buffer.
These functions get one argument, a string containing the text to be
inserted. They return the string as it should be inserted."
- :type 'hook
- :group 'eshell-mode)
+ :type 'hook)
(defcustom eshell-password-prompt-regexp
- (format "\\(%s\\).*:\\s *\\'" (regexp-opt password-word-equivalents))
+ (format "\\(%s\\)[^::៖]*[::៖]\\s *\\'" (regexp-opt password-word-equivalents))
"Regexp matching prompts for passwords in the inferior process.
This is used by `eshell-watch-for-password-prompt'."
:type 'regexp
- :group 'eshell-mode)
+ :version "27.1")
(defcustom eshell-skip-prompt-function nil
"A function called from beginning of line to skip the prompt."
- :type '(choice (const nil) function)
- :group 'eshell-mode)
-
-(define-obsolete-variable-alias 'eshell-status-in-modeline
- 'eshell-status-in-mode-line "24.3")
+ :type '(choice (const nil) function))
(defcustom eshell-status-in-mode-line t
"If non-nil, let the user know a command is running in the mode line."
- :type 'boolean
- :group 'eshell-mode)
+ :type 'boolean)
+
+(defcustom eshell-directory-name
+ (locate-user-emacs-file "eshell/" ".eshell/")
+ "The directory where Eshell control files should be kept."
+ :type 'directory
+ :group 'eshell)
(defvar eshell-first-time-p t
"A variable which is non-nil the first time Eshell is loaded.")
@@ -209,10 +193,7 @@ This is used by `eshell-watch-for-password-prompt'."
;; these are only set to nil initially for the sake of the
;; byte-compiler, when compiling other files which `require' this one
(defvar eshell-mode nil)
-(defvar eshell-mode-map nil)
(defvar eshell-command-running-string "--")
-(defvar eshell-command-map nil)
-(defvar eshell-command-prefix nil)
(defvar eshell-last-input-start nil)
(defvar eshell-last-input-end nil)
(defvar eshell-last-output-start nil)
@@ -266,22 +247,39 @@ This is used by `eshell-watch-for-password-prompt'."
(modify-syntax-entry ?\] ")[ " st)
;; All non-word multibyte characters should be `symbol'.
(map-char-table
- (if (featurep 'xemacs)
- (lambda (key _val)
- (and (characterp key)
- (>= (char-int key) 256)
- (/= (char-syntax key) ?w)
- (modify-syntax-entry key "_ " st)))
- (lambda (key _val)
- (and (if (consp key)
- (and (>= (car key) 128)
- (/= (char-syntax (car key)) ?w))
- (and (>= key 256)
- (/= (char-syntax key) ?w)))
- (modify-syntax-entry key "_ " st))))
+ (lambda (key _val)
+ (and (if (consp key)
+ (and (>= (car key) 128)
+ (/= (char-syntax (car key)) ?w))
+ (and (>= key 256)
+ (/= (char-syntax key) ?w)))
+ (modify-syntax-entry key "_ " st)))
(standard-syntax-table))
st))
+(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:
(defun eshell-kill-buffer-function ()
@@ -291,7 +289,7 @@ and the hook `eshell-exit-hook'."
;; It's fine to run this unconditionally since it can be customized
;; via the `eshell-kill-processes-on-exit' variable.
(and (fboundp 'eshell-query-kill-processes)
- (not (memq 'eshell-query-kill-processes eshell-exit-hook))
+ (not (memq #'eshell-query-kill-processes eshell-exit-hook))
(eshell-query-kill-processes))
(run-hooks 'eshell-exit-hook))
@@ -300,85 +298,48 @@ and the hook `eshell-exit-hook'."
"Emacs shell interactive mode."
(setq-local eshell-mode t)
- ;; FIXME: What the hell!?
- (setq-local eshell-mode-map (make-sparse-keymap))
- (use-local-map eshell-mode-map)
-
- (when eshell-status-in-mode-line
+ (when (and eshell-status-in-mode-line
+ (listp mode-line-format))
(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))))
- (define-key eshell-mode-map "\r" 'eshell-send-input)
- (define-key eshell-mode-map "\M-\r" 'eshell-queue-input)
- (define-key eshell-mode-map [(meta control ?l)] 'eshell-show-output)
- (define-key eshell-mode-map [(control ?a)] 'eshell-bol)
-
- (setq-local eshell-command-prefix (make-symbol "eshell-command-prefix"))
- (fset eshell-command-prefix (make-sparse-keymap))
- (setq-local eshell-command-map (symbol-function eshell-command-prefix))
- (define-key eshell-mode-map [(control ?c)] eshell-command-prefix)
-
- ;; without this, find-tag complains about read-only text being
- ;; modified
- (if (eq (key-binding [(meta ?.)]) 'find-tag)
- (define-key eshell-mode-map [(meta ?.)] 'eshell-find-tag))
- (define-key eshell-command-map [(meta ?o)] 'eshell-mark-output)
- (define-key eshell-command-map [(meta ?d)] 'eshell-toggle-direct-send)
-
- (define-key eshell-command-map [(control ?a)] 'eshell-bol)
- (define-key eshell-command-map [(control ?b)] 'eshell-backward-argument)
- (define-key eshell-command-map [(control ?e)] 'eshell-show-maximum-output)
- (define-key eshell-command-map [(control ?f)] 'eshell-forward-argument)
- (define-key eshell-command-map [return] 'eshell-copy-old-input)
- (define-key eshell-command-map [(control ?m)] 'eshell-copy-old-input)
- (define-key eshell-command-map [(control ?o)] 'eshell-kill-output)
- (define-key eshell-command-map [(control ?r)] 'eshell-show-output)
- (define-key eshell-command-map [(control ?t)] 'eshell-truncate-buffer)
- (define-key eshell-command-map [(control ?u)] 'eshell-kill-input)
- (define-key eshell-command-map [(control ?w)] 'backward-kill-word)
- (define-key eshell-command-map [(control ?y)] 'eshell-repeat-argument)
-
+ (setq-local bookmark-make-record-function #'eshell-bookmark-make-record)
(setq local-abbrev-table eshell-mode-abbrev-table)
- (set (make-local-variable 'list-buffers-directory)
- (expand-file-name default-directory))
+ (setq-local window-point-insertion-type t)
+
+ (setq-local list-buffers-directory (expand-file-name default-directory))
;; always set the tab width to 8 in Eshell buffers, since external
;; commands which do their own formatting almost always expect this
- (set (make-local-variable 'tab-width) 8)
+ (setq-local tab-width 8)
;; don't ever use auto-fill in Eshell buffers
(setq auto-fill-function nil)
;; always display everything from a return value
- (if (boundp 'print-length)
- (set (make-local-variable 'print-length) nil))
- (if (boundp 'print-level)
- (set (make-local-variable 'print-level) nil))
+ (setq-local print-length nil)
+ (setq-local print-level nil)
;; set require-final-newline to nil; otherwise, all redirected
;; output will end with a newline, whether or not the source
;; indicated it!
- (set (make-local-variable 'require-final-newline) nil)
+ (setq-local require-final-newline nil)
- (set (make-local-variable 'max-lisp-eval-depth)
- (max 3000 max-lisp-eval-depth))
- (set (make-local-variable 'max-specpdl-size)
- (max 6000 max-lisp-eval-depth))
+ (setq-local max-lisp-eval-depth (max 3000 max-lisp-eval-depth))
- (set (make-local-variable 'eshell-last-input-start) (point-marker))
- (set (make-local-variable 'eshell-last-input-end) (point-marker))
- (set (make-local-variable 'eshell-last-output-start) (point-marker))
- (set (make-local-variable 'eshell-last-output-end) (point-marker))
- (set (make-local-variable 'eshell-last-output-block-begin) (point))
+ (setq-local eshell-last-input-start (point-marker))
+ (setq-local eshell-last-input-end (point-marker))
+ (setq-local eshell-last-output-start (point-marker))
+ (setq-local eshell-last-output-end (point-marker))
+ (setq-local eshell-last-output-block-begin (point))
(let ((modules-list (copy-sequence eshell-modules-list)))
- (make-local-variable 'eshell-modules-list)
- (setq eshell-modules-list modules-list))
+ (setq-local eshell-modules-list modules-list))
;; This is to avoid making the paragraph base direction
;; right-to-left if the first word just happens to start with a
@@ -397,7 +358,11 @@ and the hook `eshell-exit-hook'."
(unless module-shortname
(error "Invalid Eshell module name: %s" module-fullname))
(unless (featurep (intern module-shortname))
- (load module-shortname))))
+ (condition-case nil
+ (load module-shortname)
+ (error (lwarn 'eshell :error
+ "Unable to load module `%s' (defined in `eshell-modules-list')"
+ module-fullname))))))
(unless (file-exists-p eshell-directory-name)
(eshell-make-private-directory eshell-directory-name t))
@@ -409,23 +374,23 @@ and the hook `eshell-exit-hook'."
(when (and load-hook (boundp load-hook))
(if (memq initfunc (symbol-value load-hook)) (setq initfunc nil))
(run-hooks load-hook))
- ;; So we don't need the -initialize functions on the hooks (b#5375).
+ ;; So we don't need the -initialize functions on the hooks (bug#5375).
(and initfunc (fboundp initfunc) (funcall initfunc))))
(if eshell-send-direct-to-subprocesses
- (add-hook 'pre-command-hook 'eshell-intercept-commands t t))
+ (add-hook 'pre-command-hook #'eshell-intercept-commands t t))
(if eshell-scroll-to-bottom-on-input
- (add-hook 'pre-command-hook 'eshell-preinput-scroll-to-bottom t t))
+ (add-hook 'pre-command-hook #'eshell-preinput-scroll-to-bottom t t))
(when eshell-scroll-show-maximum-output
- (set (make-local-variable 'scroll-conservatively) 1000))
+ (setq-local scroll-conservatively 1000))
(when eshell-status-in-mode-line
- (add-hook 'eshell-pre-command-hook 'eshell-command-started nil t)
- (add-hook 'eshell-post-command-hook 'eshell-command-finished nil t))
+ (add-hook 'eshell-pre-command-hook #'eshell-command-started nil t)
+ (add-hook 'eshell-post-command-hook #'eshell-command-finished nil t))
- (add-hook 'kill-buffer-hook 'eshell-kill-buffer-function t t)
+ (add-hook 'kill-buffer-hook #'eshell-kill-buffer-function t t)
(if eshell-first-time-p
(run-hooks 'eshell-first-time-mode-hook))
@@ -450,22 +415,22 @@ and the hook `eshell-exit-hook'."
(if eshell-send-direct-to-subprocesses
(progn
(setq eshell-send-direct-to-subprocesses nil)
- (remove-hook 'pre-command-hook 'eshell-intercept-commands t)
+ (remove-hook 'pre-command-hook #'eshell-intercept-commands t)
(message "Sending subprocess input on RET"))
(setq eshell-send-direct-to-subprocesses t)
- (add-hook 'pre-command-hook 'eshell-intercept-commands t t)
+ (add-hook 'pre-command-hook #'eshell-intercept-commands t t)
(message "Sending subprocess input directly")))
(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))
@@ -487,13 +452,15 @@ and the hook `eshell-exit-hook'."
(defun eshell-find-tag (&optional tagname next-p regexp-p)
"A special version of `find-tag' that ignores whether the text is read-only."
+ (declare (obsolete xref-find-definition "27.1"))
(interactive)
(require 'etags)
(let ((inhibit-read-only t)
(no-default (eobp))
(find-tag-default-function 'ignore))
- (setq tagname (car (find-tag-interactive "Find tag: " no-default)))
- (find-tag tagname next-p regexp-p)))
+ (setq tagname (car (find-tag-interactive "Find tag" no-default)))
+ (with-suppressed-warnings ((obsolete find-tag))
+ (find-tag tagname next-p regexp-p))))
(defun eshell-move-argument (limit func property arg)
"Move forward ARG arguments."
@@ -532,7 +499,7 @@ and the hook `eshell-exit-hook'."
(yank)))
(defun eshell-bol ()
- "Goes to the beginning of line, then skips past the prompt, if any."
+ "Go to the beginning of line, then skip past the prompt, if any."
(interactive)
(beginning-of-line)
(and eshell-skip-prompt-function
@@ -542,7 +509,7 @@ and the hook `eshell-exit-hook'."
"Push a mark at the end of the last input text."
(push-mark (1- eshell-last-input-end) t))
-(custom-add-option 'eshell-pre-command-hook 'eshell-push-command-mark)
+(custom-add-option 'eshell-pre-command-hook #'eshell-push-command-mark)
(defsubst eshell-goto-input-start ()
"Goto the start of the last command input.
@@ -550,7 +517,7 @@ Putting this function on `eshell-pre-command-hook' will mimic Plan 9's
9term behavior."
(goto-char eshell-last-input-start))
-(custom-add-option 'eshell-pre-command-hook 'eshell-push-command-mark)
+(custom-add-option 'eshell-pre-command-hook #'eshell-goto-input-start)
(defsubst eshell-interactive-print (string)
"Print STRING to the eshell display buffer."
@@ -629,13 +596,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))
@@ -647,14 +614,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))
@@ -691,6 +666,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))
@@ -701,46 +686,44 @@ newline."
"Send the output from PROCESS (STRING) to the interactive display.
This is done after all necessary filtering has been done."
(let ((oprocbuf (if process (process-buffer process)
- (current-buffer)))
- (inhibit-point-motion-hooks t)
- (inhibit-modification-hooks t))
- (let ((functions eshell-preoutput-filter-functions))
- (while (and functions string)
- (setq string (funcall (car functions) string))
- (setq functions (cdr functions))))
- (if (and string oprocbuf (buffer-name oprocbuf))
- (let (opoint obeg oend)
- (with-current-buffer oprocbuf
- (setq opoint (point))
- (setq obeg (point-min))
- (setq oend (point-max))
- (let ((buffer-read-only nil)
- (nchars (length string))
- (ostart nil))
- (widen)
- (goto-char eshell-last-output-end)
- (setq ostart (point))
- (if (<= (point) opoint)
- (setq opoint (+ opoint nchars)))
- (if (< (point) obeg)
- (setq obeg (+ obeg nchars)))
- (if (<= (point) oend)
- (setq oend (+ oend nchars)))
+ (current-buffer)))
+ (inhibit-point-motion-hooks t)
+ (inhibit-modification-hooks t))
+ (when (and string oprocbuf (buffer-name oprocbuf))
+ (with-current-buffer oprocbuf
+ (let ((functions eshell-preoutput-filter-functions))
+ (while (and functions string)
+ (setq string (funcall (car functions) string))
+ (setq functions (cdr functions))))
+ (when string
+ (let (opoint obeg oend)
+ (setq opoint (point))
+ (setq obeg (point-min))
+ (setq oend (point-max))
+ (let ((buffer-read-only nil)
+ (nchars (length string))
+ (ostart nil))
+ (widen)
+ (goto-char eshell-last-output-end)
+ (setq ostart (point))
+ (if (<= (point) opoint)
+ (setq opoint (+ opoint nchars)))
+ (if (< (point) obeg)
+ (setq obeg (+ obeg nchars)))
+ (if (<= (point) oend)
+ (setq oend (+ oend nchars)))
;; Let the ansi-color overlay hooks run.
(let ((inhibit-modification-hooks nil))
- (insert-before-markers string))
- (if (= (window-start) (point))
- (set-window-start (selected-window)
- (- (point) nchars)))
- (if (= (point) eshell-last-input-end)
- (set-marker eshell-last-input-end
- (- eshell-last-input-end nchars)))
- (set-marker eshell-last-output-start ostart)
- (set-marker eshell-last-output-end (point))
- (force-mode-line-update))
- (narrow-to-region obeg oend)
- (goto-char opoint)
- (eshell-run-output-filters))))))
+ (insert string))
+ (if (= (window-start) (point))
+ (set-window-start (selected-window)
+ (- (point) nchars)))
+ (set-marker eshell-last-output-start ostart)
+ (set-marker eshell-last-output-end (point))
+ (force-mode-line-update))
+ (narrow-to-region obeg oend)
+ (goto-char opoint)
+ (eshell-run-output-filters)))))))
(defun eshell-run-output-filters ()
"Run the `eshell-output-filter-functions' on the current output."
@@ -767,13 +750,12 @@ This function should be a pre-command hook."
(if (eq scroll 'this)
(goto-char (point-max))
(walk-windows
- (function
- (lambda (window)
- (when (and (eq (window-buffer window) current)
- (or (eq scroll t) (eq scroll 'all)))
- (select-window window)
- (goto-char (point-max))
- (select-window selected))))
+ (lambda (window)
+ (when (and (eq (window-buffer window) current)
+ (or (eq scroll t) (eq scroll 'all)))
+ (select-window window)
+ (goto-char (point-max))
+ (select-window selected)))
nil t))))))
;;; jww (1999-10-23): this needs testing
@@ -789,29 +771,28 @@ This function should be in the list `eshell-output-filter-functions'."
(scroll eshell-scroll-to-bottom-on-output))
(unwind-protect
(walk-windows
- (function
- (lambda (window)
- (if (eq (window-buffer window) current)
- (progn
- (select-window window)
- (if (and (< (point) eshell-last-output-end)
- (or (eq scroll t) (eq scroll 'all)
- ;; Maybe user wants point to jump to end.
- (and (eq scroll 'this)
- (eq selected window))
- (and (eq scroll 'others)
- (not (eq selected window)))
- ;; If point was at the end, keep it at end.
- (>= (point) eshell-last-output-start)))
- (goto-char eshell-last-output-end))
- ;; Optionally scroll so that the text
- ;; ends at the bottom of the window.
- (if (and eshell-scroll-show-maximum-output
- (>= (point) eshell-last-output-end))
- (save-excursion
- (goto-char (point-max))
- (recenter -1)))
- (select-window selected)))))
+ (lambda (window)
+ (if (eq (window-buffer window) current)
+ (progn
+ (select-window window)
+ (if (and (< (point) eshell-last-output-end)
+ (or (eq scroll t) (eq scroll 'all)
+ ;; Maybe user wants point to jump to end.
+ (and (eq scroll 'this)
+ (eq selected window))
+ (and (eq scroll 'others)
+ (not (eq selected window)))
+ ;; If point was at the end, keep it at end.
+ (>= (point) eshell-last-output-start)))
+ (goto-char eshell-last-output-end))
+ ;; Optionally scroll so that the text
+ ;; ends at the bottom of the window.
+ (if (and eshell-scroll-show-maximum-output
+ (>= (point) eshell-last-output-end))
+ (save-excursion
+ (goto-char (point-max))
+ (recenter -1)))
+ (select-window selected))))
nil t)
(set-buffer current))))
@@ -884,8 +865,7 @@ If SCROLLBACK is non-nil, clear the scrollback contents."
(interactive)
(if scrollback
(eshell/clear-scrollback)
- (let ((eshell-input-filter-functions
- (remq 'eshell-add-to-history eshell-input-filter-functions)))
+ (let ((eshell-input-filter-functions nil))
(insert (make-string (window-size) ?\n))
(eshell-send-input))))
@@ -955,9 +935,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"))))
@@ -968,14 +948,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)
@@ -1012,13 +999,43 @@ This function could be in the list `eshell-output-filter-functions'."
'eshell-handle-control-codes)
(autoload 'ansi-color-apply-on-region "ansi-color")
+(defvar ansi-color-apply-face-function)
+(declare-function ansi-color-apply-text-property-face "ansi-color"
+ (BEG END FACE))
(defun eshell-handle-ansi-color ()
"Handle ANSI color codes."
- (ansi-color-apply-on-region eshell-last-output-start
- eshell-last-output-end))
+ (require 'ansi-color)
+ (let ((ansi-color-apply-face-function #'ansi-color-apply-text-property-face))
+ (ansi-color-apply-on-region eshell-last-output-start
+ eshell-last-output-end)))
(custom-add-option 'eshell-output-filter-functions
'eshell-handle-ansi-color)
+;;; Bookmark support:
+
+(declare-function bookmark-prop-get "bookmark" (bookmark prop))
+
+(defun eshell-bookmark-name ()
+ (format "eshell-%s"
+ (file-name-nondirectory
+ (directory-file-name
+ (file-name-directory default-directory)))))
+
+(defun eshell-bookmark-make-record ()
+ "Create a bookmark for the current Eshell buffer."
+ `(,(eshell-bookmark-name)
+ (location . ,default-directory)
+ (handler . eshell-bookmark-jump)))
+
+;;;###autoload
+(defun eshell-bookmark-jump (bookmark)
+ "Default bookmark handler for Eshell buffers."
+ (let ((default-directory (bookmark-prop-get bookmark 'location)))
+ (eshell)))
+
+(put 'eshell-bookmark-jump 'bookmark-handler-type "Eshell")
+
+(provide 'esh-mode)
;;; esh-mode.el ends here
diff --git a/lisp/eshell/esh-module.el b/lisp/eshell/esh-module.el
index fe4c88e1cfd..14e91912d11 100644
--- a/lisp/eshell/esh-module.el
+++ b/lisp/eshell/esh-module.el
@@ -1,6 +1,6 @@
;;; esh-module.el --- Eshell modules -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2000, 2002-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2000, 2002-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
;; Keywords: processes
@@ -20,11 +20,10 @@
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
-;;; Code:
+;;; Commentary:
-(provide 'esh-module)
+;;; Code:
-(require 'eshell)
(require 'esh-util)
(defgroup eshell-module nil
@@ -55,6 +54,7 @@ customizing the variable `eshell-modules-list'."
eshell-basic
eshell-cmpl
eshell-dirs
+ eshell-extpipe
eshell-glob
eshell-hist
eshell-ls
@@ -68,16 +68,15 @@ Changes will only take effect in future Eshell buffers."
:type (append
(list 'set ':tag "Supported modules")
(mapcar
- (function
- (lambda (modname)
- (let ((modsym (intern modname)))
- (list 'const
- ':tag (format "%s -- %s" modname
- (get modsym 'custom-tag))
- ':link (caar (get modsym 'custom-links))
- ':doc (concat "\n" (get modsym 'group-documentation)
- "\n ")
- modsym))))
+ (lambda (modname)
+ (let ((modsym (intern modname)))
+ (list 'const
+ ':tag (format "%s -- %s" modname
+ (get modsym 'custom-tag))
+ ':link (caar (get modsym 'custom-links))
+ ':doc (concat "\n" (get modsym 'group-documentation)
+ "\n ")
+ modsym)))
(sort (mapcar 'symbol-name
(eshell-subgroups 'eshell-module))
'string-lessp))
@@ -101,4 +100,5 @@ customization group. Example: `eshell-cmpl' for that module."
(unload-feature module)
(message "Unloading %s...done" (symbol-name module))))))
+(provide 'esh-module)
;;; esh-module.el ends here
diff --git a/lisp/eshell/esh-opt.el b/lisp/eshell/esh-opt.el
index c141fe0bcea..f52b70fe7a6 100644
--- a/lisp/eshell/esh-opt.el
+++ b/lisp/eshell/esh-opt.el
@@ -1,6 +1,6 @@
;;; esh-opt.el --- command options processing -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -23,19 +23,12 @@
;;; Code:
-(provide 'esh-opt)
-
-(require 'esh-ext)
-
-;; Unused.
-;; (defgroup eshell-opt nil
-;; "The options processing code handles command argument parsing for
-;; Eshell commands implemented in Lisp."
-;; :tag "Command options processing"
-;; :group 'eshell)
-
;;; User Functions:
+;; Macro expansion of eshell-eval-using-options refers to eshell-stringify-list
+;; defined in esh-util.
+(require 'esh-util)
+
(defmacro eshell-eval-using-options (name macro-args options &rest body-forms)
"Process NAME's MACRO-ARGS using a set of command line OPTIONS.
After doing so, stores settings in local symbols as declared by OPTIONS;
@@ -77,9 +70,13 @@ arguments, some do not. The recognized :KEYWORDS are:
arguments.
:preserve-args
- If present, do not pass MACRO-ARGS through `eshell-flatten-list'
+ If present, do not pass MACRO-ARGS through `flatten-tree'
and `eshell-stringify-list'.
+:parse-leading-options-only
+ If present, do not parse dash or switch arguments after the first
+positional argument. Instead, treat them as positional arguments themselves.
+
For example, OPTIONS might look like:
((?C nil nil multi-column \"multi-column display\")
@@ -95,15 +92,15 @@ BODY-FORMS. If instead an external command is run (because of
an unknown option), the tag `eshell-external' will be thrown with
the new process for its value.
-Lastly, any remaining arguments will be available in a locally
-interned variable `args' (created using a `let' form)."
+Lastly, any remaining arguments will be available in the locally
+let-bound variable `args'."
(declare (debug (form form sexp body)))
`(let* ((temp-args
,(if (memq ':preserve-args (cadr options))
- macro-args
+ (list 'copy-tree macro-args)
(list 'eshell-stringify-list
- (list 'eshell-flatten-list macro-args))))
- (processed-args (eshell--do-opts ,name ,options temp-args))
+ (list 'flatten-tree macro-args))))
+ (processed-args (eshell--do-opts ,name ,options temp-args ,macro-args))
,@(delete-dups
(delq nil (mapcar (lambda (opt)
(and (listp opt) (nth 3 opt)
@@ -111,6 +108,8 @@ interned variable `args' (created using a `let' form)."
;; `options' is of the form (quote OPTS).
(cadr options))))
(args processed-args))
+ ;; Silence unused lexical variable warning if body does not use `args'.
+ (ignore args)
,@body-forms))
;;; Internal Functions:
@@ -118,9 +117,11 @@ interned variable `args' (created using a `let' form)."
;; 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)
+ (declare-function eshell-external-command "esh-ext" (command args))
(let ((ext-command
(catch 'eshell-ext-command
(let ((usage-msg
@@ -134,11 +135,13 @@ 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)
"Display the usage message for NAME, using OPTIONS."
+ (require 'esh-ext)
+ (declare-function eshell-search-path "esh-ext" (name))
(let ((usage (format "usage: %s %s\n\n" name
(cadr (memq ':usage options))))
(extcmd (memq ':external options))
@@ -184,53 +187,82 @@ passed to this command, the external version `%s'
will be called instead." extcmd)))))
(throw 'eshell-usage usage)))
-(defun eshell--set-option (name ai opt options opt-vals)
+(defun eshell--split-switch (switch kind)
+ "Split SWITCH into its option name and potential value, if any.
+KIND should be the integer 0 if SWITCH is a short option, or 1 if it's
+a long option."
+ (if (eq kind 0)
+ ;; Short option
+ (cons (aref switch 0)
+ (and (> (length switch) 1) (substring switch 1)))
+ ;; Long option
+ (save-match-data
+ (string-match "\\([^=]*\\)\\(?:=\\(.*\\)\\)?" switch)
+ (cons (match-string 1 switch) (match-string 2 switch)))))
+
+(defun eshell--set-option (name ai opt value options opt-vals)
"Using NAME's remaining args (index AI), set the OPT within OPTIONS.
-If the option consumes an argument for its value, the argument list
-will be modified."
+VALUE is the potential value of the OPT, coming from args like
+\"-fVALUE\" or \"--foo=VALUE\", or nil if no value was supplied. If
+OPT doesn't consume a value, return VALUE unchanged so that it can be
+processed later; otherwise, return nil.
+
+If the OPT consumes an argument for its value and VALUE is nil, the
+argument list will be modified."
(if (not (nth 3 opt))
(eshell-show-usage name options)
- (setcdr (assq (nth 3 opt) opt-vals)
- (if (eq (nth 2 opt) t)
- (if (> ai (length eshell--args))
- (error "%s: missing option argument" name)
- (prog1 (nth ai eshell--args)
- (if (> ai 0)
- (setcdr (nthcdr (1- ai) eshell--args)
- (nthcdr (1+ ai) eshell--args))
- (setq eshell--args (cdr 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."
@@ -243,27 +275,33 @@ switch is unrecognized."
(list sym)))))
options)))
(ai 0) arg
- (eshell--args args))
- (while (< ai (length args))
- (setq arg (nth ai args))
+ (eshell--args args)
+ (pos-argument-found nil))
+ (while (and (< ai (length eshell--args))
+ ;; Abort if we saw the first pos argument and option is set
+ (not (and pos-argument-found
+ (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)))
- (setq ai (1+ ai))
+ ;; Positional argument found, skip
+ (setq ai (1+ ai)
+ pos-argument-found t)
+ ;; dash or switch argument found, parse
(let* ((dash (match-string 1 arg))
(switch (match-string 2 arg)))
- (if (= ai 0)
- (setq args (cdr args))
- (setcdr (nthcdr (1- ai) args) (nthcdr (1+ ai) args)))
+ (pop (nthcdr ai eshell--args))
(if dash
(if (> (length switch) 0)
(eshell--process-option name switch 1 ai options opt-vals)
- (setq ai (length 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))))))))
- (nconc (mapcar #'cdr opt-vals) args)))
+ (setq ai (length eshell--args)))
+ (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)
;;; esh-opt.el ends here
diff --git a/lisp/eshell/esh-proc.el b/lisp/eshell/esh-proc.el
index 3e9ac281a10..7e005a0fc1c 100644
--- a/lisp/eshell/esh-proc.el
+++ b/lisp/eshell/esh-proc.el
@@ -1,6 +1,6 @@
;;; esh-proc.el --- process management -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -23,9 +23,7 @@
;;; Code:
-(provide 'esh-proc)
-
-(require 'esh-cmd)
+(require 'esh-io)
(defgroup eshell-proc nil
"When Eshell invokes external commands, it always does so
@@ -39,23 +37,19 @@ finish."
(defcustom eshell-proc-load-hook nil
"A hook that gets run when `eshell-proc' is loaded."
:version "24.1" ; removed eshell-proc-initialize
- :type 'hook
- :group 'eshell-proc)
+ :type 'hook)
(defcustom eshell-process-wait-seconds 0
"The number of seconds to delay waiting for a synchronous process."
- :type 'integer
- :group 'eshell-proc)
+ :type 'integer)
(defcustom eshell-process-wait-milliseconds 50
"The number of milliseconds to delay waiting for a synchronous process."
- :type 'integer
- :group 'eshell-proc)
+ :type 'integer)
(defcustom eshell-done-messages-in-minibuffer t
"If non-nil, subjob \"Done\" messages will display in minibuffer."
- :type 'boolean
- :group 'eshell-proc)
+ :type 'boolean)
(defcustom eshell-delete-exited-processes t
"If nil, process entries will stick around until `jobs' is run.
@@ -74,24 +68,21 @@ subjob is done is that it will no longer appear in the
Note that Eshell will have to be restarted for a change in this
variable's value to take effect."
- :type 'boolean
- :group 'eshell-proc)
+ :type 'boolean)
(defcustom eshell-reset-signals
"^\\(interrupt\\|killed\\|quit\\|stopped\\)"
"If a termination signal matches this regexp, the terminal will be reset."
- :type 'regexp
- :group 'eshell-proc)
+ :type 'regexp)
(defcustom eshell-exec-hook nil
"Called each time a process is exec'd by `eshell-gather-process-output'.
It is passed one argument, which is the process that was just started.
It is useful for things that must be done each time a process is
-executed in a eshell mode buffer (e.g., `process-kill-without-query').
-In contrast, `eshell-mode-hook' is only executed once when the buffer
+executed in an eshell mode buffer (e.g., `set-process-query-on-exit-flag').
+In contrast, `eshell-mode-hook' is only executed once, when the buffer
is created."
- :type 'hook
- :group 'eshell-proc)
+ :type 'hook)
(defcustom eshell-kill-hook nil
"Called when a process run by `eshell-gather-process-output' has ended.
@@ -101,15 +92,31 @@ nil, in which case the user attempted to send a signal, but there was
no relevant process. This can be used for displaying help
information, for example."
:version "24.1" ; removed eshell-reset-after-proc
- :type 'hook
- :group 'eshell-proc)
+ :type 'hook)
;;; Internal Variables:
(defvar eshell-current-subjob-p nil)
(defvar eshell-process-list nil
- "A list of the current status of subprocesses.")
+ "A list of the current status of subprocesses.
+Each element has the form (PROC . SUBJOB-P), where PROC is the
+process object and SUBJOB-P is non-nil if the process is a
+subjob.
+
+To add or remove elements of this list, see
+`eshell-record-process-object' and `eshell-remove-process-entry'.")
+
+(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:
@@ -118,30 +125,32 @@ information, for example."
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.
- (if (memq 'eshell-reset-after-proc eshell-kill-hook)
- (setq eshell-kill-hook (delq 'eshell-reset-after-proc eshell-kill-hook)))
- (eshell-reset-after-proc status)
+ (remove-hook 'eshell-kill-hook #'eshell-reset-after-proc)
+ ;; 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))
-(defun eshell-proc-initialize ()
+(define-minor-mode eshell-proc-mode
+ "Minor mode for the proc eshell module.
+
+\\{eshell-proc-mode-map}"
+ :keymap eshell-proc-mode-map)
+
+(defun eshell-proc-initialize () ;Called from `eshell-mode' via intern-soft!
"Initialize the process handling code."
(make-local-variable 'eshell-process-list)
- (define-key eshell-command-map [(meta ?i)] 'eshell-insert-process)
- (define-key eshell-command-map [(control ?c)] 'eshell-interrupt-process)
- (define-key eshell-command-map [(control ?k)] 'eshell-kill-process)
- (define-key eshell-command-map [(control ?d)] 'eshell-send-eof-to-process)
-; (define-key eshell-command-map [(control ?q)] 'eshell-continue-process)
- (define-key eshell-command-map [(control ?s)] 'list-processes)
-; (define-key eshell-command-map [(control ?z)] 'eshell-stop-process)
- (define-key eshell-command-map [(control ?\\)] 'eshell-quit-process))
+ (eshell-proc-mode))
(defun eshell-reset-after-proc (status)
"Reset the command input location after a process terminates.
The signals which will cause this to happen are matched by
`eshell-reset-signals'."
- (if (and (stringp status)
- (string-match eshell-reset-signals status))
- (eshell-reset)))
+ (when (and (stringp status)
+ (string-match eshell-reset-signals status))
+ (require 'esh-mode)
+ (declare-function eshell-reset "esh-mode" (&optional no-hooks))
+ (eshell-reset)))
(defun eshell-wait-for-process (&rest procs)
"Wait until PROC has successfully completed."
@@ -156,9 +165,9 @@ The signals which will cause this to happen are matched by
eshell-process-wait-milliseconds))))
(setq procs (cdr procs))))
-(defalias 'eshell/wait 'eshell-wait-for-process)
+(defalias 'eshell/wait #'eshell-wait-for-process)
-(defun eshell/jobs (&rest args)
+(defun eshell/jobs (&rest _args)
"List processes, if there are any."
(and (fboundp 'process-list)
(process-list)
@@ -167,7 +176,8 @@ The signals which will cause this to happen are matched by
(defun eshell/kill (&rest args)
"Kill processes.
Usage: kill [-<signal>] <pid>|<process> ...
-Accepts PIDs and process objects."
+Accepts PIDs and process objects. Optionally accept signals
+and signal names."
;; If the first argument starts with a dash, treat it as the signal
;; specifier.
(let ((signum 'SIGINT))
@@ -178,12 +188,12 @@ Accepts PIDs and process objects."
((string-match "\\`-[[:digit:]]+\\'" arg)
(setq signum (abs (string-to-number arg))))
((string-match "\\`-\\([[:upper:]]+\\|[[:lower:]]+\\)\\'" arg)
- (setq signum (abs (string-to-number arg)))))
+ (setq signum (intern (substring arg 1)))))
(setq args (cdr args))))
(while args
(let ((arg (if (eshell-processp (car args))
(process-id (car args))
- (car args))))
+ (string-to-number (car args)))))
(when arg
(cond
((null arg)
@@ -198,15 +208,17 @@ Accepts PIDs and process objects."
(setq args (cdr args))))
nil)
+(put 'eshell/kill 'eshell-no-numeric-conversions t)
+
(defun eshell-read-process-name (prompt)
"Read the name of a process from the minibuffer, using completion.
The prompt will be set to PROMPT."
(completing-read prompt
(mapcar
- (function
- (lambda (proc)
- (cons (process-name proc) t)))
- (process-list)) nil t))
+ (lambda (proc)
+ (cons (process-name proc) t))
+ (process-list))
+ nil t))
(defun eshell-insert-process (process)
"Insert the name of PROCESS into the current buffer at point."
@@ -217,53 +229,46 @@ The prompt will be set to PROMPT."
(defsubst eshell-record-process-object (object)
"Record OBJECT as now running."
- (if (and (eshell-processp object)
- eshell-current-subjob-p)
- (eshell-interactive-print
- (format "[%s] %d\n" (process-name object) (process-id object))))
- (setq eshell-process-list
- (cons (list object eshell-current-handles
- eshell-current-subjob-p nil nil)
- eshell-process-list)))
+ (when (and (eshell-processp object)
+ eshell-current-subjob-p)
+ (require 'esh-mode)
+ (declare-function eshell-interactive-print "esh-mode" (string))
+ (eshell-interactive-print
+ (format "[%s] %d\n" (process-name object) (process-id object))))
+ (push (cons object eshell-current-subjob-p) eshell-process-list))
(defun eshell-remove-process-entry (entry)
"Record the process ENTRY as fully completed."
(if (and (eshell-processp (car entry))
- (nth 2 entry)
+ (cdr entry)
eshell-done-messages-in-minibuffer)
(message "[%s]+ Done %s" (process-name (car entry))
(process-command (car entry))))
(setq eshell-process-list
(delq entry eshell-process-list)))
+(defun eshell-record-process-properties (process &optional index)
+ "Record Eshell bookkeeping properties for PROCESS.
+`eshell-insertion-filter' and `eshell-sentinel' will use these to
+do their jobs.
+
+INDEX is the index of the output handle to use for writing; if
+nil, write to `eshell-output-handle'."
+ (process-put process :eshell-handles eshell-current-handles)
+ (process-put process :eshell-handle-index (or index eshell-output-handle))
+ (process-put process :eshell-pending nil)
+ (process-put process :eshell-busy nil))
+
(defvar eshell-scratch-buffer " *eshell-scratch*"
"Scratch buffer for holding Eshell's input/output.")
(defvar eshell-last-sync-output-start nil
"A marker that tracks the beginning of output of the last subprocess.
Used only on systems which do not support async subprocesses.")
-(defvar eshell-needs-pipe '("bc")
- "List of commands which need `process-connection-type' to be nil.
-Currently only affects commands in pipelines, and not those at
-the front. If an element contains a directory part it must match
-the full name of a command, otherwise just the nondirectory part must match.")
-
-(defun eshell-needs-pipe-p (command)
- "Return non-nil if COMMAND needs `process-connection-type' to be nil.
-See `eshell-needs-pipe'."
- (and eshell-in-pipeline-p
- (not (eq eshell-in-pipeline-p 'first))
- ;; FIXME should this return non-nil for anything that is
- ;; neither 'first nor 'last? See bug#1388 discussion.
- (catch 'found
- (dolist (exe eshell-needs-pipe)
- (if (string-equal exe (if (string-match "/" exe)
- command
- (file-name-nondirectory command)))
- (throw 'found t))))))
-
(defun eshell-gather-process-output (command args)
"Gather the output from COMMAND + ARGS."
+ (require 'esh-var)
+ (declare-function eshell-environment-variables "esh-var" ())
(unless (and (file-executable-p command)
(file-regular-p (file-truename command)))
(error "%s: not an executable file" command))
@@ -272,37 +277,54 @@ See `eshell-needs-pipe'."
eshell-delete-exited-processes
delete-exited-processes))
(process-environment (eshell-environment-variables))
- proc decoding encoding changed)
+ proc stderr-proc decoding encoding changed)
(cond
- ((fboundp 'start-file-process)
+ ((fboundp 'make-process)
+ (unless (equal (car (aref eshell-current-handles eshell-output-handle))
+ (car (aref eshell-current-handles eshell-error-handle)))
+ (eshell-protect-handles eshell-current-handles)
+ (setq stderr-proc
+ (make-pipe-process
+ :name (concat (file-name-nondirectory command) "-stderr")
+ :buffer (current-buffer)
+ :filter (if (eshell-interactive-output-p eshell-error-handle)
+ #'eshell-output-filter
+ #'eshell-insertion-filter)
+ :sentinel #'eshell-sentinel))
+ (eshell-record-process-properties stderr-proc eshell-error-handle))
(setq proc
- (let ((process-connection-type
- (unless (eshell-needs-pipe-p command)
- process-connection-type))
- (command (file-local-name command)))
- (apply 'start-file-process
- (file-name-nondirectory command) nil
- ;; `start-process' can't deal with relative filenames.
- (append (list (expand-file-name command)) args))))
+ (let ((command (file-local-name (expand-file-name command)))
+ (conn-type (pcase (bound-and-true-p eshell-in-pipeline-p)
+ ('first '(nil . pipe))
+ ('last '(pipe . nil))
+ ('t 'pipe)
+ ('nil nil))))
+ (make-process
+ :name (file-name-nondirectory command)
+ :buffer (current-buffer)
+ :command (cons command args)
+ :filter (if (eshell-interactive-output-p)
+ #'eshell-output-filter
+ #'eshell-insertion-filter)
+ :sentinel #'eshell-sentinel
+ :connection-type conn-type
+ :stderr stderr-proc
+ :file-handler t)))
(eshell-record-process-object proc)
- (set-process-buffer proc (current-buffer))
- (if (eshell-interactive-output-p)
- (set-process-filter proc 'eshell-output-filter)
- (set-process-filter proc 'eshell-insertion-filter))
- (set-process-sentinel proc 'eshell-sentinel)
+ (eshell-record-process-properties proc)
(run-hook-with-args 'eshell-exec-hook proc)
(when (fboundp 'process-coding-system)
(let ((coding-systems (process-coding-system proc)))
(setq decoding (car coding-systems)
encoding (cdr coding-systems)))
- ;; If start-process decided to use some coding system for
+ ;; If `make-process' decided to use some coding system for
;; decoding data sent from the process and the coding system
;; doesn't specify EOL conversion, we had better convert CRLF
;; to LF.
(if (vectorp (coding-system-eol-type decoding))
(setq decoding (coding-system-change-eol-conversion decoding 'dos)
changed t))
- ;; Even if start-process left the coding system for encoding
+ ;; Even if `make-process' left the coding system for encoding
;; data sent from the process undecided, we had better use the
;; same one as what we use for decoding. But, we should
;; suppress EOL conversion.
@@ -324,14 +346,14 @@ See `eshell-needs-pipe'."
(set-buffer oldbuf)
(run-hook-with-args 'eshell-exec-hook command)
(setq exit-status
- (apply 'call-process-region
+ (apply #'call-process-region
(append (list eshell-last-sync-output-start (point)
command t
eshell-scratch-buffer nil)
args)))
;; When in a pipeline, record the place where the output of
;; this process will begin.
- (and eshell-in-pipeline-p
+ (and (bound-and-true-p eshell-in-pipeline-p)
(set-marker eshell-last-sync-output-start (point)))
;; Simulate the effect of the process filter.
(when (numberp exit-status)
@@ -348,11 +370,16 @@ See `eshell-needs-pipe'."
(setq lbeg lend)
(set-buffer proc-buf))
(set-buffer oldbuf))
+ (require 'esh-mode)
+ (declare-function eshell-update-markers "esh-mode" (pmark))
+ (defvar eshell-last-output-end) ;Defined in esh-mode.el.
(eshell-update-markers eshell-last-output-end)
;; Simulate the effect of eshell-sentinel.
- (eshell-close-handles (if (numberp exit-status) exit-status -1))
+ (eshell-close-handles
+ (if (numberp exit-status) exit-status -1)
+ (list 'quote (and (numberp exit-status) (= exit-status 0))))
(eshell-kill-process-function command exit-status)
- (or eshell-in-pipeline-p
+ (or (bound-and-true-p eshell-in-pipeline-p)
(setq eshell-last-sync-output-start nil))
(if (not (numberp exit-status))
(error "%s: external command failed: %s" command exit-status))
@@ -365,17 +392,36 @@ PROC is the process for which we're inserting output. STRING is the
output."
(when (buffer-live-p (process-buffer proc))
(with-current-buffer (process-buffer proc)
- (let ((entry (assq proc eshell-process-list)))
- (when entry
- (setcar (nthcdr 3 entry)
- (concat (nth 3 entry) string))
- (unless (nth 4 entry) ; already being handled?
- (while (nth 3 entry)
- (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)))))))))
+ (process-put proc :eshell-pending
+ (concat (process-get proc :eshell-pending)
+ string))
+ (unless (process-get proc :eshell-busy) ; Already being handled?
+ (while (process-get proc :eshell-pending)
+ (let ((handles (process-get proc :eshell-handles))
+ (index (process-get proc :eshell-handle-index))
+ (data (process-get proc :eshell-pending)))
+ (process-put proc :eshell-pending nil)
+ (process-put proc :eshell-busy t)
+ (unwind-protect
+ (condition-case nil
+ (eshell-output-object data index handles)
+ ;; 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))))
+ (process-put proc :eshell-busy nil))))))))
(defun eshell-sentinel (proc string)
"Generic sentinel for command processes. Reports only signals.
@@ -383,32 +429,39 @@ 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)))
-; (if (not entry)
-; (error "Sentinel called for unowned process `%s'"
-; (process-name proc))
- (when entry
- (unwind-protect
- (progn
- (unless (string= string "run")
- (unless (string-match "^\\(finished\\|exited\\)" string)
- (eshell-insertion-filter proc string))
- (let ((handles (nth 1 entry))
- (str (prog1 (nth 3 entry)
- (setf (nth 3 entry) nil)))
- (status (process-exit-status proc)))
- ;; If we're in the middle of handling output
- ;; from this process then schedule the EOF for
- ;; later.
- (letrec ((finish-io
- (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)))))
- (funcall finish-io)))))
- (eshell-remove-process-entry entry))))
- (eshell-kill-process-function proc string)))))
+ (unless (string= string "run")
+ ;; 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 (process-get proc :eshell-handles))
+ (index (process-get proc :eshell-handle-index))
+ (data (process-get proc :eshell-pending))
+ ;; Only get the status for the primary subprocess,
+ ;; not the pipe process (if any).
+ (status (when (= index eshell-output-handle)
+ (process-exit-status proc))))
+ (process-put proc :eshell-pending nil)
+ ;; If we're in the middle of handling output from this
+ ;; process then schedule the EOF for later.
+ (letrec ((finish-io
+ (lambda ()
+ (if (process-get proc :eshell-busy)
+ (run-at-time 0 nil finish-io)
+ (when data
+ (ignore-error 'eshell-pipe-broken
+ (eshell-output-object
+ data index handles)))
+ (eshell-close-handles
+ status
+ (when status (list 'quote (= status 0)))
+ handles)))))
+ (funcall finish-io))))
+ (when-let ((entry (assq proc eshell-process-list)))
+ (eshell-remove-process-entry entry))
+ (eshell-kill-process-function proc string)))))
(defun eshell-process-interact (func &optional all query)
"Interact with a process, using PROMPT if more than one, via FUNC.
@@ -419,7 +472,7 @@ If QUERY is non-nil, query the user with QUERY before calling FUNC."
(if (and (memq (process-status (car entry))
'(run stop open closed))
(or all
- (not (nth 2 entry)))
+ (not (cdr entry)))
(or (not query)
(y-or-n-p (format-message query
(process-name (car entry))))))
@@ -436,8 +489,7 @@ If QUERY is non-nil, query the user with QUERY before calling FUNC."
(defcustom eshell-kill-process-wait-time 5
"Seconds to wait between sending termination signals to a subprocess."
- :type 'integer
- :group 'eshell-proc)
+ :type 'integer)
(defcustom eshell-kill-process-signals '(SIGINT SIGQUIT SIGKILL)
"Signals used to kill processes when an Eshell buffer exits.
@@ -445,8 +497,7 @@ Eshell calls each of these signals in order when an Eshell buffer is
killed; if the process is still alive afterwards, Eshell waits a
number of seconds defined by `eshell-kill-process-wait-time', and
tries the next signal in the list."
- :type '(repeat symbol)
- :group 'eshell-proc)
+ :type '(repeat symbol))
(defcustom eshell-kill-processes-on-exit nil
"If non-nil, kill active processes when exiting an Eshell buffer.
@@ -468,8 +519,7 @@ long to delay between signals."
:type '(choice (const :tag "Kill all, don't ask" t)
(const :tag "Ask before killing" ask)
(const :tag "Ask for each process" every)
- (const :tag "Don't kill subprocesses" nil))
- :group 'eshell-proc)
+ (const :tag "Don't kill subprocesses" nil)))
(defun eshell-round-robin-kill (&optional query)
"Kill current process by trying various signals in sequence.
@@ -477,9 +527,8 @@ See the variable `eshell-kill-processes-on-exit'."
(let ((sigs eshell-kill-process-signals))
(while sigs
(eshell-process-interact
- (function
- (lambda (proc)
- (signal-process (process-id proc) (car sigs)))) t query)
+ (lambda (proc)
+ (signal-process (process-id proc) (car sigs))) t query)
(setq query nil)
(if (not eshell-process-list)
(setq sigs nil)
@@ -497,7 +546,7 @@ See the variable `eshell-kill-processes-on-exit'."
(buffer-name))))
(eshell-round-robin-kill
(if (eq eshell-kill-processes-on-exit 'every)
- (format-message "Kill Eshell child process `%s'? "))))
+ "Kill Eshell child process `%s'? ")))
(let ((buf (get-buffer "*Process List*")))
(if (and buf (buffer-live-p buf))
(kill-buffer buf)))
@@ -536,10 +585,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)
- (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 8b24ec3c430..9258ca5e40e 100644
--- a/lisp/eshell/esh-util.el
+++ b/lisp/eshell/esh-util.el
@@ -1,6 +1,6 @@
;;; esh-util.el --- general utilities -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -23,6 +23,7 @@
;;; Code:
+(require 'seq)
(eval-when-compile (require 'cl-lib))
(defgroup eshell-util nil
@@ -37,46 +38,44 @@
If nil, t will be represented only in the exit code of the function,
and not printed as a string. This causes Lisp functions to behave
similarly to external commands, as far as successful result output."
- :type 'boolean
- :group 'eshell-util)
+ :type 'boolean)
(defcustom eshell-group-file "/etc/group"
"If non-nil, the name of the group file on your system."
- :type '(choice (const :tag "No group file" nil) file)
- :group 'eshell-util)
+ :type '(choice (const :tag "No group file" nil) file))
(defcustom eshell-passwd-file "/etc/passwd"
"If non-nil, the name of the passwd file on your system."
- :type '(choice (const :tag "No passwd file" nil) file)
- :group 'eshell-util)
+ :type '(choice (const :tag "No passwd file" nil) file))
(defcustom eshell-hosts-file "/etc/hosts"
- "The name of the /etc/hosts file."
- :type '(choice (const :tag "No hosts file" nil) file)
- :group 'eshell-util)
+ "The name of the /etc/hosts file.
+Use `pcomplete-hosts-file' instead; this variable is obsolete and
+has no effect."
+ :type '(choice (const :tag "No hosts file" nil) file))
+;; Don't make it into an alias, because it doesn't really work with
+;; custom and risks creating duplicate entries. Just point users to
+;; the other variable, which is less frustrating.
+(make-obsolete-variable 'eshell-hosts-file nil "28.1")
(defcustom eshell-handle-errors t
"If non-nil, Eshell will handle errors itself.
Setting this to nil is offered as an aid to debugging only."
- :type 'boolean
- :group 'eshell-util)
+ :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
- :group 'eshell-util)
+ :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
- :group 'eshell-util)
+ :type 'integer)
(defcustom eshell-tar-regexp
"\\.t\\(ar\\(\\.\\(gz\\|bz2\\|xz\\|Z\\)\\)?\\|gz\\|a[zZ]\\|z2\\)\\'"
"Regular expression used to match tar file names."
:version "24.1" ; added xz
- :type 'regexp
- :group 'eshell-util)
+ :type 'regexp)
(defcustom eshell-convert-numeric-arguments t
"If non-nil, converting arguments of numeric form to Lisp numbers.
@@ -93,16 +92,14 @@ following in your init file:
Any function with the property `eshell-no-numeric-conversions' set to
a non-nil value, will be passed strings, not numbers, even when an
argument matches `eshell-number-regexp'."
- :type 'boolean
- :group 'eshell-util)
+ :type 'boolean)
(defcustom eshell-number-regexp "-?\\([0-9]*\\.\\)?[0-9]+\\(e[-0-9.]+\\)?"
"Regular expression used to match numeric arguments.
If `eshell-convert-numeric-arguments' is non-nil, and an argument
matches this regexp, it will be converted to a Lisp number, using the
function `string-to-number'."
- :type 'regexp
- :group 'eshell-util)
+ :type 'regexp)
(defcustom eshell-ange-ls-uids nil
"List of user/host/id strings, used to determine remote ownership."
@@ -110,8 +107,7 @@ function `string-to-number'."
(string :tag "Hostname")
(repeat (cons :tag "User/UID List"
(string :tag "Username")
- (repeat :tag "UIDs" string)))))
- :group 'eshell-util)
+ (repeat :tag "UIDs" string))))))
;;; Internal Variables:
@@ -127,11 +123,14 @@ function `string-to-number'."
(defvar eshell-user-timestamp nil
"A timestamp of when the user file was read.")
-(defvar eshell-host-names nil
- "A cache the names of frequently accessed hosts.")
+;;; Obsolete variables:
-(defvar eshell-host-timestamp nil
- "A timestamp of when the hosts file was read.")
+(define-obsolete-variable-alias 'eshell-host-names
+ 'pcomplete--host-name-cache "28.1")
+(define-obsolete-variable-alias 'eshell-host-timestamp
+ 'pcomplete--host-name-cache-timestamp "28.1")
+(defvar pcomplete--host-name-cache)
+(defvar pcomplete--host-name-cache-timestamp)
;;; Functions:
@@ -152,85 +151,111 @@ Otherwise, evaluates FORM with no error handling."
(defun eshell-find-delimiter
(open close &optional bound reverse-p backslash-p)
"From point, find the CLOSE delimiter corresponding to OPEN.
-The matching is bounded by BOUND.
-If REVERSE-P is non-nil, process the region backwards.
-If BACKSLASH-P is non-nil, and OPEN and CLOSE are the same character,
-then quoting is done by a backslash, rather than a doubled delimiter."
+The matching is bounded by BOUND. If REVERSE-P is non-nil,
+process the region backwards.
+
+If BACKSLASH-P is non-nil, or OPEN and CLOSE are different
+characters, then a backslash can be used to escape a delimiter
+(or another backslash). Otherwise, the delimiter is escaped by
+doubling it up."
(save-excursion
(let ((depth 1)
(bound (or bound (point-max))))
- (if (if reverse-p
- (eq (char-before) close)
- (eq (char-after) open))
- (forward-char (if reverse-p -1 1)))
+ (when (if reverse-p
+ (eq (char-before) close)
+ (eq (char-after) open))
+ (forward-char (if reverse-p -1 1)))
(while (and (> depth 0)
- (funcall (if reverse-p '> '<) (point) bound))
- (let ((c (if reverse-p (char-before) (char-after))) nc)
+ (funcall (if reverse-p #'> #'<) (point) bound))
+ (let ((c (if reverse-p (char-before) (char-after))))
(cond ((and (not reverse-p)
(or (not (eq open close))
backslash-p)
(eq c ?\\)
- (setq nc (char-after (1+ (point))))
- (or (eq nc open) (eq nc close)))
+ (memq (char-after (1+ (point)))
+ (list open close ?\\)))
(forward-char 1))
((and reverse-p
(or (not (eq open close))
backslash-p)
- (or (eq c open) (eq c close))
- (eq (char-before (1- (point)))
- ?\\))
+ (eq (char-before (1- (point))) ?\\)
+ (memq c (list open close ?\\)))
(forward-char -1))
((eq open close)
- (if (eq c open)
- (if (and (not backslash-p)
- (eq (if reverse-p
- (char-before (1- (point)))
- (char-after (1+ (point)))) open))
- (forward-char (if reverse-p -1 1))
- (setq depth (1- depth)))))
+ (when (eq c open)
+ (if (and (not backslash-p)
+ (eq (if reverse-p
+ (char-before (1- (point)))
+ (char-after (1+ (point))))
+ open))
+ (forward-char (if reverse-p -1 1))
+ (setq depth (1- depth)))))
((= c open)
(setq depth (+ depth (if reverse-p -1 1))))
((= c close)
(setq depth (+ depth (if reverse-p 1 -1))))))
(forward-char (if reverse-p -1 1)))
- (if (= depth 0)
- (if reverse-p (point) (1- (point)))))))
-
-(defun eshell-convert (string)
- "Convert STRING into a more native looking Lisp object."
- (if (not (stringp string))
- string
- (let ((len (length string)))
- (if (= len 0)
- string
- (if (eq (aref string (1- len)) ?\n)
+ (when (= depth 0)
+ (if reverse-p (point) (1- (point)))))))
+
+(defun eshell-convertible-to-number-p (string)
+ "Return non-nil if STRING can be converted to a number.
+If `eshell-convert-numeric-aguments', always return nil."
+ (and eshell-convert-numeric-arguments
+ (string-match
+ (concat "\\`\\s-*" eshell-number-regexp "\\s-*\\'")
+ string)))
+
+(defun eshell-convert-to-number (string)
+ "Try to convert STRING to a number.
+If STRING doesn't look like a number (or
+`eshell-convert-numeric-aguments' is nil), just return STRING
+unchanged."
+ (if (eshell-convertible-to-number-p string)
+ (string-to-number string)
+ string))
+
+(defun eshell-convert (string &optional to-string)
+ "Convert STRING into a more-native Lisp object.
+If TO-STRING is non-nil, always return a single string with
+trailing newlines removed. Otherwise, this behaves as follows:
+
+* Return non-strings as-is.
+
+* Split multiline strings by line.
+
+* If `eshell-convert-numeric-aguments' is non-nil and every line
+ of output looks like a number, convert them to numbers."
+ (cond
+ ((not (stringp string))
+ (if to-string
+ (eshell-stringify string)
+ string))
+ (to-string (string-trim-right string "\n+"))
+ (t (let ((len (length string)))
+ (if (= len 0)
+ string
+ (when (eq (aref string (1- len)) ?\n)
(setq string (substring string 0 (1- len))))
- (if (string-match "\n" string)
- (split-string string "\n")
- (if (and eshell-convert-numeric-arguments
- (string-match
- (concat "\\`\\s-*" eshell-number-regexp
- "\\s-*\\'") string))
- (string-to-number string)
- string))))))
-
-(defun eshell-sublist (l &optional n m)
- "Return from LIST the N to M elements.
-If N or M is nil, it means the end of the list."
- (let ((a (copy-sequence l)))
- (if (and m (consp (nthcdr m a)))
- (setcdr (nthcdr m a) nil))
- (if n
- (setq a (nthcdr n a))
- (setq n (1- (length a))
- a (last a)))
- a))
-
-(defvar eshell-path-env (getenv "PATH")
+ (if (string-search "\n" string)
+ (let ((lines (split-string string "\n")))
+ (if (seq-every-p #'eshell-convertible-to-number-p lines)
+ (mapcar #'string-to-number lines)
+ lines))
+ (eshell-convert-to-number string)))))))
+
+(defvar-local eshell-path-env (getenv "PATH")
"Content of $PATH.
It might be different from \(getenv \"PATH\"), when
`default-directory' points to a remote host.")
-(make-variable-buffer-local 'eshell-path-env)
+
+(defun eshell-get-path ()
+ "Return $PATH as a list.
+Add the current directory on MS-Windows."
+ (eshell-parse-colon-path
+ (if (eshell-under-windows-p)
+ (concat "." path-separator eshell-path-env)
+ eshell-path-env)))
(defun eshell-parse-colon-path (path-env)
"Split string with `parse-colon-path'.
@@ -268,6 +293,7 @@ Prepend remote identification of `default-directory', if any."
(defun eshell-to-flat-string (value)
"Make value a string. If separated by newlines change them to spaces."
+ (declare (obsolete nil "29.1"))
(let ((text (eshell-stringify value)))
(if (string-match "\n+\\'" text)
(setq text (replace-match "" t t text)))
@@ -275,36 +301,7 @@ Prepend remote identification of `default-directory', if any."
(setq text (replace-match " " t t text)))
text))
-(defmacro eshell-for (for-var for-list &rest forms)
- "Iterate through a list."
- (declare (obsolete dolist "24.1"))
- (declare (indent 2))
- `(let ((list-iter ,for-list))
- (while list-iter
- (let ((,for-var (car list-iter)))
- ,@forms)
- (setq list-iter (cdr list-iter)))))
-
-(defun eshell-flatten-list (args)
- "Flatten any lists within ARGS, so that there are no sublists."
- (let ((new-list (list t)))
- (dolist (a args)
- (if (and (listp a)
- (listp (cdr a)))
- (nconc new-list (eshell-flatten-list a))
- (nconc new-list (list a))))
- (cdr new-list)))
-
-(defun eshell-uniqify-list (l)
- "Remove occurring multiples in L. You probably want to sort first."
- (let ((m l))
- (while m
- (while (and (cdr m)
- (string= (car m)
- (cadr m)))
- (setcdr m (cddr m)))
- (setq m (cdr m))))
- l)
+(define-obsolete-function-alias 'eshell-flatten-list #'flatten-tree "27.1")
(defun eshell-stringify (object)
"Convert OBJECT into a string value."
@@ -323,11 +320,11 @@ Prepend remote identification of `default-directory', if any."
(defsubst eshell-stringify-list (args)
"Convert each element of ARGS into a string value."
- (mapcar 'eshell-stringify args))
+ (mapcar #'eshell-stringify args))
(defsubst eshell-flatten-and-stringify (&rest args)
"Flatten and stringify all of the ARGS into a single string."
- (mapconcat 'eshell-stringify (eshell-flatten-list args) " "))
+ (mapconcat #'eshell-stringify (flatten-tree args) " "))
(defsubst eshell-directory-files (regexp &optional directory)
"Return a list of files in the given DIRECTORY matching REGEXP."
@@ -444,7 +441,7 @@ list."
(not (symbol-value timestamp-var))
(time-less-p
(symbol-value timestamp-var)
- (nth 5 (file-attributes file))))
+ (file-attribute-modification-time (file-attributes file))))
(progn
(set result-var (eshell-read-passwd-file file))
(set timestamp-var (current-time))))
@@ -474,58 +471,19 @@ list."
"Return the user id for user NAME."
(car (rassoc name (eshell-read-user-names))))
-(defalias 'eshell-user-name 'user-login-name)
-
-(defun eshell-read-hosts-file (filename)
- "Read in the hosts from FILENAME, default `eshell-hosts-file'."
- (let (hosts)
- (with-temp-buffer
- (insert-file-contents (or filename eshell-hosts-file))
- (goto-char (point-min))
- (while (re-search-forward
- "^\\([^#[:space:]]+\\)\\s-+\\(\\S-+\\)\\(\\s-*\\(\\S-+\\)\\)?" nil t)
- (if (match-string 1)
- (cl-pushnew (match-string 1) hosts :test #'equal))
- (if (match-string 2)
- (cl-pushnew (match-string 2) hosts :test #'equal))
- (if (match-string 4)
- (cl-pushnew (match-string 4) hosts :test #'equal))))
- (sort hosts #'string-lessp)))
-
-(defun eshell-read-hosts (file result-var timestamp-var)
- "Read the contents of /etc/passwd for user names."
- (if (or (not (symbol-value result-var))
- (not (symbol-value timestamp-var))
- (time-less-p
- (symbol-value timestamp-var)
- (nth 5 (file-attributes file))))
- (progn
- (set result-var (eshell-read-hosts-file file))
- (set timestamp-var (current-time))))
- (symbol-value result-var))
-
-(defun eshell-read-host-names ()
- "Read the contents of /etc/hosts for host names."
- (if eshell-hosts-file
- (eshell-read-hosts eshell-hosts-file 'eshell-host-names
- 'eshell-host-timestamp)))
-
-(and (featurep 'xemacs)
- (not (fboundp 'subst-char-in-string))
- (defun subst-char-in-string (fromchar tochar string &optional inplace)
- "Replace FROMCHAR with TOCHAR in STRING each time it occurs.
-Unless optional argument INPLACE is non-nil, return a new string."
- (let ((i (length string))
- (newstr (if inplace string (copy-sequence string))))
- (while (> i 0)
- (setq i (1- i))
- (if (eq (aref newstr i) fromchar)
- (aset newstr i tochar)))
- newstr)))
+(autoload 'pcomplete-read-hosts-file "pcomplete")
+(autoload 'pcomplete-read-hosts "pcomplete")
+(autoload 'pcomplete-read-host-names "pcomplete")
+(define-obsolete-function-alias 'eshell-read-hosts-file
+ #'pcomplete-read-hosts-file "28.1")
+(define-obsolete-function-alias 'eshell-read-hosts
+ #'pcomplete-read-hosts "28.1")
+(define-obsolete-function-alias 'eshell-read-host-names
+ #'pcomplete-read-host-names "28.1")
(defsubst eshell-copy-environment ()
"Return an unrelated copy of `process-environment'."
- (mapcar 'concat process-environment))
+ (mapcar #'concat process-environment))
(defun eshell-subgroups (groupsym)
"Return all of the subgroups of GROUPSYM."
@@ -558,29 +516,8 @@ Unless optional argument INPLACE is non-nil, return a new string."
(substring string 0 sublen)
string)))
-(defvar ange-cache)
-
-;; Partial reimplementation of Emacs's builtin directory-files-and-attributes.
-;; id-format not implemented.
-(and (featurep 'xemacs)
- (not (fboundp 'directory-files-and-attributes))
- (defun directory-files-and-attributes (directory &optional full match nosort _id-format)
- "Return a list of names of files and their attributes in DIRECTORY.
-There are three optional arguments:
-If FULL is non-nil, return absolute file names. Otherwise return names
- that are relative to the specified directory.
-If MATCH is non-nil, mention only file names that match the regexp MATCH.
-If NOSORT is non-nil, the list is not sorted--its order is unpredictable.
- NOSORT is useful if you plan to sort the result yourself."
- (let ((directory (expand-file-name directory)) ange-cache)
- (mapcar
- (function
- (lambda (file)
- (cons file (eshell-file-attributes (expand-file-name file directory)))))
- (directory-files directory full match nosort)))))
-
(defun eshell-directory-files-and-attributes (dir &optional full match nosort id-format)
- "Make sure to use the handler for `directory-file-and-attributes'."
+ "Make sure to use the handler for `directory-files-and-attributes'."
(let* ((dir (expand-file-name dir)))
(if (string-equal (file-remote-p dir 'method) "ftp")
(let ((files (directory-files dir full match nosort)))
@@ -599,13 +536,10 @@ If NOSORT is non-nil, the list is not sorted--its order is unpredictable.
(setq host-users (cdr host-users))
(cdr (assoc user host-users))))))
-;; Add an autoload for parse-time-string
-(if (and (not (fboundp 'parse-time-string))
- (locate-library "parse-time"))
- (autoload 'parse-time-string "parse-time"))
+(autoload 'parse-time-string "parse-time")
(eval-when-compile
- (require 'ange-ftp nil t)) ; ange-ftp-parse-filename
+ (require 'ange-ftp)) ; ange-ftp-parse-filename
(defvar tramp-file-name-structure)
(declare-function ange-ftp-ls "ange-ftp"
@@ -648,17 +582,14 @@ If NOSORT is non-nil, the list is not sorted--its order is unpredictable.
(size (string-to-number (match-string 5)))
(name (ange-ftp-parse-filename))
(mtime
- (if (fboundp 'parse-time-string)
- (let ((moment (parse-time-string
- (match-string 6))))
- (if (nth 0 moment)
- (setcar (nthcdr 5 moment)
- (nth 5 (decode-time)))
- (setcar (nthcdr 0 moment) 0)
- (setcar (nthcdr 1 moment) 0)
- (setcar (nthcdr 2 moment) 0))
- (apply 'encode-time moment))
- (ange-ftp-file-modtime (expand-file-name name dir))))
+ (let ((moment (parse-time-string (match-string 6))))
+ (if (decoded-time-second moment)
+ (setf (decoded-time-year moment)
+ (decoded-time-year (decode-time)))
+ (setf (decoded-time-second moment) 0)
+ (setf (decoded-time-minute moment) 0)
+ (setf (decoded-time-hour moment) 0))
+ (encode-time moment)))
symlink)
(if (string-match "\\(.+\\) -> \\(.+\\)" name)
(setq symlink (match-string 2 name)
@@ -680,20 +611,14 @@ If NOSORT is non-nil, the list is not sorted--its order is unpredictable.
The optional argument ID-FORMAT specifies the preferred uid and
gid format. Valid values are `string' and `integer', defaulting to
`integer'. See `file-attributes'."
- (let* ((file (expand-file-name file))
+ (let* ((expanded-file (expand-file-name file))
entry)
- (if (string-equal (file-remote-p file 'method) "ftp")
- (let ((base (file-name-nondirectory file))
- (dir (file-name-directory file)))
+ (if (string-equal (file-remote-p expanded-file 'method) "ftp")
+ (let ((base (file-name-nondirectory expanded-file))
+ (dir (file-name-directory expanded-file)))
(if (string-equal "" base) (setq base "."))
- (if (boundp 'ange-cache)
- (setq entry (cdr (assoc base (cdr (assoc dir ange-cache))))))
(unless entry
(setq entry (eshell-parse-ange-ls dir))
- (if (boundp 'ange-cache)
- (setq ange-cache
- (cons (cons dir entry)
- ange-cache)))
(if entry
(let ((fentry (assoc base (cdr entry))))
(if fentry
@@ -702,76 +627,99 @@ gid format. Valid values are `string' and `integer', defaulting to
entry)
(file-attributes file id-format))))
-(defalias 'eshell-copy-tree 'copy-tree)
-
(defsubst eshell-processp (proc)
"If the `processp' function does not exist, PROC is not a process."
(and (fboundp 'processp) (processp proc)))
-; (defun eshell-copy-file
-; (file newname &optional ok-if-already-exists keep-date)
-; "Copy FILE to NEWNAME. See docs for `copy-file'."
-; (let (copied)
-; (if (string-match "\\`\\([^:]+\\):\\(.*\\)" file)
-; (let ((front (match-string 1 file))
-; (back (match-string 2 file))
-; buffer)
-; (if (and front (string-match eshell-tar-regexp front)
-; (setq buffer (find-file-noselect front)))
-; (with-current-buffer buffer
-; (goto-char (point-min))
-; (if (re-search-forward (concat " " (regexp-quote back)
-; "$") nil t)
-; (progn
-; (tar-copy (if (file-directory-p newname)
-; (expand-file-name
-; (file-name-nondirectory back) newname)
-; newname))
-; (setq copied t))
-; (error "%s not found in tar file %s" back front))))))
-; (unless copied
-; (copy-file file newname ok-if-already-exists keep-date))))
-
-; (defun eshell-file-attributes (filename)
-; "Return a list of attributes of file FILENAME.
-; See the documentation for `file-attributes'."
-; (let (result)
-; (when (string-match "\\`\\([^:]+\\):\\(.*\\)\\'" filename)
-; (let ((front (match-string 1 filename))
-; (back (match-string 2 filename))
-; buffer)
-; (when (and front (string-match eshell-tar-regexp front)
-; (setq buffer (find-file-noselect front)))
-; (with-current-buffer buffer
-; (goto-char (point-min))
-; (when (re-search-forward (concat " " (regexp-quote back)
-; "\\s-*$") nil t)
-; (let* ((descrip (tar-current-descriptor))
-; (tokens (tar-desc-tokens descrip)))
-; (setq result
-; (list
-; (cond
-; ((eq (tar-header-link-type tokens) 5)
-; t)
-; ((eq (tar-header-link-type tokens) t)
-; (tar-header-link-name tokens)))
-; 1
-; (tar-header-uid tokens)
-; (tar-header-gid tokens)
-; (tar-header-date tokens)
-; (tar-header-date tokens)
-; (tar-header-date tokens)
-; (tar-header-size tokens)
-; (concat
-; (cond
-; ((eq (tar-header-link-type tokens) 5) "d")
-; ((eq (tar-header-link-type tokens) t) "l")
-; (t "-"))
-; (tar-grind-file-mode (tar-header-mode tokens)
-; (make-string 9 ? ) 0))
-; nil nil nil))))))))
-; (or result
-; (file-attributes filename))))
+(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'."
+;; (let (copied)
+;; (if (string-match "\\`\\([^:]+\\):\\(.*\\)" file)
+;; (let ((front (match-string 1 file))
+;; (back (match-string 2 file))
+;; buffer)
+;; (if (and front (string-match eshell-tar-regexp front)
+;; (setq buffer (find-file-noselect front)))
+;; (with-current-buffer buffer
+;; (goto-char (point-min))
+;; (if (re-search-forward (concat " " (regexp-quote back)
+;; "$") nil t)
+;; (progn
+;; (tar-copy (if (file-directory-p newname)
+;; (expand-file-name
+;; (file-name-nondirectory back) newname)
+;; newname))
+;; (setq copied t))
+;; (error "%s not found in tar file %s" back front))))))
+;; (unless copied
+;; (copy-file file newname ok-if-already-exists keep-date))))
+
+;; (defun eshell-file-attributes (filename)
+;; "Return a list of attributes of file FILENAME.
+;; See the documentation for `file-attributes'."
+;; (let (result)
+;; (when (string-match "\\`\\([^:]+\\):\\(.*\\)\\'" filename)
+;; (let ((front (match-string 1 filename))
+;; (back (match-string 2 filename))
+;; buffer)
+;; (when (and front (string-match eshell-tar-regexp front)
+;; (setq buffer (find-file-noselect front)))
+;; (with-current-buffer buffer
+;; (goto-char (point-min))
+;; (when (re-search-forward (concat " " (regexp-quote back)
+;; "\\s-*$") nil t)
+;; (let* ((descrip (tar-current-descriptor))
+;; (tokens (tar-desc-tokens descrip)))
+;; (setq result
+;; (list
+;; (cond
+;; ((eq (tar-header-link-type tokens) 5)
+;; t)
+;; ((eq (tar-header-link-type tokens) t)
+;; (tar-header-link-name tokens)))
+;; 1
+;; (tar-header-uid tokens)
+;; (tar-header-gid tokens)
+;; (tar-header-date tokens)
+;; (tar-header-date tokens)
+;; (tar-header-date tokens)
+;; (tar-header-size tokens)
+;; (file-modes-number-to-symbolic
+;; (logior (tar-header-mode tokens)
+;; (cond
+;; ((eq (tar-header-link-type tokens) 5) 16384)
+;; ((eq (tar-header-link-type tokens) t) 32768))))
+;; nil nil nil))))))))
+;; (or result
+;; (file-attributes filename))))
+
+;; Obsolete.
+
+(define-obsolete-function-alias 'eshell-uniquify-list #'seq-uniq "28.1")
+(define-obsolete-function-alias 'eshell-uniqify-list #'seq-uniq "28.1")
+(define-obsolete-function-alias 'eshell-copy-tree #'copy-tree "28.1")
+(define-obsolete-function-alias 'eshell-user-name #'user-login-name "28.1")
+
+(defun eshell-sublist (l &optional n m)
+ "Return from LIST the N to M elements.
+If N or M is nil, it means the end of the list."
+ (declare (obsolete seq-subseq "28.1"))
+ (seq-subseq l n (1+ m)))
(provide 'esh-util)
diff --git a/lisp/eshell/esh-var.el b/lisp/eshell/esh-var.el
index d038609d957..36e59cd5a41 100644
--- a/lisp/eshell/esh-var.el
+++ b/lisp/eshell/esh-var.el
@@ -1,6 +1,6 @@
;;; esh-var.el --- handling of variables -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
@@ -34,18 +34,14 @@
;;
;; "-" is a valid part of a variable name.
;;
-;; $<MYVAR>-TOO
+;; $\"MYVAR\"-TOO
+;; $'MYVAR'-TOO
;;
;; 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
+;; Returns result of Lisp evaluation. Note: Used alone like this, it
;; is identical to just saying (lisp); but with the variable expansion
;; form, the result may be interpolated a larger string, such as
;; '$(lisp)/other'.
@@ -55,38 +51,40 @@
;; Returns the value of an eshell subcommand. See the note above
;; regarding Lisp evaluations.
;;
-;; $ANYVAR[10]
+;; $<command>
+;;
+;; Evaluates an eshell subcommand, redirecting the output to a
+;; temporary file, and returning the file name.
+;;
+;; $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
@@ -105,16 +103,19 @@
;;; Code:
-(provide 'esh-var)
-
(require 'esh-util)
(require 'esh-cmd)
(require 'esh-opt)
+(require 'esh-module)
+(require 'esh-arg)
+(require 'esh-io)
(require 'pcomplete)
-(require 'env)
(require 'ring)
+(defconst eshell-inside-emacs (format "%s,eshell" emacs-version)
+ "Value for the `INSIDE_EMACS' environment variable.")
+
(defgroup eshell-var nil
"Variable interpolation is introduced whenever the `$' character
appears unquoted in any argument (except when that argument is
@@ -128,107 +129,127 @@ variable value, a subcommand, or even the result of a Lisp form."
(defcustom eshell-var-load-hook nil
"A list of functions to call when loading `eshell-var'."
:version "24.1" ; removed eshell-var-initialize
- :type 'hook
- :group 'eshell-var)
+ :type 'hook)
(defcustom eshell-prefer-lisp-variables nil
"If non-nil, prefer Lisp variables to environment variables."
- :type 'boolean
- :group 'eshell-var)
+ :type 'boolean)
(defcustom eshell-complete-export-definition t
"If non-nil, completing names for `export' shows current definition."
- :type 'boolean
- :group 'eshell-var)
+ :type 'boolean)
(defcustom eshell-modify-global-environment nil
"If non-nil, using `export' changes Emacs's global environment."
- :type 'boolean
- :group 'eshell-var)
+ :type 'boolean)
(defcustom eshell-variable-name-regexp "[A-Za-z0-9_-]+"
"A regexp identifying what constitutes a variable name reference.
Note that this only applies for `$NAME'. If the syntax `$<NAME>' is
used, then NAME can contain any character, including angle brackets,
if they are quoted with a backslash."
- :type 'regexp
- :group 'eshell-var)
+ :type 'regexp)
(defcustom eshell-variable-aliases-list
- '(;; for eshell.el
- ("COLUMNS" (lambda (indices) (window-width)) t)
- ("LINES" (lambda (indices) (window-height)) t)
+ `(;; for eshell.el
+ ("COLUMNS" ,(lambda () (window-body-width nil 'remap)) t t)
+ ("LINES" ,(lambda () (window-body-height nil 'remap)) t t)
+ ("INSIDE_EMACS" eshell-inside-emacs t)
;; for eshell-cmd.el
- ("_" (lambda (indices)
- (if (not indices)
- (car (last eshell-last-arguments))
- (eshell-apply-indices eshell-last-arguments
- indices))))
+ ("_" ,(lambda (indices quoted)
+ (if (not indices)
+ (car (last eshell-last-arguments))
+ (eshell-apply-indices eshell-last-arguments
+ indices quoted))))
("?" eshell-last-command-status)
("$" eshell-last-command-result)
+
+ ;; for em-alias.el and em-script.el
("0" eshell-command-name)
- ("1" (lambda (indices) (nth 0 eshell-command-arguments)))
- ("2" (lambda (indices) (nth 1 eshell-command-arguments)))
- ("3" (lambda (indices) (nth 2 eshell-command-arguments)))
- ("4" (lambda (indices) (nth 3 eshell-command-arguments)))
- ("5" (lambda (indices) (nth 4 eshell-command-arguments)))
- ("6" (lambda (indices) (nth 5 eshell-command-arguments)))
- ("7" (lambda (indices) (nth 6 eshell-command-arguments)))
- ("8" (lambda (indices) (nth 7 eshell-command-arguments)))
- ("9" (lambda (indices) (nth 8 eshell-command-arguments)))
- ("*" (lambda (indices)
- (if (not indices)
- eshell-command-arguments
- (eshell-apply-indices eshell-command-arguments
- indices)))))
+ ("1" ,(lambda () (nth 0 eshell-command-arguments)) nil t)
+ ("2" ,(lambda () (nth 1 eshell-command-arguments)) nil t)
+ ("3" ,(lambda () (nth 2 eshell-command-arguments)) nil t)
+ ("4" ,(lambda () (nth 3 eshell-command-arguments)) nil t)
+ ("5" ,(lambda () (nth 4 eshell-command-arguments)) nil t)
+ ("6" ,(lambda () (nth 5 eshell-command-arguments)) nil t)
+ ("7" ,(lambda () (nth 6 eshell-command-arguments)) nil t)
+ ("8" ,(lambda () (nth 7 eshell-command-arguments)) nil t)
+ ("9" ,(lambda () (nth 8 eshell-command-arguments)) nil t)
+ ("*" eshell-command-arguments))
"This list provides aliasing for variable references.
-It is very similar in concept to what `eshell-user-aliases-list' does
-for commands. Each member of this defines the name of a command,
-and the Lisp value to return for that variable if it is accessed
-via the syntax `$NAME'.
-
-If the value is a function, that function will be called with two
-arguments: the list of the indices that was used in the reference, and
-whether the user is requesting the length of the ultimate element.
-For example, a reference of `$NAME[10][20]' would result in the
-function for alias `NAME' being called (assuming it were aliased to a
-function), and the arguments passed to this function would be the list
-'(10 20)', and nil."
+Each member is of the following form:
+
+ (NAME VALUE [COPY-TO-ENVIRONMENT] [SIMPLE-FUNCTION])
+
+NAME defines the name of the variable, VALUE is a Lisp value used to
+compute the string value that will be returned when the variable is
+accessed via the syntax `$NAME'.
+
+If VALUE is a function, its behavior depends on the value of
+SIMPLE-FUNCTION. If SIMPLE-FUNCTION is nil, call VALUE with two
+arguments: the list of the indices that were used in the reference,
+and either t or nil depending on whether or not the variable was
+quoted with double quotes. For example, if `NAME' were aliased
+to a function, a reference of `$NAME[10][20]' would result in that
+function being called with the arguments `((\"10\") (\"20\"))' and
+nil.
+If SIMPLE-FUNCTION is non-nil, call the function with no arguments
+and then pass its return value to `eshell-apply-indices'.
+
+If VALUE is a string, return the value for the variable with that
+name in the current environment. If no variable with that name exists
+in the environment, but if a symbol with that same name exists and has
+a value bound to it, return that symbol's value instead. You can
+prefer symbol values over environment values by setting the value
+of `eshell-prefer-lisp-variables' to t.
+
+If VALUE is a symbol, return the value bound to it.
+
+If VALUE has any other type, signal an error.
+
+Additionally, if COPY-TO-ENVIRONMENT is non-nil, the alias should be
+copied (a.k.a. \"exported\") 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))))
- :group 'eshell-var)
+ (const :tag "Use only in Eshell" nil))
+ (choice (const :tag "Call without argument" t)
+ (const :tag "Call with 2 arguments" nil))))
+ :risky t)
-(put 'eshell-variable-aliases-list 'risky-local-variable t)
+(defvar-keymap eshell-var-mode-map
+ "C-c M-v" #'eshell-insert-envvar)
;;; Functions:
-(defun eshell-var-initialize ()
+(define-minor-mode eshell-var-mode
+ "Minor mode for the esh-var module.
+
+\\{eshell-var-mode-map}"
+ :keymap eshell-var-mode-map)
+
+(defun eshell-var-initialize () ;Called from `eshell-mode' via intern-soft!
"Initialize the variable handle code."
;; Break the association with our parent's environment. Otherwise,
;; changing a variable will affect all of Emacs.
(unless eshell-modify-global-environment
- (set (make-local-variable 'process-environment)
- (eshell-copy-environment)))
-
- (define-key eshell-command-map [(meta ?v)] 'eshell-insert-envvar)
+ (setq-local process-environment (eshell-copy-environment)))
- (set (make-local-variable 'eshell-special-chars-inside-quoting)
+ (setq-local eshell-special-chars-inside-quoting
(append eshell-special-chars-inside-quoting '(?$)))
- (set (make-local-variable 'eshell-special-chars-outside-quoting)
+ (setq-local eshell-special-chars-outside-quoting
(append eshell-special-chars-outside-quoting '(?$)))
- (add-hook 'eshell-parse-argument-hook 'eshell-interpolate-variable t t)
+ (add-hook 'eshell-parse-argument-hook #'eshell-interpolate-variable t t)
(add-hook 'eshell-prepare-command-hook
- 'eshell-handle-local-variables nil t)
+ #'eshell-handle-local-variables nil t)
(when (eshell-using-module 'eshell-cmpl)
(add-hook 'pcomplete-try-first-hook
- 'eshell-complete-variable-reference nil t)
+ #'eshell-complete-variable-reference nil t)
(add-hook 'pcomplete-try-first-hook
- 'eshell-complete-variable-assignment nil t)))
+ #'eshell-complete-variable-assignment nil t)))
(defun eshell-handle-local-variables ()
"Allow for the syntax `VAR=val <command> <args>'."
@@ -340,9 +361,11 @@ This function is explicit for adding to `eshell-parse-argument-hook'."
(defun pcomplete/eshell-mode/setq ()
"Completion function for Eshell's `setq'."
(while (and (pcomplete-here (all-completions pcomplete-stub
- obarray 'boundp))
+ obarray #'boundp))
(pcomplete-here))))
+;; FIXME the real "env" command does more than this, it runs a program
+;; in a modified environment.
(defun eshell/env (&rest args)
"Implementation of `env' in Lisp."
(eshell-init-print-buffer)
@@ -363,9 +386,8 @@ This function is explicit for adding to `eshell-parse-argument-hook'."
(defun eshell-envvar-names (&optional environment)
"Return a list of currently visible environment variable names."
- (mapcar (function
- (lambda (x)
- (substring x 0 (string-match "=" x))))
+ (mapcar (lambda (x)
+ (substring x 0 (string-search "=" x)))
(or environment process-environment)))
(defun eshell-environment-variables ()
@@ -391,44 +413,63 @@ process any indices that come after the variable reference."
(let* ((get-len (when (eq (char-after) ?#)
(forward-char) t))
value indices)
- (setq value (eshell-parse-variable-ref)
+ (setq value (eshell-parse-variable-ref get-len)
indices (and (not (eobp))
(eq (char-after) ?\[)
(eshell-parse-indices))
- value `(let ((indices ',indices)) ,value))
- (if get-len
- `(length ,value)
- value)))
-
-(defun eshell-parse-variable-ref ()
+ ;; This is an expression that will be evaluated by `eshell-do-eval',
+ ;; which only support let-binding of dynamically-scoped vars
+ value `(let ((indices (eshell-eval-indices ',indices))) ,value))
+ (when get-len
+ (setq value `(length ,value)))
+ (when eshell-current-quoted
+ (setq value `(eshell-stringify ,value)))
+ value))
+
+(defun eshell-parse-variable-ref (&optional modifier-p)
"Eval a variable reference.
Returns a Lisp form which, if evaluated, will return the value of the
variable.
-Possible options are:
+If MODIFIER-P is non-nil, the value of the variable will be
+modified by some function. If MODIFIER-P is nil, the value will be
+used as-is; this allows optimization of some kinds of variable
+references.
+
+Possible variable references are:
NAME an environment or Lisp variable value
- <LONG-NAME> disambiguates the length of the name
+ \"LONG-NAME\" disambiguates the length of the name
+ `LONG-NAME' as above
{COMMAND} result of command is variable's value
- (LISP-FORM) result of Lisp form is variable's value"
+ (LISP-FORM) result of Lisp form is variable's value
+ <COMMAND> write the output of command to a temporary file;
+ result is the file name"
(cond
((eq (char-after) ?{)
(let ((end (eshell-find-delimiter ?\{ ?\})))
(if (not end)
(throw 'eshell-incomplete ?\{)
+ (forward-char)
(prog1
- (list 'eshell-convert
- (list 'eshell-command-to-value
- (list 'eshell-as-subcommand
- (eshell-parse-command
- (cons (1+ (point)) end)))))
+ `(eshell-apply-indices
+ (eshell-convert
+ (eshell-command-to-value
+ (eshell-as-subcommand
+ ,(let ((subcmd (or (eshell-unescape-inner-double-quote end)
+ (cons (point) end)))
+ (eshell-current-quoted nil))
+ (eshell-parse-command subcmd))))
+ ;; If this is a simple double-quoted form like
+ ;; "${COMMAND}" (i.e. no indices after the subcommand
+ ;; and no `#' modifier before), ensure we convert to a
+ ;; single string. This avoids unnecessary work
+ ;; (e.g. splitting the output by lines) when it would
+ ;; just be joined back together afterwards.
+ ,(when (and (not modifier-p) eshell-current-quoted)
+ '(not indices)))
+ indices ,eshell-current-quoted)
(goto-char (1+ end))))))
- ((memq (char-after) '(?\' ?\"))
- (let ((name (if (eq (char-after) ?\')
- (eshell-parse-literal-quote)
- (eshell-parse-double-quote))))
- (if name
- (list 'eshell-get-variable (eval name) 'indices))))
((eq (char-after) ?\<)
(let ((end (eshell-find-delimiter ?\< ?\>)))
(if (not end)
@@ -437,37 +478,53 @@ Possible options are:
(cmd (concat (buffer-substring (1+ (point)) end)
" > " temp)))
(prog1
- (list
- 'let (list (list 'eshell-current-handles
- (list 'eshell-create-handles temp
- (list 'quote 'overwrite))))
- (list
- 'progn
- (list 'eshell-as-subcommand
- (eshell-parse-command cmd))
- (list 'ignore
- (list 'nconc 'eshell-this-command-hook
- (list 'list
- (list 'function
- (list 'lambda nil
- (list 'delete-file temp))))))
- (list 'quote temp)))
+ `(let ((eshell-current-handles
+ (eshell-create-handles ,temp 'overwrite)))
+ (progn
+ (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
+ ;; by `eshell-do-eval', which requires very
+ ;; particular forms in order to work
+ ;; properly. See bug#54190.
+ (list (function
+ (lambda ()
+ (delete-file ,temp)
+ (when-let ((buffer (get-file-buffer ,temp)))
+ (kill-buffer buffer)))))))
+ (eshell-apply-indices ,temp indices ,eshell-current-quoted)))
(goto-char (1+ end)))))))
((eq (char-after) ?\()
(condition-case nil
- (list 'eshell-command-to-value
- (list 'eshell-lisp-command
- (list 'quote (read (current-buffer)))))
+ `(eshell-apply-indices
+ (eshell-command-to-value
+ (eshell-lisp-command
+ ',(read (or (eshell-unescape-inner-double-quote (point-max))
+ (current-buffer)))))
+ indices ,eshell-current-quoted)
(end-of-file
(throw 'eshell-incomplete ?\())))
+ ((looking-at (rx-to-string
+ `(or "'" ,(if eshell-current-quoted "\\\"" "\""))))
+ (eshell-with-temp-command
+ (or (eshell-unescape-inner-double-quote (point-max))
+ (cons (point) (point-max)))
+ (let ((name (if (eq (char-after) ?\')
+ (eshell-parse-literal-quote)
+ (eshell-parse-double-quote))))
+ (when name
+ `(eshell-get-variable ,(eval name) indices ,eshell-current-quoted)))))
((assoc (char-to-string (char-after))
eshell-variable-aliases-list)
(forward-char)
- (list 'eshell-get-variable
- (char-to-string (char-before)) 'indices))
+ `(eshell-get-variable ,(char-to-string (char-before)) indices
+ ,eshell-current-quoted))
((looking-at eshell-variable-name-regexp)
(prog1
- (list 'eshell-get-variable (match-string 0) 'indices)
+ `(eshell-get-variable ,(match-string 0) indices ,eshell-current-quoted)
(goto-char (match-end 0))))
(t
(error "Invalid variable reference"))))
@@ -475,44 +532,64 @@ Possible options are:
(defvar eshell-glob-function)
(defun eshell-parse-indices ()
- "Parse and return a list of list of indices."
+ "Parse and return a list of index-lists.
+
+For example, \"[0 1][2]\" becomes:
+ ((\"0\" \"1\") (\"2\")."
(let (indices)
(while (eq (char-after) ?\[)
(let ((end (eshell-find-delimiter ?\[ ?\])))
(if (not end)
(throw 'eshell-incomplete ?\[)
(forward-char)
- (let (eshell-glob-function)
- (setq indices (cons (eshell-parse-arguments (point) end)
- indices)))
+ (eshell-with-temp-command (or (eshell-unescape-inner-double-quote end)
+ (cons (point) end))
+ (let (eshell-glob-function (eshell-current-quoted nil))
+ (setq indices (cons (eshell-parse-arguments
+ (point-min) (point-max))
+ indices))))
(goto-char (1+ end)))))
(nreverse indices)))
-(defun eshell-get-variable (name &optional indices)
- "Get the value for the variable NAME."
- (let* ((alias (assoc name eshell-variable-aliases-list))
- (var (if alias
- (cadr alias)
- name)))
- (if (and alias (functionp var))
- (funcall var indices)
- (eshell-apply-indices
- (cond
- ((stringp var)
- (let ((sym (intern-soft var)))
- (if (and sym (boundp sym)
- (or eshell-prefer-lisp-variables
- (memq sym eshell--local-vars) ; bug#15372
- (not (getenv var))))
- (symbol-value sym)
- (getenv var))))
- ((symbolp var)
- (symbol-value var))
- (t
- (error "Unknown variable `%s'" (eshell-stringify var))))
- indices))))
-
-(defun eshell-apply-indices (value 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 quoted)
+ "Get the value for the variable NAME.
+INDICES is a list of index-lists (see `eshell-parse-indices').
+If QUOTED is non-nil, this was invoked inside double-quotes."
+ (if-let ((alias (assoc name eshell-variable-aliases-list)))
+ (let ((target (nth 1 alias)))
+ (cond
+ ((functionp target)
+ (if (nth 3 alias)
+ (eshell-apply-indices (funcall target) indices quoted)
+ (condition-case nil
+ (funcall target indices quoted)
+ (wrong-number-of-arguments
+ (display-warning
+ :warning (concat "Function for `eshell-variable-aliases-list' "
+ "entry should accept two arguments: INDICES "
+ "and QUOTED.'"))
+ (funcall target indices)))))
+ ((symbolp target)
+ (eshell-apply-indices (symbol-value target) indices quoted))
+ (t
+ (eshell-get-variable target indices quoted))))
+ (unless (stringp name)
+ (error "Unknown variable `%s'" (eshell-stringify name)))
+ (eshell-apply-indices
+ (let ((sym (intern-soft name)))
+ (if (and sym (boundp sym)
+ (or eshell-prefer-lisp-variables
+ (memq sym eshell--local-vars) ; bug#15372
+ (not (getenv name))))
+ (symbol-value sym)
+ (getenv name)))
+ indices quoted)))
+
+(defun eshell-apply-indices (value indices &optional quoted)
"Apply to VALUE all of the given INDICES, returning the sub-result.
The format of INDICES is:
@@ -521,12 +598,18 @@ The format of INDICES is:
Each member of INDICES represents a level of nesting. If the first
member of a sublist is not an integer or name, and the value it's
-reference is a string, that will be used as the regexp with which is
-to divide the string into sub-parts. The default is whitespace.
+referencing is a string, that will be used as the regexp with which
+is to divide the string into sub-parts. The default is whitespace.
Otherwise, each INT-OR-NAME refers to an element of the list value.
Integers imply a direct index, and names, an associate lookup using
`assoc'.
+If QUOTED is non-nil, this was invoked inside double-quotes.
+This affects the behavior of splitting strings: without quoting,
+the split values are converted to numbers via
+`eshell-convert-to-number' if possible; with quoting, they're
+left as strings.
+
For example, to retrieve the second element of a user's record in
'/etc/passwd', the variable reference would look like:
@@ -534,16 +617,14 @@ 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)))
- (setq value
- (mapcar 'eshell-convert
- (split-string value separator)))))
+ (let (separator (index (caar indices)))
+ (when (and (stringp index)
+ (not (get-text-property 0 'number index)))
+ (setq separator index
+ refs (cdr refs)))
+ (setq value (split-string value separator))
+ (unless quoted
+ (setq value (mapcar #'eshell-convert-to-number value)))))
(cond
((< (length refs) 0)
(error "Invalid array variable index: %s"
@@ -565,23 +646,24 @@ For example, to retrieve the second element of a user's record in
"Reference VALUE using the given INDEX."
(when (and (stringp index) (get-text-property 0 'number index))
(setq index (string-to-number index)))
- (if (stringp index)
- (cdr (assoc index value))
- (cond
- ((ring-p value)
- (if (> index (ring-length value))
- (error "Index exceeds length of ring")
- (ring-ref value index)))
- ((listp value)
- (if (> index (length value))
- (error "Index exceeds length of list")
- (nth index value)))
- ((vectorp value)
- (if (> index (length value))
- (error "Index exceeds length of vector")
- (aref value index)))
- (t
- (error "Invalid data type for indexing")))))
+ (if (integerp index)
+ (cond
+ ((ring-p value)
+ (if (> index (ring-length value))
+ (error "Index exceeds length of ring")
+ (ring-ref value index)))
+ ((listp value)
+ (if (> index (length value))
+ (error "Index exceeds length of list")
+ (nth index value)))
+ ((vectorp value)
+ (if (> index (length value))
+ (error "Index exceeds length of vector")
+ (aref value index)))
+ (t
+ (error "Invalid data type for indexing")))
+ ;; INDEX is some non-integer value, so treat VALUE as an alist.
+ (cdr (assoc index value))))
;;;_* Variable name completion
@@ -605,14 +687,13 @@ For example, to retrieve the second element of a user's record in
(sort
(append
(mapcar
- (function
- (lambda (varname)
- (let ((value (eshell-get-variable varname)))
- (if (and value
- (stringp value)
- (file-directory-p value))
- (concat varname "/")
- varname))))
+ (lambda (varname)
+ (let ((value (eshell-get-variable varname)))
+ (if (and value
+ (stringp value)
+ (file-directory-p value))
+ (concat varname "/")
+ varname)))
(eshell-envvar-names (eshell-environment-variables)))
(all-completions argname obarray 'boundp)
completions)
@@ -628,4 +709,5 @@ For example, to retrieve the second element of a user's record in
(setq pcomplete-stub (substring arg pos))
(throw 'pcomplete-completions (pcomplete-entries)))))
+(provide 'esh-var)
;;; esh-var.el ends here
diff --git a/lisp/eshell/eshell.el b/lisp/eshell/eshell.el
index f85f0e82b38..e0c927cad41 100644
--- a/lisp/eshell/eshell.el
+++ b/lisp/eshell/eshell.el
@@ -1,6 +1,6 @@
;;; eshell.el --- the Emacs command shell -*- lexical-binding:t -*-
-;; Copyright (C) 1999-2017 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2022 Free Software Foundation, Inc.
;; Author: John Wiegley <johnw@gnu.org>
;; Version: 2.4.2
@@ -33,15 +33,15 @@
;; @ A high degree of configurability
;;
;; @ The ability to have the same shell on every system Emacs has been
-;; ported to. Since Eshell imposes no external requirements, and
+;; ported to. Since Eshell imposes no external requirements, and
;; relies upon only the Lisp functions exposed by Emacs, it is quite
-;; operating system independent. Several of the common UNIX
+;; operating system independent. Several of the common UNIX
;; commands, such as ls, mv, rm, ln, etc., have been implemented in
;; Lisp in order to provide a more consistent work environment.
;;
;; For those who might be using an older version of Eshell, version
-;; 2.1 represents an entirely new, module-based architecture. It
-;; supports most of the features offered by modern shells. Here is a
+;; 2.1 represents an entirely new, module-based architecture. It
+;; supports most of the features offered by modern shells. Here is a
;; brief list of some of its more visible features:
;;
;; @ Command argument completion (tcsh, zsh)
@@ -136,7 +136,7 @@
;; errors, such as 'dri' for `dir'. Since executing non-existent
;; programs is rarely the intention of the user, eshell could prompt
;; for the replacement string, and then record that in a database of
-;; known misspellings. (Note: The typo at the beginning of this
+;; known misspellings. (Note: The typo at the beginning of this
;; paragraph wasn't discovered until two months after I wrote the
;; text; it was not intentional).
;;
@@ -175,7 +175,10 @@
(eval-when-compile
(require 'cl-lib))
(require 'esh-util)
-(require 'esh-mode)
+(require 'esh-module) ;For eshell-using-module
+(require 'esh-proc) ;For eshell-wait-for-process
+(require 'esh-io) ;For eshell-last-command-status
+(require 'esh-cmd)
(defgroup eshell nil
"Command shell implemented entirely in Emacs Lisp.
@@ -191,17 +194,6 @@ shells such as bash, zsh, rc, 4dos."
;; The following user options modify the behavior of Eshell overall.
(defvar eshell-buffer-name)
-(defun eshell-add-to-window-buffer-names ()
- "Add `eshell-buffer-name' to `same-window-buffer-names'."
- (declare (obsolete nil "24.3"))
- (add-to-list 'same-window-buffer-names eshell-buffer-name))
-
-(defun eshell-remove-from-window-buffer-names ()
- "Remove `eshell-buffer-name' from `same-window-buffer-names'."
- (declare (obsolete nil "24.3"))
- (setq same-window-buffer-names
- (delete eshell-buffer-name same-window-buffer-names)))
-
(defcustom eshell-load-hook nil
"A hook run once Eshell has been loaded."
:type 'hook
@@ -213,14 +205,14 @@ shells such as bash, zsh, rc, 4dos."
:group 'eshell)
(defcustom eshell-buffer-name "*eshell*"
- "The basename used for Eshell buffers."
- :type 'string
- :group 'eshell)
+ "The basename used for Eshell buffers.
+This is the default name used when running `eshell'.
-(defcustom eshell-directory-name
- (locate-user-emacs-file "eshell/" ".eshell/")
- "The directory where Eshell control files should be kept."
- :type 'directory
+With a numeric prefix argument to `eshell', the buffer name will
+be the value of this variable followed by the number. For
+example, with the numeric prefix argument 2, the buffer would be
+named \"*eshell*<2>\"."
+ :type 'string
:group 'eshell)
;;;_* Running Eshell
@@ -232,13 +224,20 @@ shells such as bash, zsh, rc, 4dos."
;;;###autoload
(defun eshell (&optional arg)
"Create an interactive Eshell buffer.
-The buffer used for Eshell sessions is determined by the value of
-`eshell-buffer-name'. If there is already an Eshell session active in
-that buffer, Emacs will simply switch to it. Otherwise, a new session
-will begin. A numeric prefix arg (as in `C-u 42 M-x eshell RET')
-switches to the session with that number, creating it if necessary. A
-nonnumeric prefix arg means to create a new session. Returns the
-buffer selected (or created)."
+Start a new Eshell session, or switch to an already active
+session. Return the buffer selected (or created).
+
+With a nonnumeric prefix arg, create a new session.
+
+With a numeric prefix arg (as in `\\[universal-argument] 42 \\[eshell]'), switch
+to the session with that number, or create it if it doesn't
+already exist.
+
+The buffer name used for Eshell sessions is determined by the
+value of `eshell-buffer-name', which see.
+
+Eshell is a shell-like command interpreter. For more
+information on Eshell, see Info node `(eshell)Top'."
(interactive "P")
(cl-assert eshell-buffer-name)
(let ((buf (cond ((numberp arg)
@@ -250,18 +249,23 @@ buffer selected (or created)."
(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))
-(defun eshell-return-exits-minibuffer ()
- (define-key eshell-mode-map [(control ?g)] 'abort-recursive-edit)
- (define-key eshell-mode-map [return] 'exit-minibuffer)
- (define-key eshell-mode-map [(control ?m)] 'exit-minibuffer)
- (define-key eshell-mode-map [(control ?j)] 'exit-minibuffer)
- (define-key eshell-mode-map [(meta return)] 'exit-minibuffer)
- (define-key eshell-mode-map [(meta control ?m)] 'exit-minibuffer))
+(define-minor-mode eshell-command-mode
+ "Minor mode for `eshell-command' input.
+\\{eshell-command-mode-map}"
+ :keymap (let ((map (make-sparse-keymap)))
+ (define-key map [(control ?g)] 'abort-recursive-edit)
+ (define-key map [(control ?m)] 'exit-minibuffer)
+ (define-key map [(control ?j)] 'exit-minibuffer)
+ (define-key map [(meta control ?m)] 'exit-minibuffer)
+ map))
+
+(define-obsolete-function-alias 'eshell-return-exits-minibuffer
+ #'eshell-command-mode "28.1")
(defvar eshell-non-interactive-p nil
"A variable which is non-nil when Eshell is not running interactively.
@@ -275,14 +279,13 @@ non-interactive sessions, such as when using `eshell-command'.")
"Execute the Eshell command string COMMAND.
With prefix ARG, insert output into the current buffer at point."
(interactive)
- (require 'esh-cmd)
(unless arg
(setq arg current-prefix-arg))
(let ((eshell-non-interactive-p t))
;; Enable `eshell-mode' only in this minibuffer.
- (minibuffer-with-setup-hook #'(lambda ()
- (eshell-mode)
- (eshell-return-exits-minibuffer))
+ (minibuffer-with-setup-hook (lambda ()
+ (eshell-mode)
+ (eshell-command-mode +1))
(unless command
(setq command (read-from-minibuffer "Emacs shell command: "))
(if (eshell-using-module 'eshell-hist)
@@ -318,9 +321,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)))
@@ -363,21 +366,13 @@ corresponding to a successful execution."
(let ((result (eshell-do-eval
(list 'eshell-commands
(list 'eshell-command-to-value
- (eshell-parse-command command))) t)))
+ (eshell-parse-command command)))
+ t)))
(cl-assert (eq (car result) 'quote))
(if (and status-var (symbolp status-var))
(set status-var eshell-last-command-status))
(cadr result))))))
-;;;_* Reporting bugs
-;;
-;; If you do encounter a bug, on any system, please report
-;; it -- in addition to any particular oddities in your configuration
-;; -- so that the problem may be corrected for the benefit of others.
-
-;;;###autoload
-(define-obsolete-function-alias 'eshell-report-bug 'report-emacs-bug "23.1")
-
;;; Code:
(defun eshell-unload-all-modules ()
@@ -404,5 +399,4 @@ Emacs."
(run-hooks 'eshell-load-hook)
(provide 'eshell)
-
;;; eshell.el ends here