aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJim Porter2023-08-22 13:13:45 -0700
committerJim Porter2023-10-24 11:36:27 -0700
commit1c2cb9cd6192e97a29fbe338fd1a639f6dfae2d2 (patch)
tree8ffa80dc0de1013094e9e33e052693b7a19d83a7
parentf7d88f4a0478d89f70243456af8c4d4817d6b251 (diff)
downloademacs-1c2cb9cd6192e97a29fbe338fd1a639f6dfae2d2.tar.gz
emacs-1c2cb9cd6192e97a29fbe338fd1a639f6dfae2d2.zip
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'.
-rw-r--r--lisp/eshell/em-extpipe.el161
-rw-r--r--lisp/eshell/esh-arg.el87
-rw-r--r--lisp/eshell/esh-io.el3
-rw-r--r--lisp/eshell/esh-proc.el38
-rw-r--r--test/lisp/eshell/em-extpipe-tests.el8
-rw-r--r--test/lisp/eshell/esh-arg-tests.el25
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 @@ as though it were Eshell syntax."
118 ;; other members of `eshell-parse-argument-hook'. We must avoid 118 ;; other members of `eshell-parse-argument-hook'. We must avoid
119 ;; misinterpreting a quoted `*|', `*<' or `*>' as indicating an 119 ;; misinterpreting a quoted `*|', `*<' or `*>' as indicating an
120 ;; external pipeline, hence the structure of the loop in `findbeg1'. 120 ;; external pipeline, hence the structure of the loop in `findbeg1'.
121 (cl-flet 121 (unless eshell-current-argument-plain
122 ((findbeg1 (pat &optional go (bound (point-max))) 122 (cl-flet
123 (let* ((start (point)) 123 ((findbeg1 (pat &optional go (bound (point-max)))
124 (result 124 (let* ((start (point))
125 (catch 'found 125 (result
126 (while (> bound (point)) 126 (catch 'found
127 (let* ((found 127 (while (> bound (point))
128 (save-excursion 128 (let* ((found
129 (re-search-forward 129 (save-excursion
130 "\\(?:#?'\\|\"\\|\\\\\\)" bound t))) 130 (re-search-forward
131 (next (or (and found (match-beginning 0)) 131 "\\(?:#?'\\|\"\\|\\\\\\)" bound t)))
132 bound))) 132 (next (or (and found (match-beginning 0))
133 (if (re-search-forward pat next t) 133 bound)))
134 (throw 'found (match-beginning 1)) 134 (if (re-search-forward pat next t)
135 (goto-char next) 135 (throw 'found (match-beginning 1))
136 (while (eshell-extpipe--or-with-catch 136 (goto-char next)
137 (eshell-parse-lisp-argument) 137 (while (eshell-extpipe--or-with-catch
138 (eshell-parse-backslash) 138 (eshell-parse-lisp-argument)
139 (eshell-parse-double-quote) 139 (eshell-parse-backslash)
140 (eshell-parse-literal-quote))) 140 (eshell-parse-double-quote)
141 ;; Guard against an infinite loop if none of 141 (eshell-parse-literal-quote)))
142 ;; the parsers moved us forward. 142 ;; Guard against an infinite loop if none of
143 (unless (or (> (point) next) (eobp)) 143 ;; the parsers moved us forward.
144 (forward-char 1)))))))) 144 (unless (or (> (point) next) (eobp))
145 (goto-char (if (and result go) (match-end 0) start)) 145 (forward-char 1))))))))
146 result))) 146 (goto-char (if (and result go) (match-end 0) start))
147 (unless (or eshell-current-argument eshell-current-quoted) 147 result)))
148 (let ((beg (point)) end 148 (unless (or eshell-current-argument eshell-current-quoted)
149 (next-marked (findbeg1 "\\(?:\\=\\|\\s-\\)\\(\\*[|<>]\\)")) 149 (let ((beg (point)) end
150 (next-unmarked 150 (next-marked (findbeg1 "\\(?:\\=\\|\\s-\\)\\(\\*[|<>]\\)"))
151 (or (findbeg1 "\\(?:\\=\\|[^*]\\|\\S-\\*\\)\\(|\\)") 151 (next-unmarked
152 (point-max)))) 152 (or (findbeg1 "\\(?:\\=\\|[^*]\\|\\S-\\*\\)\\(|\\)")
153 (when (and next-marked (> next-unmarked next-marked) 153 (point-max))))
154 (or (> next-marked (point)) 154 (when (and next-marked (> next-unmarked next-marked)
155 (looking-back "\\`\\|\\s-" nil))) 155 (or (> next-marked (point))
156 ;; Skip to the final segment of the external pipeline. 156 (looking-back "\\`\\|\\s-" nil)))
157 (while (findbeg1 "\\(?:\\=\\|\\s-\\)\\(\\*|\\)" t)) 157 ;; Skip to the final segment of the external pipeline.
158 ;; Find output redirections. 158 (while (findbeg1 "\\(?:\\=\\|\\s-\\)\\(\\*|\\)" t))
159 (while (findbeg1 159 ;; Find output redirections.
160 "\\([0-9]?>+&?[0-9]?\\s-*\\S-\\)" t next-unmarked) 160 (while (findbeg1
161 ;; Is the output redirection Eshell-specific? We have our 161 "\\([0-9]?>+&?[0-9]?\\s-*\\S-\\)" t next-unmarked)
162 ;; own logic, rather than calling `eshell-parse-argument', 162 ;; Is the output redirection Eshell-specific? We have our
163 ;; to avoid specifying here all the possible cars of 163 ;; own logic, rather than calling `eshell-parse-argument',
164 ;; parsed special references -- `get-buffer-create' etc. 164 ;; to avoid specifying here all the possible cars of
165 (forward-char -1) 165 ;; parsed special references -- `get-buffer-create' etc.
166 (let ((this-end 166 (forward-char -1)
167 (save-match-data 167 (let ((this-end
168 (cond ((looking-at "#<") 168 (save-match-data
169 (forward-char 1) 169 (cond ((looking-at "#<")
170 (1+ (eshell-find-delimiter ?\< ?\>))) 170 (forward-char 1)
171 ((and (looking-at "/\\S-+") 171 (1+ (eshell-find-delimiter ?\< ?\>)))
172 (assoc (match-string 0) 172 ((and (looking-at "/\\S-+")
173 eshell-virtual-targets)) 173 (assoc (match-string 0)
174 (match-end 0)))))) 174 eshell-virtual-targets))
175 (cond ((and this-end end) 175 (match-end 0))))))
176 (goto-char this-end)) 176 (cond ((and this-end end)
177 (this-end 177 (goto-char this-end))
178 (goto-char this-end) 178 (this-end
179 (setq end (match-beginning 0))) 179 (goto-char this-end)
180 (t 180 (setq end (match-beginning 0)))
181 (setq end nil))))) 181 (t
182 ;; We've moved past all Eshell-specific output redirections 182 (setq end nil)))))
183 ;; we could find. If there is only whitespace left, then 183 ;; We've moved past all Eshell-specific output redirections
184 ;; `end' is right before redirections we should exclude; 184 ;; we could find. If there is only whitespace left, then
185 ;; otherwise, we must include everything. 185 ;; `end' is right before redirections we should exclude;
186 (unless (and end (skip-syntax-forward "\s" next-unmarked) 186 ;; otherwise, we must include everything.
187 (= next-unmarked (point))) 187 (unless (and end (skip-syntax-forward "\s" next-unmarked)
188 (setq end next-unmarked)) 188 (= next-unmarked (point)))
189 (let ((cmd (string-trim 189 (setq end next-unmarked))
190 (buffer-substring-no-properties beg end)))) 190 (let ((cmd (string-trim
191 (goto-char end) 191 (buffer-substring-no-properties beg end))))
192 ;; We must now drop the asterisks, unless quoted/escaped. 192 (goto-char end)
193 (with-temp-buffer 193 ;; We must now drop the asterisks, unless quoted/escaped.
194 (insert cmd) 194 (with-temp-buffer
195 (goto-char (point-min)) 195 (insert cmd)
196 (cl-loop 196 (goto-char (point-min))
197 for next = (findbeg1 "\\(?:\\=\\|\\s-\\)\\(\\*[|<>]\\)" t) 197 (cl-loop
198 while next do (forward-char -2) (delete-char 1)) 198 for next = (findbeg1 "\\(?:\\=\\|\\s-\\)\\(\\*[|<>]\\)" t)
199 (eshell-finish-arg 199 while next do (forward-char -2) (delete-char 1))
200 `(eshell-external-pipeline ,(buffer-string)))))))))) 200 (eshell-finish-arg
201 `(eshell-external-pipeline ,(buffer-string)))))))))))
201 202
202(defun eshell-rewrite-external-pipeline (terms) 203(defun eshell-rewrite-external-pipeline (terms)
203 "Rewrite an external pipeline in TERMS as parsed by 204 "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 @@ yield the values intended."
49(defvar eshell-arg-listified nil) 49(defvar eshell-arg-listified nil)
50(defvar eshell-nested-argument nil) 50(defvar eshell-nested-argument nil)
51(defvar eshell-current-quoted nil) 51(defvar eshell-current-quoted nil)
52(defvar eshell-current-argument-plain nil
53 "If non-nil, the current argument is \"plain\", and not part of a command.")
52(defvar eshell-inside-quote-regexp nil) 54(defvar eshell-inside-quote-regexp nil)
53(defvar eshell-outside-quote-regexp nil) 55(defvar eshell-outside-quote-regexp nil)
54 56
@@ -184,11 +186,6 @@ treated as a literal character."
184 (add-hook 'pcomplete-try-first-hook 186 (add-hook 'pcomplete-try-first-hook
185 #'eshell-complete-special-reference nil t))) 187 #'eshell-complete-special-reference nil t)))
186 188
187(defun eshell-insert-buffer-name (buffer-name)
188 "Insert BUFFER-NAME into the current buffer at point."
189 (interactive "BName of buffer: ")
190 (insert-and-inherit "#<buffer " buffer-name ">"))
191
192(defsubst eshell-escape-arg (string) 189(defsubst eshell-escape-arg (string)
193 "Return STRING with the `escaped' property on it." 190 "Return STRING with the `escaped' property on it."
194 (if (stringp string) 191 (if (stringp string)
@@ -505,42 +502,6 @@ leaves point where it was."
505 (goto-char bound) 502 (goto-char bound)
506 (apply #'concat (nreverse strings)))))) 503 (apply #'concat (nreverse strings))))))
507 504
508(defun eshell-parse-special-reference ()
509 "Parse a special syntax reference, of the form `#<args>'.
510
511args := `type' `whitespace' `arbitrary-args' | `arbitrary-args'
512type := \"buffer\" or \"process\"
513arbitrary-args := any string of characters.
514
515If the form has no `type', the syntax is parsed as if `type' were
516\"buffer\"."
517 (when (and (not eshell-current-argument)
518 (not eshell-current-quoted)
519 (looking-at (rx "#<" (? (group (or "buffer" "process"))
520 space))))
521 (let ((here (point)))
522 (goto-char (match-end 0)) ;; Go to the end of the match.
523 (let ((buffer-p (if (match-beginning 1)
524 (equal (match-string 1) "buffer")
525 t)) ; With no type keyword, assume we want a buffer.
526 (end (eshell-find-delimiter ?\< ?\>)))
527 (when (not end)
528 (when (match-beginning 1)
529 (goto-char (match-beginning 1)))
530 (throw 'eshell-incomplete "#<"))
531 (if (eshell-arg-delimiter (1+ end))
532 (prog1
533 (list (if buffer-p #'get-buffer-create #'get-process)
534 ;; FIXME: We should probably parse this as a
535 ;; real Eshell argument so that we get the
536 ;; benefits of quoting, variable-expansion, etc.
537 (string-trim-right
538 (replace-regexp-in-string
539 (rx "\\" (group anychar)) "\\1"
540 (buffer-substring-no-properties (point) end))))
541 (goto-char (1+ end)))
542 (ignore (goto-char here)))))))
543
544(defun eshell-parse-delimiter () 505(defun eshell-parse-delimiter ()
545 "Parse an argument delimiter, which is essentially a command operator." 506 "Parse an argument delimiter, which is essentially a command operator."
546 ;; this `eshell-operator' keyword gets parsed out by 507 ;; this `eshell-operator' keyword gets parsed out by
@@ -591,7 +552,38 @@ If no argument requested a splice, return nil."
591 (when splicep 552 (when splicep
592 grouped-args))) 553 grouped-args)))
593 554
594;;;_* Special ref completion 555;;; Special references
556
557(defun eshell-parse-special-reference ()
558 "Parse a special syntax reference, of the form `#<args>'.
559
560args := `type' `whitespace' `arbitrary-args' | `arbitrary-args'
561type := \"buffer\" or \"process\"
562arbitrary-args := any number of Eshell arguments
563
564If the form has no `type', the syntax is parsed as if `type' were
565\"buffer\"."
566 (when (and (not eshell-current-argument)
567 (not eshell-current-quoted)
568 (looking-at (rx "#<" (? (group (or "buffer" "process"))
569 space))))
570 (let ((here (point)))
571 (goto-char (match-end 0)) ;; Go to the end of the match.
572 (let ((buffer-p (if (match-beginning 1)
573 (equal (match-string 1) "buffer")
574 t)) ; With no type keyword, assume we want a buffer.
575 (end (eshell-find-delimiter ?\< ?\>)))
576 (when (not end)
577 (when (match-beginning 1)
578 (goto-char (match-beginning 1)))
579 (throw 'eshell-incomplete "#<"))
580 (if (eshell-arg-delimiter (1+ end))
581 (prog1
582 (cons (if buffer-p #'eshell-get-buffer #'get-process)
583 (let ((eshell-current-argument-plain t))
584 (eshell-parse-arguments (point) end)))
585 (goto-char (1+ end)))
586 (ignore (goto-char here)))))))
595 587
596(defun eshell-complete-special-reference () 588(defun eshell-complete-special-reference ()
597 "If there is a special reference, complete it." 589 "If there is a special reference, complete it."
@@ -627,5 +619,16 @@ If no argument requested a splice, return nil."
627 (throw 'pcomplete-completions 619 (throw 'pcomplete-completions
628 (all-completions pcomplete-stub all-results)))))) 620 (all-completions pcomplete-stub all-results))))))
629 621
622(defun eshell-get-buffer (buffer-or-name)
623 "Return the buffer specified by BUFFER-OR-NAME, creating a new one if needed.
624This is equivalent to `get-buffer-create', but only accepts a
625single argument."
626 (get-buffer-create buffer-or-name))
627
628(defun eshell-insert-buffer-name (buffer-name)
629 "Insert BUFFER-NAME into the current buffer at point."
630 (interactive "BName of buffer: ")
631 (insert-and-inherit "#<buffer " (eshell-quote-argument buffer-name) ">"))
632
630(provide 'esh-arg) 633(provide 'esh-arg)
631;;; esh-arg.el ends here 634;;; 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 @@ describing the mode, e.g. for using with `eshell-get-target'.")
196 196
197(defun eshell-parse-redirection () 197(defun eshell-parse-redirection ()
198 "Parse an output redirection, such as `2>' or `>&'." 198 "Parse an output redirection, such as `2>' or `>&'."
199 (when (not eshell-current-quoted) 199 (unless (or eshell-current-quoted
200 eshell-current-argument-plain)
200 (cond 201 (cond
201 ;; Copying a handle (e.g. `2>&1'). 202 ;; Copying a handle (e.g. `2>&1').
202 ((looking-at (rx (? (group digit)) 203 ((looking-at (rx (? (group digit))
diff --git a/lisp/eshell/esh-proc.el b/lisp/eshell/esh-proc.el
index 3c946c22bdc..ea5896461b4 100644
--- a/lisp/eshell/esh-proc.el
+++ b/lisp/eshell/esh-proc.el
@@ -227,23 +227,6 @@ and signal names."
227 227
228(put 'eshell/kill 'eshell-no-numeric-conversions t) 228(put 'eshell/kill 'eshell-no-numeric-conversions t)
229 229
230(defun eshell-read-process-name (prompt)
231 "Read the name of a process from the minibuffer, using completion.
232The prompt will be set to PROMPT."
233 (completing-read prompt
234 (mapcar
235 (lambda (proc)
236 (cons (process-name proc) t))
237 (process-list))
238 nil t))
239
240(defun eshell-insert-process (process)
241 "Insert the name of PROCESS into the current buffer at point."
242 (interactive
243 (list (get-process
244 (eshell-read-process-name "Name of process: "))))
245 (insert-and-inherit "#<process " (process-name process) ">"))
246
247(defsubst eshell-record-process-object (object) 230(defsubst eshell-record-process-object (object)
248 "Record OBJECT as now running." 231 "Record OBJECT as now running."
249 (when (and eshell-subjob-messages 232 (when (and eshell-subjob-messages
@@ -695,5 +678,26 @@ everything."
695; ;; `eshell-resume-eval'. 678; ;; `eshell-resume-eval'.
696; (eshell--reset-after-signal "continue\n"))) 679; (eshell--reset-after-signal "continue\n")))
697 680
681;;; Special references
682
683(defun eshell-read-process-name (prompt)
684 "Read the name of a process from the minibuffer, using completion.
685The prompt will be set to PROMPT."
686 (completing-read prompt
687 (mapcar
688 (lambda (proc)
689 (cons (process-name proc) t))
690 (process-list))
691 nil t))
692
693(defun eshell-insert-process (process)
694 "Insert the name of PROCESS into the current buffer at point."
695 (interactive
696 (list (get-process
697 (eshell-read-process-name "Name of process: "))))
698 (insert-and-inherit "#<process "
699 (eshell-quote-argument (process-name process))
700 ">"))
701
698(provide 'esh-proc) 702(provide 'esh-proc)
699;;; esh-proc.el ends here 703;;; 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 @@
55 "temp\\([^>]\\|\\'\\)" temp 55 "temp\\([^>]\\|\\'\\)" temp
56 (string-replace 56 (string-replace
57 "#<buffer temp>" 57 "#<buffer temp>"
58 (concat "#<buffer " (buffer-name temp-buffer) ">") 58 (format "#<buffer %s>"
59 (eshell-quote-argument
60 (buffer-name temp-buffer)))
59 input)))) 61 input))))
60 ,@body) 62 ,@body)
61 (when (buffer-name temp-buffer) 63 (when (buffer-name temp-buffer)
@@ -110,7 +112,7 @@
110 '(progn 112 '(progn
111 (ignore 113 (ignore
112 (eshell-set-output-handle 1 'overwrite 114 (eshell-set-output-handle 1 'overwrite
113 (get-buffer-create "temp"))) 115 (eshell-get-buffer "temp")))
114 (eshell-named-command "sh" 116 (eshell-named-command "sh"
115 (list "-c" "echo \"bar\" | rev")))) 117 (list "-c" "echo \"bar\" | rev"))))
116 (with-substitute-for-temp 118 (with-substitute-for-temp
@@ -133,7 +135,7 @@
133 '(progn 135 '(progn
134 (ignore 136 (ignore
135 (eshell-set-output-handle 1 'overwrite 137 (eshell-set-output-handle 1 'overwrite
136 (get-buffer-create "quux"))) 138 (eshell-get-buffer "quux")))
137 (ignore 139 (ignore
138 (eshell-set-output-handle 1 'append 140 (eshell-set-output-handle 1 'append
139 (get-process "other"))) 141 (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 @@ treated literally, as a backslash and a newline."
118 (format "echo #<buffer %s>" (buffer-name)) 118 (format "echo #<buffer %s>" (buffer-name))
119 (current-buffer)))) 119 (current-buffer))))
120 120
121(ert-deftest esh-arg-test/special-reference/special () 121(ert-deftest esh-arg-test/special-reference/quoted ()
122 "Test that '#<buffer \"foo bar\">' refers to the buffer \"foo bar\"."
123 (with-temp-buffer
124 (rename-buffer "foo bar" t)
125 (eshell-command-result-equal
126 (format "echo #<buffer \"%s\">" (buffer-name))
127 (current-buffer))
128 (eshell-command-result-equal
129 (format "echo #<buffer '%s'>" (buffer-name))
130 (current-buffer))))
131
132(ert-deftest esh-arg-test/special-reference/var-expansion ()
133 "Test that variable expansion inside special references works."
134 (with-temp-buffer
135 (rename-buffer "my-buffer" t)
136 (let ((eshell-test-value (buffer-name)))
137 (eshell-command-result-equal
138 "echo #<buffer $eshell-test-value>"
139 (current-buffer))
140 (eshell-command-result-equal
141 "echo #<buffer \"$eshell-test-value\">"
142 (current-buffer)))))
143
144(ert-deftest esh-arg-test/special-reference/special-characters ()
122 "Test that \"#<...>\" works correctly when escaping special characters." 145 "Test that \"#<...>\" works correctly when escaping special characters."
123 (with-temp-buffer 146 (with-temp-buffer
124 (rename-buffer "<my buffer>" t) 147 (rename-buffer "<my buffer>" t)