* 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'.
;; 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
(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)
(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)
(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
(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."
(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
(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))
(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
; ;; `eshell-resume-eval'.
; (eshell--reset-after-signal "continue\n")))
+;;; 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
"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)
'(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
'(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")))
(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)