]> git.eshelyaron.com Git - emacs.git/commitdiff
Add 'eshell-special-ref-alist' to allow extending Eshell special refs
authorJim Porter <jporterbugs@gmail.com>
Wed, 23 Aug 2023 01:43:51 +0000 (18:43 -0700)
committerJim Porter <jporterbugs@gmail.com>
Tue, 24 Oct 2023 18:36:27 +0000 (11:36 -0700)
* 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
lisp/eshell/em-cmpl.el
lisp/eshell/esh-arg.el
lisp/eshell/esh-cmd.el
lisp/eshell/esh-proc.el
lisp/eshell/esh-util.el

index cc94f610615a6f25c7eceb2f48ae18c47f61e6fd..b5cc9faeec20bdbf6683fbf6166cb15fe5edfad8 100644 (file)
@@ -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
index 61f1237b90744f1b200f1b8bfe6cdbfded2545ca..0255da88dbd5fce28067867da5bd30cbaab41a9c 100644 (file)
@@ -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)
index c3d3347e888e0916eb6dfaa369c9096331e743f7..d5fcabccb14bd8e53ad2d1429ff7682a21c08d79 100644 (file)
@@ -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
index 990d2ca11224f00fb610ba9830ba78c2458285f5..ecd947774eeea06118b8d8bfb8181cc5b93df705 100644 (file)
@@ -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
index ea5896461b4886e7797d7300254e943b32b1531c..6561561440e64aab8060d38a97ced2966a58c197 100644 (file)
@@ -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
index ca2f775318a3b7b5df74972b473c8696aab3628b..b22c286c635863035b071750c2ecafae225cd8c0 100644 (file)
@@ -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.