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.
This commit is contained in:
parent
1c2cb9cd61
commit
69e8333210
6 changed files with 190 additions and 94 deletions
|
@ -2590,11 +2590,6 @@ If it's a Lisp function, input redirection implies @command{xargs} (in a
|
|||
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
|
||||
|
|
|
@ -377,7 +377,8 @@ to writing a completion function."
|
|||
(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)
|
||||
|
|
|
@ -165,6 +165,39 @@ treated as a literal character."
|
|||
: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 @@ If no argument requested a splice, return nil."
|
|||
|
||||
;;; 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 @@ single argument."
|
|||
(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
|
||||
|
|
|
@ -393,49 +393,6 @@ for a given 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
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
;;; Code:
|
||||
|
||||
(require 'esh-arg)
|
||||
(require 'esh-io)
|
||||
(require 'esh-util)
|
||||
|
||||
|
@ -158,6 +159,14 @@ PROC and STATUS to functions on the latter."
|
|||
(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))
|
||||
|
||||
(define-obsolete-function-alias 'eshell-reset-after-proc
|
||||
|
@ -699,5 +708,9 @@ The prompt will be set to PROMPT."
|
|||
(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
|
||||
|
|
|
@ -242,6 +242,49 @@ current buffer."
|
|||
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.
|
||||
|
|
Loading…
Add table
Reference in a new issue