(1 2 3)
@end example
-Additionally, many built-in Eshell commands (@pxref{Built-ins}) will
-flatten the arguments they receive, so passing a list as an argument
-will ``spread'' the elements into multiple arguments:
+When calling external commands (and many built-in Eshell commands,
+too) Eshell will flatten the arguments the command receives, so
+passing a list as an argument will ``spread'' the elements into
+multiple arguments:
@example
~ $ printnl (list 1 2) 3
@node Globbing
@section Globbing
-@vindex eshell-glob-case-insensitive
Eshell's globbing syntax is very similar to that of Zsh
(@pxref{Filename Generation, , , zsh, The Z Shell Manual}). Users
coming from Bash can still use Bash-style globbing, as there are no
incompatibilities.
-By default, globs are case sensitive, except on MS-DOS/MS-Windows
+@vindex eshell-glob-case-insensitive
+Globs are case sensitive by default, except on MS-DOS/MS-Windows
systems. You can control this behavior via the
-@code{eshell-glob-case-insensitive} option. You can further customize
-the syntax and behavior of globbing in Eshell via the Customize group
-@code{eshell-glob} (@pxref{Easy Customization, , , emacs, The GNU
-Emacs Manual}).
+@code{eshell-glob-case-insensitive} option.
+
+@vindex eshell-glob-splice-results
+By default, Eshell expands the results of a glob as a sublist into the
+list of arguments. You can change this to splice the results in-place
+by setting @code{eshell-glob-splice-results} to a non-@code{nil}
+value. If you want to splice a glob in-place for just one use, you
+can use a subcommand form like @samp{$@@@{listify @var{my-glob}@}}.
+(Conversely, you can explicitly expand a glob as a sublist via
+@samp{$@{listify @var{my-glob}@}}.)
+
+You can further customize the syntax and behavior of globbing in
+Eshell via the Customize group @code{eshell-glob} (@pxref{Easy
+Customization, , , emacs, The GNU Emacs Manual}).
@table @samp
This would be so that if a Lisp function calls @code{print}, everything
will happen as it should (albeit slowly).
-@item If a globbing pattern returns one match, should it be a list?
-
@item Make sure syntax table is correct in Eshell mode
So that @kbd{M-@key{DEL}} acts in a predictable manner, etc.
information, see the "(eshell) Dollars Expansion" node in the Eshell
manual.
++++
+*** You can now splice Eshell globs in-place into argument lists.
+By setting 'eshell-glob-splice-results' to a non-nil value, Eshell
+will expand glob results in-place as if you had typed each matching
+file name individually. For more information, see the "(eshell)
+Globbing" node in the Eshell manual.
+
+++
*** Eshell now supports negative numbers and ranges for indices.
Now, you can retrieve the last element of a list with '$my-list[-1]'
:type 'hook
:group 'eshell-glob)
+(defcustom eshell-glob-splice-results nil
+ "If non-nil, the results of glob patterns will be spliced in-place.
+When splicing, the resulting command is as though the user typed
+each result individually. Otherwise, the glob results a single
+argument as a list."
+ :version "30.1"
+ :type 'boolean
+ :group 'eshell-glob)
+
(defcustom eshell-glob-include-dot-files nil
"If non-nil, glob patterns will match files beginning with a dot."
:type 'boolean
(defun eshell-no-command-globbing (terms)
"Don't glob the command argument. Reflect this by modifying TERMS."
(ignore
- (when (and (listp (car terms))
- (eq (caar terms) 'eshell-extended-glob))
- (setcar terms (cadr (car terms))))))
+ (pcase (car terms)
+ ((or `(eshell-extended-glob ,term)
+ `(eshell-splice-args (eshell-extended-glob ,term)))
+ (setcar terms term)))))
(defun eshell-add-glob-modifier ()
"Add `eshell-extended-glob' to the argument modifier list."
+ (when eshell-glob-splice-results
+ (add-to-list 'eshell-current-modifiers 'eshell-splice-args t))
(add-to-list 'eshell-current-modifiers 'eshell-extended-glob))
(defun eshell-parse-glob-chars ()
(or (and eshell-glob-matches (sort eshell-glob-matches #'string<))
(if eshell-error-if-no-glob
(error "No matches found: %s" glob)
- glob))))
+ (if eshell-glob-splice-results
+ (list glob)
+ glob)))))
;; FIXME does this really need to abuse eshell-glob-matches, message-shown?
(defun eshell-glob-entries (path globs only-dirs)
(modifiers (eshell-parse-modifiers))
(preds (car modifiers))
(mods (cdr modifiers)))
- (if (or preds mods)
- ;; has to go at the end, which is only natural since
- ;; syntactically it can only occur at the end
- (setq eshell-current-modifiers
- (append
- eshell-current-modifiers
- (list
- (lambda (lst)
- (eshell-apply-modifiers
- lst preds mods modifier-string))))))))
+ (when (or preds mods)
+ ;; Has to go at the end, which is only natural since
+ ;; syntactically it can only occur at the end.
+ (setq eshell-current-modifiers
+ (append
+ eshell-current-modifiers
+ (list
+ (lambda (lst)
+ (eshell-apply-modifiers
+ lst preds mods modifier-string)))))
+ (when (memq 'eshell-splice-args eshell-current-modifiers)
+ ;; If splicing results, ensure that
+ ;; `eshell-splice-args' is the last modifier.
+ ;; Eshell's command parsing can't handle it anywhere
+ ;; else.
+ (setq eshell-current-modifiers
+ (append (delq 'eshell-splice-args
+ eshell-current-modifiers)
+ (list 'eshell-splice-args)))))))
(goto-char (1+ end))
(eshell-finish-arg))))))
(require 'ert)
(require 'em-glob)
+(require 'eshell-tests-helpers
+ (expand-file-name "eshell-tests-helpers"
+ (file-name-directory (or load-file-name
+ default-directory))))
+
+(defvar eshell-prefer-lisp-functions)
+
(defmacro with-fake-files (files &rest body)
"Evaluate BODY forms, pretending that FILES exist on the filesystem.
FILES is a list of file names that should be reported as
;;; Tests:
+(ert-deftest em-glob-test/expand/splice-results ()
+ "Test that globs are spliced into the argument list when
+`eshell-glob-splice-results' is non-nil."
+ (let ((eshell-prefer-lisp-functions t)
+ (eshell-glob-splice-results t))
+ (with-fake-files '("a.el" "b.el" "c.txt")
+ ;; Ensure the default expansion splices the glob.
+ (eshell-command-result-equal "list *.el" '("a.el" "b.el"))
+ (eshell-command-result-equal "list *.txt" '("c.txt"))
+ (eshell-command-result-equal "list *.no" '("*.no")))))
+
+(ert-deftest em-glob-test/expand/no-splice-results ()
+ "Test that globs are treated as lists when
+`eshell-glob-splice-results' is nil."
+ (let ((eshell-prefer-lisp-functions t)
+ (eshell-glob-splice-results nil))
+ (with-fake-files '("a.el" "b.el" "c.txt")
+ ;; Ensure the default expansion splices the glob.
+ (eshell-command-result-equal "list *.el" '(("a.el" "b.el")))
+ (eshell-command-result-equal "list *.txt" '(("c.txt")))
+ ;; The no-matches case is special here: the glob is just the
+ ;; string, not the list of results.
+ (eshell-command-result-equal "list *.no" '("*.no")))))
+
+(ert-deftest em-glob-test/expand/explicitly-splice-results ()
+ "Test explicitly splicing globs works the same no matter the
+value of `eshell-glob-splice-results'."
+ (let ((eshell-prefer-lisp-functions t))
+ (dolist (eshell-glob-splice-results '(nil t))
+ (ert-info ((format "eshell-glob-splice-results: %s"
+ eshell-glob-splice-results))
+ (with-fake-files '("a.el" "b.el" "c.txt")
+ (eshell-command-result-equal "list $@{listify *.el}"
+ '("a.el" "b.el"))
+ (eshell-command-result-equal "list $@{listify *.txt}"
+ '("c.txt"))
+ (eshell-command-result-equal "list $@{listify *.no}"
+ '("*.no")))))))
+
+(ert-deftest em-glob-test/expand/explicitly-listify-results ()
+ "Test explicitly listifying globs works the same no matter the
+value of `eshell-glob-splice-results'."
+ (let ((eshell-prefer-lisp-functions t))
+ (dolist (eshell-glob-splice-results '(nil t))
+ (ert-info ((format "eshell-glob-splice-results: %s"
+ eshell-glob-splice-results))
+ (with-fake-files '("a.el" "b.el" "c.txt")
+ (eshell-command-result-equal "list ${listify *.el}"
+ '(("a.el" "b.el")))
+ (eshell-command-result-equal "list ${listify *.txt}"
+ '(("c.txt")))
+ (eshell-command-result-equal "list ${listify *.no}"
+ '(("*.no"))))))))
+
(ert-deftest em-glob-test/match-any-string ()
"Test that \"*\" pattern matches any string."
(with-fake-files '("a.el" "b.el" "c.txt" "dir/a.el")
(with-fake-files '("foo.el" "bar.el")
(should (equal (eshell-extended-glob "*.txt")
"*.txt"))
+ (let ((eshell-glob-splice-results t))
+ (should (equal (eshell-extended-glob "*.txt")
+ '("*.txt"))))
(let ((eshell-error-if-no-glob t))
(should-error (eshell-extended-glob "*.txt")))))