: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)
;;; 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.
(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
;; 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
string)
string)
+(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.