Subject: bug#66458: closed (Re: bug#66458: 30.0.50; [PATCH] Improve Eshell support for special reference types)

From: [email-masked] (GNU bug Tracking System)
Date: Tue, 24 Oct 2023 19:31:02 +0000

Your bug report

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

which was filed against the emacs package, has been closed.

The explanation is attached below, along with your original report.
If you require more details, please reply to [email-masked].

-- 
66458: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=66458
GNU Bug Tracking System
Contact [email-masked] with problems

Subject: Re: bug#66458: 30.0.50; [PATCH] Improve Eshell support for special reference types
From: Jim Porter <[email-masked]>
To: [email-masked]
Cc: [email-masked]
Date: Tue, 24 Oct 2023 12:30:10 -0700
List-Id: <debbugs-submit.debbugs.gnu.org>

Version: 30.1

On 10/10/2023 5:47 PM, Jim Porter wrote:
> 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>").

Merged to master as 64aa01f60ad. Closing this now.

Subject: 30.0.50; [PATCH] Improve Eshell support for special reference types
From: Jim Porter <[email-masked]>
To: [email-masked]
Date: Tue, 10 Oct 2023 17:47:52 -0700
List-Id: <debbugs-submit.debbugs.gnu.org>

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