Subject: 30.0.50; [PATCH] Improve Eshell support for special reference types

From: Jim Porter <[email-masked]>
Date: Tue, 10 Oct 2023 17:47:52 -0700

X-Debbugs-Cc: [email-masked]

(Stefan, I added some fairly-tricky calls to Pcomplete in the second 
patch - see 'eshell-complete-special-reference' - so let me know if you 
have any thoughts on that code.)

These patches do a few things to improve how Eshell's special references 
work (these are ways of typing out special Lisp objects like buffers or 
processes, e.g. "#<buffer FOO>").

First, this resolves an edge case where the arguments to a special 
reference weren't parsed as regular Eshell arguments. That meant that 
you couldn't do things like "#<buffer $some-variable>". This does 
introduce one very minor incompatible edge case though. Previously, to 
refer to a buffer named " foo" (note the leading space), you could type 
"#<buffer  foo>" (note the two spaces). Now, you'd need to do something 
like "#<buffer ' foo'>". I think this is super unlikely to occur in 
practice though, and (in my opinion) makes it a lot more obvious how to 
refer to these buffers in Eshell anyway.

Second, this makes it possible to (relatively) easily add new special 
reference types. In this patch, I also reworked how special refs use 
Pcomplete. This lets us write Pcomplete functions for any special ref 
type, so we can do the usual '(pcomplete-here ...)' magic in them. 
Stefan, if you have any comments on this, I'm all ears. The code works 
in all the cases I can think of, but maybe there are cleaner ways to do 
things.

Finally, I added a new, hopefully-useful special ref type: markers. 
Since Eshell lets you redirect output of commands to a marker, I think 
this would make it easier to create these on-the-fly. It also helps to 
stress-test some of the new code here, since a marker ref can contain a 
nested buffer ref: "#<marker 1 #<buffer my-buffer>>".

From 1975e799da455afa69c875a68be681b319d6d18e Mon Sep 17 00:00:00 2001
From: Jim Porter <[email-masked]>
Date: Tue, 22 Aug 2023 13:13:45 -0700
Subject: [PATCH 1/3] Support arbitrary Eshell arguments inside special
 references

* lisp/eshell/esh-arg.el (eshell-current-argument-plain): New variable.
(eshell-parse-special-reference): Use 'eshell-parse-arguments'.
(eshell-get-buffer): New function.
(eshell-insert-buffer-name): Properly quote the buffer name.

* lisp/eshell/esh-proc.el (eshell-read-process-name): Move to "Special
references" section.
(eshell-insert-process): Properly quote the process name.

* lisp/eshell/em-extpipe.el (eshell-parse-external-pipeline):
* lisp/eshell/esh-io.el (eshell-parse-redirection): Don't do anything
when 'eshell-argument-plain' is non-nil.

* test/lisp/eshell/esh-arg-tests.el
(esh-arg-test/special-reference/quoted)
(esh-arg-test/special-reference/var-expansion): New tests.
(esh-arg-test/special-reference/special): Rename to...
(esh-arg-test/special-reference/special-characters): ... this.

* test/lisp/eshell/em-extpipe-tests.el (em-extpipe-tests--deftest):
Properly quote the buffer name.
(em-extpipe-test-4, em-extpipe-test-7): Use 'eshell-get-buffer'.
---
 lisp/eshell/em-extpipe.el            | 161 ++++++++++++++-------------
 lisp/eshell/esh-arg.el               |  87 ++++++++-------
 lisp/eshell/esh-io.el                |   3 +-
 lisp/eshell/esh-proc.el              |  38 ++++---
 test/lisp/eshell/em-extpipe-tests.el |   8 +-
 test/lisp/eshell/esh-arg-tests.el    |  25 ++++-
 6 files changed, 178 insertions(+), 144 deletions(-)

diff --git a/lisp/eshell/em-extpipe.el b/lisp/eshell/em-extpipe.el
index 5c9a0a85934..0d5c217f5f0 100644
--- a/lisp/eshell/em-extpipe.el
+++ b/lisp/eshell/em-extpipe.el
@@ -118,86 +118,87 @@ eshell-parse-external-pipeline
   ;; 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))))))))))
