]> git.eshelyaron.com Git - emacs.git/commitdiff
Add support for completing special references (e.g. buffers) in Eshell
authorJim Porter <jporterbugs@gmail.com>
Wed, 25 Jan 2023 05:22:06 +0000 (21:22 -0800)
committerJim Porter <jporterbugs@gmail.com>
Sun, 12 Mar 2023 02:49:28 +0000 (18:49 -0800)
* lisp/eshell/em-cmpl.el (eshell-complete-parse-arguments): Handle
special references.

* lisp/eshell/em-arg.el (eshell-parse-special-reference): Ensure point
is just after the "#<" when incomplete, and handle backslash escapes
more thoroughly.
(eshell-complete-special-reference): New function.

* test/lisp/eshell/esh-arg-tests.el
(esh-arg-test/special-reference/default)
(esh-arg-test/special-reference/buffer)
(esh-arg-test/special-reference/special):
* test/lisp/eshell/em-cmpl-tests.el
(em-cmpl-test/special-ref-completion/type)
(em-cmpl-test/special-ref-completion/implicit-buffer)
(em-cmpl-test/special-ref-completion/buffer): New tests.

lisp/eshell/em-cmpl.el
lisp/eshell/esh-arg.el
test/lisp/eshell/em-cmpl-tests.el
test/lisp/eshell/esh-arg-tests.el

index 5dfd10d6e4ce8dc8e3e4f8baab79695bbf064c13..b65652019d4c3030a06cab3875b8963253b3548f 100644 (file)
@@ -317,7 +317,7 @@ to writing a completion function."
     (eshell--pcomplete-insert-tab))
   (let ((end (point-marker))
        (begin (save-excursion (beginning-of-line) (point)))
-       args posns delim)
+       args posns delim incomplete-arg)
     (when (and pcomplete-allow-modifications
               (memq this-command '(pcomplete-expand
                                    pcomplete-expand-and-complete)))
@@ -332,10 +332,11 @@ to writing a completion function."
         (cond ((member (car delim) '("{" "${" "$<"))
               (setq begin (1+ (cadr delim))
                     args (eshell-parse-arguments begin end)))
-              ((member (car delim) '("$'" "$\""))
+              ((member (car delim) '("$'" "$\"" "#<"))
                ;; Add the (incomplete) argument to our arguments, and
                ;; note its position.
-               (setq args (append (nth 2 delim) (list (car delim))))
+               (setq args (append (nth 2 delim) (list (car delim)))
+                     incomplete-arg t)
                (push (- (nth 1 delim) 2) posns))
               ((member (car delim) '("(" "$("))
               (throw 'pcompleted (elisp-completion-at-point)))
@@ -362,7 +363,8 @@ to writing a completion function."
        (setq args (nthcdr (1+ new-start) args)
              posns (nthcdr (1+ new-start) posns))))
     (cl-assert (= (length args) (length posns)))
-    (when (and args (eq (char-syntax (char-before end)) ? )
+    (when (and args (not incomplete-arg)
+               (eq (char-syntax (char-before end)) ? )
               (not (eq (char-before (1- end)) ?\\)))
       (nconc args (list ""))
       (nconc posns (list (point))))
index cb0b2e0938c339627c06248736d75c9682d2adbe..aa1e8f77ea578d5d348f12332fc7077475b57cbb 100644 (file)
@@ -28,6 +28,9 @@
 ;;; Code:
 
 (require 'esh-util)
+(require 'esh-module)
+
+(require 'pcomplete)
 
 (eval-when-compile
   (require 'cl-lib))
@@ -175,7 +178,11 @@ treated as a literal character."
   "Initialize the argument parsing code."
   (eshell-arg-mode)
   (setq-local eshell-inside-quote-regexp nil)
-  (setq-local eshell-outside-quote-regexp nil))
+  (setq-local eshell-outside-quote-regexp nil)
+
+  (when (eshell-using-module 'eshell-cmpl)
+    (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."
@@ -506,21 +513,28 @@ 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 "#<\\(\\(buffer\\|process\\)\\s-\\)?"))
+             (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-string 1)
-                          (string= (match-string 2) "buffer")
-                        t)) ;; buffer-p is non-nil by default.
+      (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)
-                      (replace-regexp-in-string
-                       (rx "\\" (group (or "\\" "<" ">"))) "\\1"
-                       (buffer-substring-no-properties (point) end)))
+                (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)))))))
 
@@ -574,5 +588,41 @@ If no argument requested a splice, return nil."
     (when splicep
       grouped-args)))
 
+;;;_* Special ref completion
+
+(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))))))
+
 (provide 'esh-arg)
 ;;; esh-arg.el ends here
index be2199c0464f8eba1de94e322d446768c8d8f44c..b60faab911401a1261e93f45cbfc59242a0e7f95 100644 (file)
@@ -176,6 +176,46 @@ See <lisp/eshell/esh-cmd.el>."
    (should (equal (eshell-insert-and-complete "echo (eshell/ech")
                   "echo (eshell/echo"))))
 
+(ert-deftest em-cmpl-test/special-ref-completion/type ()
+  "Test completion of the start of special references like \"#<buffer\".
+See <lisp/eshell/esh-arg.el>."
+  (with-temp-eshell
+   (should (equal (eshell-insert-and-complete "echo hi > #<buf")
+                  "echo hi > #<buffer ")))
+  (with-temp-eshell
+   (should (equal (eshell-insert-and-complete "echo hi > #<proc")
+                  "echo hi > #<process "))))
+
+(ert-deftest em-cmpl-test/special-ref-completion/implicit-buffer ()
+  "Test completion of special references like \"#<buf>\".
+See <lisp/eshell/esh-arg.el>."
+  (let (bufname)
+    (with-temp-buffer
+      (setq bufname (rename-buffer "my-buffer" t))
+      (with-temp-eshell
+       (should (equal (eshell-insert-and-complete "echo hi > #<my-buf")
+                      (format "echo hi > #<%s> " bufname))))
+      (setq bufname (rename-buffer "another buffer" t))
+      (with-temp-eshell
+       (should (equal (eshell-insert-and-complete "echo hi > #<anoth")
+                      (format "echo hi > #<%s> "
+                              (string-replace " " "\\ " bufname))))))))
+
+(ert-deftest em-cmpl-test/special-ref-completion/buffer ()
+  "Test completion of special references like \"#<buffer buf>\".
+See <lisp/eshell/esh-arg.el>."
+  (let (bufname)
+    (with-temp-buffer
+      (setq bufname (rename-buffer "my-buffer" t))
+      (with-temp-eshell
+       (should (equal (eshell-insert-and-complete "echo hi > #<buffer my-buf")
+                      (format "echo hi > #<buffer %s> " bufname))))
+      (setq bufname (rename-buffer "another buffer" t))
+      (with-temp-eshell
+       (should (equal (eshell-insert-and-complete "echo hi > #<buffer anoth")
+                      (format "echo hi > #<buffer %s> "
+                              (string-replace " " "\\ " bufname))))))))
+
 (ert-deftest em-cmpl-test/variable-ref-completion ()
   "Test completion of variable references like \"$var\".
 See <lisp/eshell/esh-var.el>."
index 918ad3a949f2d92413a0305fe0ef810c230206e4..c883db3907fe7db2c5bdfc0e89071a3e429eeeb1 100644 (file)
@@ -102,4 +102,34 @@ treated literally, as a backslash and a newline."
    (eshell-match-command-output "echo \"hi\\\nthere\""
                                 "hithere\n")))
 
+(ert-deftest esh-arg-test/special-reference/default ()
+  "Test that \"#<buf>\" refers to the buffer \"buf\"."
+  (with-temp-buffer
+    (rename-buffer "my-buffer" t)
+    (eshell-command-result-equal
+     (format "echo #<%s>" (buffer-name))
+     (current-buffer))))
+
+(ert-deftest esh-arg-test/special-reference/buffer ()
+  "Test that \"#<buffer buf>\" refers to the buffer \"buf\"."
+  (with-temp-buffer
+    (rename-buffer "my-buffer" t)
+    (eshell-command-result-equal
+     (format "echo #<buffer %s>" (buffer-name))
+     (current-buffer))))
+
+(ert-deftest esh-arg-test/special-reference/special ()
+  "Test that \"#<...>\" works correctly when escaping special characters."
+  (with-temp-buffer
+    (rename-buffer "<my buffer>" t)
+    (let ((escaped-bufname (replace-regexp-in-string
+                            (rx (group (or "\\" "<" ">" space))) "\\\\\\1"
+                            (buffer-name))))
+      (eshell-command-result-equal
+       (format "echo #<%s>" escaped-bufname)
+       (current-buffer))
+      (eshell-command-result-equal
+       (format "echo #<buffer %s>" escaped-bufname)
+       (current-buffer)))))
+
 ;; esh-arg-tests.el ends here