+  (unless eshell-current-argument-plain
+    (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
diff --git a/lisp/eshell/esh-arg.el b/lisp/eshell/esh-arg.el
index e7b5eef11db..c3d3347e888 100644
--- a/lisp/eshell/esh-arg.el
+++ b/lisp/eshell/esh-arg.el
@@ -49,6 +49,8 @@ eshell-current-modifiers
 (defvar eshell-arg-listified nil)
 (defvar eshell-nested-argument nil)
 (defvar eshell-current-quoted nil)
+(defvar eshell-current-argument-plain nil
+  "If non-nil, the current argument is \"plain\", and not part of a command.")
 (defvar eshell-inside-quote-regexp nil)
 (defvar eshell-outside-quote-regexp nil)
 
@@ -184,11 +186,6 @@ eshell-arg-initialize
     (add-hook 'pcomplete-try-first-hook
               #'eshell-complete-special-reference nil t)))
 
-(defun eshell-insert-buffer-name (buffer-name)
-  "Insert BUFFER-NAME into the current buffer at point."
-  (interactive "BName of buffer: ")
-  (insert-and-inherit "#<buffer " buffer-name ">"))
-
 (defsubst eshell-escape-arg (string)
   "Return STRING with the `escaped' property on it."
   (if (stringp string)
@@ -505,42 +502,6 @@ eshell-unescape-inner-double-quote
         (goto-char bound)
         (apply #'concat (nreverse strings))))))
 
-(defun eshell-parse-special-reference ()
-  "Parse a special syntax reference, of the form `#<args>'.
-
-args           := `type' `whitespace' `arbitrary-args' | `arbitrary-args'
-type           := \"buffer\" or \"process\"
-arbitrary-args := any string of characters.
-
-If the form has no `type', the syntax is parsed as if `type' were
-\"buffer\"."
-  (when (and (not eshell-current-argument)
-             (not eshell-current-quoted)
-             (looking-at (rx "#<" (? (group (or "buffer" "process"))
-                                     space))))
-    (let ((here (point)))
-      (goto-char (match-end 0)) ;; Go to the end of the match.
-      (let ((buffer-p (if (match-beginning 1)
-                          (equal (match-string 1) "buffer")
-                        t)) ; With no type keyword, assume we want a buffer.
-            (end (eshell-find-delimiter ?\< ?\>)))
-        (when (not end)
-          (when (match-beginning 1)
-            (goto-char (match-beginning 1)))
-          (throw 'eshell-incomplete "#<"))
-        (if (eshell-arg-delimiter (1+ end))
-            (prog1
-                (list (if buffer-p #'get-buffer-create #'get-process)
-                      ;; FIXME: We should probably parse this as a
-                      ;; real Eshell argument so that we get the
-                      ;; benefits of quoting, variable-expansion, etc.
-                      (string-trim-right
-                       (replace-regexp-in-string
-                        (rx "\\" (group anychar)) "\\1"
-                        (buffer-substring-no-properties (point) end))))
-              (goto-char (1+ end)))
-          (ignore (goto-char here)))))))
-
 (defun eshell-parse-delimiter ()
   "Parse an argument delimiter, which is essentially a command operator."
   ;; this `eshell-operator' keyword gets parsed out by
@@ -591,7 +552,38 @@ eshell-prepare-splice
     (when splicep
       grouped-args)))
 
-;;;_* Special ref completion
+;;; Special references
+
+(defun eshell-parse-special-reference ()
+  "Parse a special syntax reference, of the form `#<args>'.
+
+args           := `type' `whitespace' `arbitrary-args' | `arbitrary-args'
+type           := \"buffer\" or \"process\"
+arbitrary-args := any number of Eshell arguments
+
+If the form has no `type', the syntax is parsed as if `type' were
+\"buffer\"."
+  (when (and (not eshell-current-argument)
+             (not eshell-current-quoted)
+             (looking-at (rx "#<" (? (group (or "buffer" "process"))
+                                     space))))
+    (let ((here (point)))
+      (goto-char (match-end 0)) ;; Go to the end of the match.
+      (let ((buffer-p (if (match-beginning 1)
+                          (equal (match-string 1) "buffer")
+                        t)) ; With no type keyword, assume we want a buffer.
+            (end (eshell-find-delimiter ?\< ?\>)))
+        (when (not end)
+          (when (match-beginning 1)
+            (goto-char (match-beginning 1)))
+          (throw 'eshell-incomplete "#<"))
+        (if (eshell-arg-delimiter (1+ end))
+            (prog1
+                (cons (if buffer-p #'eshell-get-buffer #'get-process)
+                      (let ((eshell-current-argument-plain t))
+                        (eshell-parse-arguments (point) end)))
+              (goto-char (1+ end)))
+          (ignore (goto-char here)))))))
 
 (defun eshell-complete-special-reference ()
   "If there is a special reference, complete it."
@@ -627,5 +619,16 @@ eshell-complete-special-reference
         (throw 'pcomplete-completions
                (all-completions pcomplete-stub all-results))))))
 
+(defun eshell-get-buffer (buffer-or-name)
+  "Return the buffer specified by BUFFER-OR-NAME, creating a new one if needed.
+This is equivalent to `get-buffer-create', but only accepts a
+single argument."
+  (get-buffer-create buffer-or-name))
+
+(defun eshell-insert-buffer-name (buffer-name)
+  "Insert BUFFER-NAME into the current buffer at point."
+  (interactive "BName of buffer: ")
+  (insert-and-inherit "#<buffer " (eshell-quote-argument buffer-name) ">"))
+
 (provide 'esh-arg)
 ;;; esh-arg.el ends here
diff --git a/lisp/eshell/esh-io.el b/lisp/eshell/esh-io.el
index d0f1e04e925..c29b96dd711 100644
--- a/lisp/eshell/esh-io.el
+++ b/lisp/eshell/esh-io.el
@@ -196,7 +196,8 @@ eshell-io-initialize
 
 (defun eshell-parse-redirection ()
   "Parse an output redirection, such as `2>' or `>&'."
-  (when (not eshell-current-quoted)
+  (unless (or eshell-current-quoted
+              eshell-current-argument-plain)
     (cond
      ;; Copying a handle (e.g. `2>&1').
      ((looking-at (rx (? (group digit))
diff --git a/lisp/eshell/esh-proc.el b/lisp/eshell/esh-proc.el
index 126c7d0f26e..3aca3a9fba1 100644
--- a/lisp/eshell/esh-proc.el
+++ b/lisp/eshell/esh-proc.el
@@ -226,23 +226,6 @@ eshell/kill
 
 (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
-                    (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."
-  (interactive
-   (list (get-process
-	  (eshell-read-process-name "Name of process: "))))
-  (insert-and-inherit "#<process " (process-name process) ">"))
-
 (defsubst eshell-record-process-object (object)
   "Record OBJECT as now running."
   (when (and eshell-subjob-messages
@@ -682,5 +665,26 @@ eshell-quit-process
 ;    ;; `eshell-resume-eval'.
 ;    (run-hook-with-args 'eshell-kill-hook nil "continue")))
 
+;;; Special references
+
+(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
+                    (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."
+  (interactive
+   (list (get-process
+	  (eshell-read-process-name "Name of process: "))))
+  (insert-and-inherit "#<process "
+                      (eshell-quote-argument (process-name process))
+                      ">"))
+
 (provide 'esh-proc)
 ;;; esh-proc.el ends here
diff --git a/test/lisp/eshell/em-extpipe-tests.el b/test/lisp/eshell/em-extpipe-tests.el
index bdffcd9b320..6984ec2de59 100644
--- a/test/lisp/eshell/em-extpipe-tests.el
+++ b/test/lisp/eshell/em-extpipe-tests.el
@@ -55,7 +55,9 @@ em-extpipe-tests--deftest
                              "temp\\([^>]\\|\\'\\)" temp
                              (string-replace
                               "#<buffer temp>"
-                              (concat "#<buffer " (buffer-name temp-buffer) ">")
+                              (format "#<buffer %s>"
+                                      (eshell-quote-argument
+                                       (buffer-name temp-buffer)))
                               input))))
                        ,@body)
                    (when (buffer-name temp-buffer)
@@ -110,7 +112,7 @@ em-extpipe-test-4
    '(progn
       (ignore
        (eshell-set-output-handle 1 'overwrite
-				 (get-buffer-create "temp")))
+				 (eshell-get-buffer "temp")))
       (eshell-named-command "sh"
 			    (list "-c" "echo \"bar\" | rev"))))
   (with-substitute-for-temp
@@ -133,7 +135,7 @@ em-extpipe-test-7
    '(progn
       (ignore
        (eshell-set-output-handle 1 'overwrite
-				 (get-buffer-create "quux")))
+				 (eshell-get-buffer "quux")))
       (ignore
        (eshell-set-output-handle 1 'append
 				 (get-process "other")))
diff --git a/test/lisp/eshell/esh-arg-tests.el b/test/lisp/eshell/esh-arg-tests.el
index c883db3907f..0e07d107562 100644
--- a/test/lisp/eshell/esh-arg-tests.el
+++ b/test/lisp/eshell/esh-arg-tests.el
@@ -118,7 +118,30 @@ esh-arg-test/special-reference/buffer
      (format "echo #<buffer %s>" (buffer-name))
      (current-buffer))))
 
-(ert-deftest esh-arg-test/special-reference/special ()
+(ert-deftest esh-arg-test/special-reference/quoted ()
+  "Test that '#<buffer \"foo bar\">' refers to the buffer \"foo bar\"."
+  (with-temp-buffer
+    (rename-buffer "foo bar" t)
+    (eshell-command-result-equal
+     (format "echo #<buffer \"%s\">" (buffer-name))
+     (current-buffer))
+    (eshell-command-result-equal
+     (format "echo #<buffer '%s'>" (buffer-name))
+     (current-buffer))))
+
+(ert-deftest esh-arg-test/special-reference/var-expansion ()
+  "Test that variable expansion inside special references works."
+  (with-temp-buffer
+    (rename-buffer "my-buffer" t)
+    (let ((eshell-test-value (buffer-name)))
+      (eshell-command-result-equal
+       "echo #<buffer $eshell-test-value>"
+       (current-buffer))
+      (eshell-command-result-equal
+       "echo #<buffer \"$eshell-test-value\">"
+       (current-buffer)))))
+
+(ert-deftest esh-arg-test/special-reference/special-characters ()
   "Test that \"#<...>\" works correctly when escaping special characters."
   (with-temp-buffer
     (rename-buffer "<my buffer>" t)
-- 
2.25.1

From f0e87218ecbf2619d2733372a4f9360f554e3ba0 Mon Sep 17 00:00:00 2001
From: Jim Porter <[email-masked]>
Date: Tue, 22 Aug 2023 18:43:51 -0700
Subject: [PATCH 2/3] Add 'eshell-special-ref-alist' to allow extending Eshell
 special refs

* lisp/eshell/esh-cmd.el (eshell--region-p, eshell-with-temp-command):
Move to...
* lisp/eshell/esh-util.el (eshell--region-p)
(eshell-with-temp-command): ... here.

* lisp/eshell/em-cmpl.el (eshell-complete-parse-arguments): Fix edge
case when 'end' is at beginning of (possibly-narrowed) buffer.

* lisp/eshell/esh-arg.el (eshell-special-ref-alist) New variable...
(eshell-special-ref-default): ... New option...
(eshell--special-ref-function): ... New function...
(eshell-parse-special-reference): ... use them.
(eshell-insert-special-reference): New function.
(eshell-complete-special-reference): Reimplement to use a nested call
to Pcomplete.
(eshell-complete-buffer-ref): New function.

* lisp/eshell/esh-proc.el (eshell-proc-initialize): Add "process"
special ref type here.
(eshell-complete-process-ref): New function.

* doc/misc/eshell.texi (Bugs and ideas): Remove now-implemented idea.
---
 doc/misc/eshell.texi    |   5 --
 lisp/eshell/em-cmpl.el  |   3 +-
 lisp/eshell/esh-arg.el  | 177 ++++++++++++++++++++++++++++++----------
 lisp/eshell/esh-cmd.el  |  43 ----------
 lisp/eshell/esh-proc.el |  13 +++
 lisp/eshell/esh-util.el |  43 ++++++++++
 6 files changed, 190 insertions(+), 94 deletions(-)

diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi
index cc94f610615..b5cc9faeec2 100644
--- a/doc/misc/eshell.texi
+++ b/doc/misc/eshell.texi
@@ -2590,11 +2590,6 @@ Bugs and ideas
 way@dots{}).  If input redirection is added, also update the
 @code{file-name-quote-list}, and the delimiter list.
 
-@item Allow @samp{#<@var{word} @var{arg}>} as a generic syntax
-
-With the handling of @emph{word} specified by an
-@code{eshell-special-alist}.
-
 @item In @code{eshell-eval-using-options}, allow a @code{:complete} tag
 
 It would be used to provide completion rules for that command.  Then the
diff --git a/lisp/eshell/em-cmpl.el b/lisp/eshell/em-cmpl.el
index 61f1237b907..0255da88dbd 100644
--- a/lisp/eshell/em-cmpl.el
+++ b/lisp/eshell/em-cmpl.el
@@ -377,7 +377,8 @@ eshell-complete-parse-arguments
 	         (throw 'pcompleted (elisp-completion-at-point)))
 	        (t
 	         (eshell--pcomplete-insert-tab)))))
-    (when (get-text-property (1- end) 'comment)
+    (when (and (< begin end)
+               (get-text-property (1- end) 'comment))
       (eshell--pcomplete-insert-tab))
     (let ((pos (1- end)))
       (while (>= pos begin)
diff --git a/lisp/eshell/esh-arg.el b/lisp/eshell/esh-arg.el
index c3d3347e888..d5fcabccb14 100644
--- a/lisp/eshell/esh-arg.el
+++ b/lisp/eshell/esh-arg.el
@@ -165,6 +165,39 @@ eshell-parse-argument-hook
   :type 'hook
   :group 'eshell-arg)
 
+(defvar eshell-special-ref-alist
+  '(("buffer"
+     (creation-function   eshell-get-buffer)
+     (insertion-function  eshell-insert-buffer-name)
+     (completion-function eshell-complete-buffer-ref)))
+  "Alist of special reference types for Eshell.
+Each entry is a list of the form (TYPE (KEY VALUE)...).  TYPE is
+the name of the special reference type, and each KEY/VALUE pair
+represents a parameter for the type.  Eshell defines the
+following KEYs:
+
+* `creation-function'
+  A function taking any number of arguments that returns the Lisp
+  object for this special ref type.
+
+* `insertion-function'
+  An interactive function that returns the special reference in
+  string form.  This string should look like \"#<TYPE ARG...>\";
+  Eshell will pass the ARGs to `creation-function'.
+
+* `completion-function'
+  A function using Pcomplete to perform completion on any
+  arguments necessary for creating this special reference type.")
+
+(defcustom eshell-special-ref-default "buffer"
+  "The default type for special references when the type keyword is omitted.
+This should be a key in `eshell-special-ref-alist' (which see).
+Eshell will expand special refs like \"#<ARG...>\" into
+\"#<`eshell-special-ref-default' ARG...>\"."
+  :version "30.1"
+  :type 'string
+  :group 'eshell-arg)
+
 (defvar-keymap eshell-arg-mode-map
   "C-c M-b" #'eshell-insert-buffer-name)
 
@@ -554,70 +587,120 @@ eshell-prepare-splice
 
 ;;; Special references
 
+(defsubst eshell--special-ref-function (type function)
+  "Get the specified FUNCTION for a particular special ref TYPE.
+If TYPE is nil, get the FUNCTION for the `eshell-special-ref-default'."
+  (cadr (assq function (assoc (or type eshell-special-ref-default)
+                              eshell-special-ref-alist))))
+
 (defun eshell-parse-special-reference ()
   "Parse a special syntax reference, of the form `#<args>'.
 
 args           := `type' `whitespace' `arbitrary-args' | `arbitrary-args'
-type           := \"buffer\" or \"process\"
+type           := one of the keys in `eshell-special-ref-alist'
 arbitrary-args := any number of Eshell arguments
 
 If the form has no `type', the syntax is parsed as if `type' were
-\"buffer\"."
-  (when (and (not eshell-current-argument)
-             (not eshell-current-quoted)
-             (looking-at (rx "#<" (? (group (or "buffer" "process"))
-                                     space))))
-    (let ((here (point)))
-      (goto-char (match-end 0)) ;; Go to the end of the match.
-      (let ((buffer-p (if (match-beginning 1)
-                          (equal (match-string 1) "buffer")
-                        t)) ; With no type keyword, assume we want a buffer.
-            (end (eshell-find-delimiter ?\< ?\>)))
-        (when (not end)
+`eshell-special-ref-default'."
+  (let ((here (point))
+        (special-ref-types (mapcar #'car eshell-special-ref-alist)))
+    (when (and (not eshell-current-argument)
+               (not eshell-current-quoted)
+               (looking-at (rx-to-string
+                            `(seq "#<" (? (group (or ,@special-ref-types))
+                                          (+ space)))
+                            t)))
+      (goto-char (match-end 0))         ; Go to the end of the match.
+      (let ((end (eshell-find-delimiter ?\< ?\>))
+            (creation-fun (eshell--special-ref-function
+                           (match-string 1) 'creation-function)))
+        (unless end
           (when (match-beginning 1)
             (goto-char (match-beginning 1)))
           (throw 'eshell-incomplete "#<"))
         (if (eshell-arg-delimiter (1+ end))
             (prog1
-                (cons (if buffer-p #'eshell-get-buffer #'get-process)
+                (cons creation-fun
                       (let ((eshell-current-argument-plain t))
                         (eshell-parse-arguments (point) end)))
               (goto-char (1+ end)))
           (ignore (goto-char here)))))))
 
+(defun eshell-insert-special-reference (type &rest args)
+  "Insert a special reference of the specified TYPE.
+ARGS is a list of arguments to pass to the insertion function for
+TYPE (see `eshell-special-ref-alist')."
+  (interactive
+   (let* ((type (completing-read
+                 (format-prompt "Type" eshell-special-ref-default)
+                 (mapcar #'car eshell-special-ref-alist)
+                 nil 'require-match nil nil eshell-special-ref-default))
+          (insertion-fun (eshell--special-ref-function
+                          type 'insertion-function)))
+     (list :interactive (call-interactively insertion-fun))))
+  (if (eq type :interactive)
+      (car args)
+    (apply (eshell--special-ref-function type 'insertion-function) args)))
+
 (defun eshell-complete-special-reference ()
   "If there is a special reference, complete it."
-  (let ((arg (pcomplete-actual-arg)))
-    (when (string-match
-           (rx string-start
-               "#<" (? (group (or "buffer" "process")) space)
-               (group (* anychar))
-               string-end)
-           arg)
-      (let ((all-results (if (equal (match-string 1 arg) "process")
-                             (mapcar #'process-name (process-list))
-                           (mapcar #'buffer-name (buffer-list))))
-            (saw-type (match-beginning 1)))
-        (unless saw-type
-          ;; Include the special reference types as completion options.
-          (setq all-results (append '("buffer" "process") all-results)))
-        (setq pcomplete-stub (replace-regexp-in-string
-                              (rx "\\" (group anychar)) "\\1"
-                              (substring arg (match-beginning 2))))
-        ;; When finished with completion, add a trailing ">" (unless
-        ;; we just completed the initial "buffer" or "process"
-        ;; keyword).
-        (add-function
-         :before (var pcomplete-exit-function)
-         (lambda (value status)
-           (when (and (eq status 'finished)
-                      (or saw-type
-                          (not (member value '("buffer" "process")))))
-             (if (looking-at ">")
-                 (goto-char (match-end 0))
-               (insert ">")))))
-        (throw 'pcomplete-completions
-               (all-completions pcomplete-stub all-results))))))
+  (when (string-prefix-p "#<" (pcomplete-actual-arg))
+    (let ((special-ref-types (mapcar #'car eshell-special-ref-alist))
+          num-args explicit-type)
+      ;; When finished with completion, add a trailing ">" when
+      ;; appropriate.
+      (add-function
+       :around (var pcomplete-exit-function)
+       (lambda (oldfun value status)
+         (when (eq status 'finished)
+           ;; Don't count the special reference type (e.g. "buffer").
+           (when (or explicit-type
+                     (and (= num-args 1)
+                          (member value special-ref-types)))
+             (setq num-args (1- num-args)))
+           (let ((creation-fun (eshell--special-ref-function
+                                explicit-type 'creation-function)))
+             ;; Check if we already have the maximum number of
+             ;; arguments for this special ref type.  If so, finish
+             ;; the ref with ">".  Otherwise, insert a space and set
+             ;; the completion status to `sole'.
+             (if (eq (cdr (func-arity creation-fun)) num-args)
+                 (if (looking-at ">")
+                     (goto-char (match-end 0))
+                   (insert ">"))
+               (pcomplete-default-exit-function value status)
+               (setq status 'sole))
+             (funcall oldfun value status)))))
+      ;; Parse the arguments to this special reference and call the
+      ;; appropriate completion function.
+      (save-excursion
+        (eshell-with-temp-command (cons (+ 2 (pcomplete-begin)) (point))
+          (goto-char (point-max))
+          (let (pcomplete-args pcomplete-last pcomplete-index pcomplete-begins)
+            (when (let ((eshell-current-argument-plain t))
+                    (pcomplete-parse-arguments
+                     pcomplete-expand-before-complete))
+              (setq num-args (length pcomplete-args))
+              (if (= pcomplete-index pcomplete-last)
+                  ;; Call the default special ref completion function,
+                  ;; and also add the known special ref types as
+                  ;; possible completions.
+                  (throw 'pcomplete-completions
+                         (nconc
+                          (mapcar #'car eshell-special-ref-alist)
+                          (catch 'pcomplete-completions
+                            (funcall (eshell--special-ref-function
+                                      nil 'completion-function)))))
+                ;; Get the special ref type and call its completion
+                ;; function.
+                (let ((first (pcomplete-arg 'first)))
+                  (when (member first special-ref-types)
+                    ;; "Complete" the ref type (which we already
+                    ;; completed above).
+                    (pcomplete-here)
+                    (setq explicit-type first)))
+                (funcall (eshell--special-ref-function
+                          explicit-type 'completion-function))))))))))
 
 (defun eshell-get-buffer (buffer-or-name)
   "Return the buffer specified by BUFFER-OR-NAME, creating a new one if needed.
@@ -630,5 +713,9 @@ eshell-insert-buffer-name
   (interactive "BName of buffer: ")
   (insert-and-inherit "#<buffer " (eshell-quote-argument buffer-name) ">"))
 
+(defun eshell-complete-buffer-ref ()
+  "Perform completion for buffer references."
+  (pcomplete-here (mapcar #'buffer-name (buffer-list))))
+
 (provide 'esh-arg)
 ;;; esh-arg.el ends here
diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el
index 990d2ca1122..ecd947774ee 100644
--- a/lisp/eshell/esh-cmd.el
+++ b/lisp/eshell/esh-cmd.el
@@ -393,49 +393,6 @@ eshell-commands-for-process
 
 ;; Command parsing
 
-(defsubst eshell--region-p (object)
-  "Return non-nil if OBJECT is a pair of numbers or markers."
-  (and (consp object)
-       (number-or-marker-p (car object))
-       (number-or-marker-p (cdr object))))
-
-(defmacro eshell-with-temp-command (command &rest body)
-  "Temporarily insert COMMAND into the buffer and execute the forms in BODY.
-
-COMMAND can be a string to insert, a cons cell (START . END)
-specifying a region in the current buffer, or (:file . FILENAME)
-to temporarily insert the contents of FILENAME.
-
-Before executing BODY, narrow the buffer to the text for COMMAND
-and and set point to the beginning of the narrowed region.
-
-The value returned is the last form in BODY."
-  (declare (indent 1))
-  (let ((command-sym (make-symbol "command"))
-        (begin-sym (make-symbol "begin"))
-        (end-sym (make-symbol "end")))
-    `(let ((,command-sym ,command))
-       (if (eshell--region-p ,command-sym)
-           (save-restriction
-             (narrow-to-region (car ,command-sym) (cdr ,command-sym))
-             (goto-char (car ,command-sym))
-             ,@body)
-         ;; 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-sym (point)) ,end-sym)
-           (with-silent-modifications
-             (if (stringp ,command-sym)
-                 (insert ,command-sym)
-               (forward-char (cadr (insert-file-contents (cdr ,command-sym)))))
-             (setq ,end-sym (point))
-             (unwind-protect
-                 (save-restriction
-                   (narrow-to-region ,begin-sym ,end-sym)
-                   (goto-char ,begin-sym)
-                   ,@body)
-               (delete-region ,begin-sym ,end-sym))))))))
-
 (defun eshell-parse-command (command &optional args toplevel)
   "Parse the COMMAND, adding ARGS if given.
 COMMAND can be a string, a cons cell (START . END) demarcating a
diff --git a/lisp/eshell/esh-proc.el b/lisp/eshell/esh-proc.el
index 3aca3a9fba1..31585697a35 100644
--- a/lisp/eshell/esh-proc.el
+++ b/lisp/eshell/esh-proc.el
@@ -23,6 +23,7 @@
 
 ;;; Code:
 
+(require 'esh-arg)
 (require 'esh-io)
 (require 'esh-util)
 
@@ -148,6 +149,14 @@ eshell-proc-mode
 (defun eshell-proc-initialize ()    ;Called from `eshell-mode' via intern-soft!
   "Initialize the process handling code."
   (make-local-variable 'eshell-process-list)
+  (setq-local eshell-special-ref-alist
+              (cons
+               `("process"
+                 (creation-function   get-process)
+                 (insertion-function  eshell-insert-process)
+                 (completion-function eshell-complete-process-ref))
+               eshell-special-ref-alist))
+
   (eshell-proc-mode))
 
 (defun eshell-reset-after-proc (status)
@@ -686,5 +695,9 @@ eshell-insert-process
                       (eshell-quote-argument (process-name process))
                       ">"))
 
+(defun eshell-complete-process-ref ()
+  "Perform completion for process references."
+  (pcomplete-here (mapcar #'process-name (process-list))))
+
 (provide 'esh-proc)
 ;;; esh-proc.el ends here
diff --git a/lisp/eshell/esh-util.el b/lisp/eshell/esh-util.el
index 4c251a29269..2de4c2c1a53 100644
--- a/lisp/eshell/esh-util.el
+++ b/lisp/eshell/esh-util.el
@@ -234,6 +234,49 @@ eshell--mark-yanked-as-output
                 (eshell--mark-as-output start1 end1)))))
     (add-hook 'after-change-functions hook nil t)))
 
+(defsubst eshell--region-p (object)
+  "Return non-nil if OBJECT is a pair of numbers or markers."
+  (and (consp object)
+       (number-or-marker-p (car object))
+       (number-or-marker-p (cdr object))))
+
+(defmacro eshell-with-temp-command (command &rest body)
+  "Temporarily insert COMMAND into the buffer and execute the forms in BODY.
+
+COMMAND can be a string to insert, a cons cell (START . END)
+specifying a region in the current buffer, or (:file . FILENAME)
+to temporarily insert the contents of FILENAME.
+
+Before executing BODY, narrow the buffer to the text for COMMAND
+and and set point to the beginning of the narrowed region.
+
+The value returned is the last form in BODY."
+  (declare (indent 1))
+  (let ((command-sym (make-symbol "command"))
+        (begin-sym (make-symbol "begin"))
+        (end-sym (make-symbol "end")))
+    `(let ((,command-sym ,command))
+       (if (eshell--region-p ,command-sym)
+           (save-restriction
+             (narrow-to-region (car ,command-sym) (cdr ,command-sym))
+             (goto-char (car ,command-sym))
+             ,@body)
+         ;; 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-sym (point)) ,end-sym)
+           (with-silent-modifications
+             (if (stringp ,command-sym)
+                 (insert ,command-sym)
+               (forward-char (cadr (insert-file-contents (cdr ,command-sym)))))
+             (setq ,end-sym (point))
+             (unwind-protect
+                 (save-restriction
+                   (narrow-to-region ,begin-sym ,end-sym)
+                   (goto-char ,begin-sym)
+                   ,@body)
+               (delete-region ,begin-sym ,end-sym))))))))
+
 (defun eshell-find-delimiter
   (open close &optional bound reverse-p backslash-p)
   "From point, find the CLOSE delimiter corresponding to OPEN.
-- 
2.25.1

From c12f9a2759fde69e53d9c1c3444957d9e2599818 Mon Sep 17 00:00:00 2001
From: Jim Porter <[email-masked]>
Date: Mon, 9 Oct 2023 20:25:28 -0700
Subject: [PATCH 3/3] Add a new Eshell special reference type for markers

* lisp/eshell/esh-arg.el (eshell-get-marker, eshell-insert-marker)
(eshell-complete-marker-ref): New functions...
(eshell-special-ref-alist): ... Add them to the new "marker" entry.

* test/lisp/eshell/esh-arg-tests.el
(esh-arg-test/special-reference/marker)
(esh-arg-test/special-reference/nested)
(esh-arg-test/special-reference/lisp-form):
* test/lisp/eshell/em-cmpl-tests.el
(em-cmpl-test/special-ref-completion/type)
(em-cmpl-test/special-ref-completion/marker): New tests.

* doc/misc/eshell.texi (Arguments): Document the new special ref type.

* etc/NEWS: Announce this change.
---
 doc/misc/eshell.texi              |  8 +++++++
 etc/NEWS                          |  8 +++++++
 lisp/eshell/esh-arg.el            | 27 +++++++++++++++++++++-
 test/lisp/eshell/em-cmpl-tests.el | 32 ++++++++++++++++++++++++--
 test/lisp/eshell/esh-arg-tests.el | 38 +++++++++++++++++++++++++++++++
 5 files changed, 110 insertions(+), 3 deletions(-)

diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi
index b5cc9faeec2..e8aa8cdc6a3 100644
--- a/doc/misc/eshell.texi
+++ b/doc/misc/eshell.texi
@@ -400,6 +400,14 @@ Arguments
 @samp{$(get-buffer-create "@var{name}")} (@pxref{Creating Buffers, , ,
 elisp, The Emacs Lisp Reference Manual}).
 
+@item #<marker @var{position} @var{buffer-or-name}>
+Return a marker at @var{position} in the buffer @var{buffer-or-name}.
+@var{buffer-or-name} can either be a string naming a buffer or an
+actual buffer object.  This is roughly equivalent to creating a new
+marker and calling @samp{$(set-marker marker @var{position}
+@var{buffer-or-name})} (@pxref{Moving Markers, , , elisp, The Emacs
+Lisp Reference Manual}).
+
 @item #<process @var{name}>
 Return the process named @var{name}.  This is equivalent to
 @samp{$(get-process "@var{name}")} (@pxref{Process Information, , ,
diff --git a/etc/NEWS b/etc/NEWS
index ececb7e8459..1b23b7d29c2 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -384,6 +384,14 @@ appropriate, but still allow piping the output elsewhere if desired.
 For more information, see the "(eshell) Built-ins" node in the Eshell
 manual.
 
++++
+*** New special reference type '#<marker POSITION BUFFER>'.
+This special reference type returns a marker at 'POSITION' in
+'BUFFER'.  You can insert it by typing or using the new interactive
+command 'eshell-insert-marker'.  You can also insert markers of any
+type with the new command 'eshell-insert-special-reference'.  See the
+"(eshell) Arguments" node in the Eshell manual for more details.
+
 +++
 *** New splice operator for Eshell dollar expansions.
 Dollar expansions in Eshell now let you splice the elements of the
diff --git a/lisp/eshell/esh-arg.el b/lisp/eshell/esh-arg.el
index d5fcabccb14..2bdfdff8a3a 100644
--- a/lisp/eshell/esh-arg.el
+++ b/lisp/eshell/esh-arg.el
@@ -169,7 +169,11 @@ eshell-special-ref-alist
   '(("buffer"
      (creation-function   eshell-get-buffer)
      (insertion-function  eshell-insert-buffer-name)
-     (completion-function eshell-complete-buffer-ref)))
+     (completion-function eshell-complete-buffer-ref))
+    ("marker"
+     (creation-function   eshell-get-marker)
+     (insertion-function  eshell-insert-marker)
+     (completion-function eshell-complete-marker-ref)))
   "Alist of special reference types for Eshell.
 Each entry is a list of the form (TYPE (KEY VALUE)...).  TYPE is
 the name of the special reference type, and each KEY/VALUE pair
@@ -717,5 +721,26 @@ eshell-complete-buffer-ref
   "Perform completion for buffer references."
   (pcomplete-here (mapcar #'buffer-name (buffer-list))))
 
+(defun eshell-get-marker (position buffer-or-name)
+  "Return the marker for character number POSITION in BUFFER-OR-NAME.
+BUFFER-OR-NAME can be a buffer or a string.  If a string and a
+live buffer with that name exists, use that buffer.  If no such
+buffer exists, create a new buffer with that name and use it."
+  (let ((marker (make-marker)))
+    (set-marker marker (string-to-number position)
+                (get-buffer-create buffer-or-name))))
+
+(defun eshell-insert-marker (position buffer-name)
+  "Insert a marker into the current buffer at point.
+This marker will point to POSITION in BUFFER-NAME."
+  (interactive "nPosition: \nBName of buffer: ")
+  (insert-and-inherit "#<marker " (number-to-string position) " "
+                      (eshell-quote-argument buffer-name) ">"))
+
+(defun eshell-complete-marker-ref ()
+  "Perform completion for marker references."
+  (pcomplete-here)
+  (pcomplete-here (mapcar #'buffer-name (buffer-list))))
+
 (provide 'esh-arg)
 ;;; esh-arg.el ends here
diff --git a/test/lisp/eshell/em-cmpl-tests.el b/test/lisp/eshell/em-cmpl-tests.el
index 29a41625d5e..dd3c338ac54 100644
--- a/test/lisp/eshell/em-cmpl-tests.el
+++ b/test/lisp/eshell/em-cmpl-tests.el
@@ -243,14 +243,17 @@ em-cmpl-test/lisp-function-completion
                   "echo $(eshell/echo"))))
 
 (ert-deftest em-cmpl-test/special-ref-completion/type ()
-  "Test completion of the start of special references like \"#<buffer\".
+  "Test completion of the start of special reference types like \"#<buffer\".
 See <lisp/eshell/esh-arg.el>."
   (with-temp-eshell
    (should (equal (eshell-insert-and-complete "echo hi > #<buf")
                   "echo hi > #<buffer ")))
   (with-temp-eshell
    (should (equal (eshell-insert-and-complete "echo hi > #<proc")
-                  "echo hi > #<process "))))
+                  "echo hi > #<process ")))
+  (with-temp-eshell
+   (should (equal (eshell-insert-and-complete "echo hi > #<mark")
+                  "echo hi > #<marker "))))
 
 (ert-deftest em-cmpl-test/special-ref-completion/implicit-buffer ()
   "Test completion of special references like \"#<buf>\".
@@ -282,6 +285,31 @@ em-cmpl-test/special-ref-completion/buffer
                       (format "echo hi > #<buffer %s> "
                               (string-replace " " "\\ " bufname))))))))
 
+(ert-deftest em-cmpl-test/special-ref-completion/marker ()
+  "Test completion of special references like \"#<marker 1 buf>\".
+See <lisp/eshell/esh-arg.el>."
+  (let (bufname)
+    (with-temp-buffer
+      (setq bufname (rename-buffer "my-buffer" t))
+      ;; Complete the buffer name in various forms.
+      (with-temp-eshell
+       (should (equal (eshell-insert-and-complete
+                       "echo hi > #<marker 1 my-buf")
+                      (format "echo hi > #<marker 1 %s> " bufname))))
+      (with-temp-eshell
+       (should (equal (eshell-insert-and-complete
+                       "echo hi > #<marker 1 #<my-buf")
+                      (format "echo hi > #<marker 1 #<%s>> " bufname))))
+      (with-temp-eshell
+       (should (equal (eshell-insert-and-complete
+                       "echo hi > #<marker 1 #<buffer my-buf")
+                      (format "echo hi > #<marker 1 #<buffer %s>> " bufname))))
+      ;; Partially-complete the "buffer" type name.
+      (with-temp-eshell
+       (should (equal (eshell-insert-and-complete
+                       "echo hi > #<marker 1 #<buf")
+                      "echo hi > #<marker 1 #<buffer "))))))
+
 (ert-deftest em-cmpl-test/variable-ref-completion ()
   "Test completion of variable references like \"$var\".
 See <lisp/eshell/esh-var.el>."
diff --git a/test/lisp/eshell/esh-arg-tests.el b/test/lisp/eshell/esh-arg-tests.el
index 0e07d107562..1eb8e08b883 100644
--- a/test/lisp/eshell/esh-arg-tests.el
+++ b/test/lisp/eshell/esh-arg-tests.el
@@ -118,6 +118,17 @@ esh-arg-test/special-reference/buffer
      (format "echo #<buffer %s>" (buffer-name))
      (current-buffer))))
 
+(ert-deftest esh-arg-test/special-reference/marker ()
+  "Test that \"#<marker N buf>\" refers to a marker in the buffer \"buf\"."
+  (with-temp-buffer
+    (rename-buffer "my-buffer" t)
+    (insert "hello")
+    (let ((marker (make-marker)))
+      (set-marker marker 1 (current-buffer))
+      (eshell-command-result-equal
+       (format "echo #<marker 1 %s>" (buffer-name))
+       marker))))
+
 (ert-deftest esh-arg-test/special-reference/quoted ()
   "Test that '#<buffer \"foo bar\">' refers to the buffer \"foo bar\"."
   (with-temp-buffer
@@ -129,6 +140,20 @@ esh-arg-test/special-reference/quoted
      (format "echo #<buffer '%s'>" (buffer-name))
      (current-buffer))))
 
+(ert-deftest esh-arg-test/special-reference/nested ()
+  "Test that nested special references work correctly."
+  (with-temp-buffer
+    (rename-buffer "my-buffer" t)
+    (insert "hello")
+    (let ((marker (make-marker)))
+      (set-marker marker 1 (current-buffer))
+      (eshell-command-result-equal
+       (format "echo #<marker 1 #<%s>>" (buffer-name))
+       marker)
+      (eshell-command-result-equal
+       (format "echo #<marker 1 #<buffer %s>>" (buffer-name))
+       marker))))
+
 (ert-deftest esh-arg-test/special-reference/var-expansion ()
   "Test that variable expansion inside special references works."
   (with-temp-buffer
@@ -141,6 +166,19 @@ esh-arg-test/special-reference/var-expansion
        "echo #<buffer \"$eshell-test-value\">"
        (current-buffer)))))
 
+(ert-deftest esh-arg-test/special-reference/lisp-form ()
+  "Test that Lisp forms inside special references work."
+  (with-temp-eshell
+   (let ((marker (make-marker))
+         eshell-test-value)
+     (set-marker marker 1 (current-buffer))
+     (eshell-insert-command
+      "setq eshell-test-value #<marker 1 (current-buffer)>")
+     (should (equal eshell-test-value marker))
+     (eshell-insert-command
+      "setq eshell-test-value #<marker 1 #<buffer (buffer-name)>>")
+     (should (equal eshell-test-value marker)))))
+
 (ert-deftest esh-arg-test/special-reference/special-characters ()
   "Test that \"#<...>\" works correctly when escaping special characters."
   (with-temp-buffer
-- 
2.25.1