]> git.eshelyaron.com Git - emacs.git/commitdiff
New commands for cycling completions and restoring completed input
authorEshel Yaron <me@eshelyaron.com>
Sun, 14 Jan 2024 14:47:05 +0000 (15:47 +0100)
committerEshel Yaron <me@eshelyaron.com>
Fri, 19 Jan 2024 10:08:42 +0000 (11:08 +0100)
This adds two new minibuffer commands, 'minibuffer-cycle-completion'
and 'minibuffer-restore-completion-input', bound to 'C-o' and 'C-l',
respectively.

* lisp/minibuffer.el (completion--input): New local variable.
(minibuffer--cache-completion-input)
(completion-switch-cycling-direction): New functions.
(completion-all-sorted-completions): Respect
'minibuffer-completions-sort-function', and...
(minibuffer-completion-help)
(completion--do-completion): Cache completion input.
(completions-auto-update): Preserve 'completion--input'.
(minibuffer-force-complete): Highlight inserted candidate in
completions list if *Completions* is already visible.
(minibuffer-cycle-completion)
(minibuffer-restore-completion-input): New commands.
(minibuffer-local-completion-map): Bind them.

* doc/emacs/mini.texi (Completion Commands, Completion Options)
* doc/lispref/minibuf.texi (Completion Commands): Document them.

* etc/NEWS: Announce.

* test/lisp/minibuffer-tests.el (restore-completion-input-test):
New test.

doc/emacs/mini.texi
doc/lispref/minibuf.texi
etc/NEWS
lisp/minibuffer.el
test/lisp/minibuffer-tests.el

index 50cd80e72d46c0f64950411acb38da70c14cf460..9f2a789350865d313f45b0739bfc1bc98e9584f8 100644 (file)
@@ -355,6 +355,13 @@ Submit the text in the minibuffer as the argument, possibly completing
 first (@code{minibuffer-complete-and-exit}).  @xref{Completion Exit}.
 @item ?
 Display a list of completions (@code{minibuffer-completion-help}).
+@item C-o
+Cycle minibuffer input among possible completion candidates
+(@code{minibuffer-cycle-completion}).
+@item C-l
+Restore the minibuffer input that Emacs used to compute the current
+set of completion candidates.
+(@code{minibuffer-restore-completion-input}).
 @item C-x C-v
 Change the order of the list of possible completions
 (@code{minibuffer-sort-completions}).
@@ -396,9 +403,46 @@ all the way to @samp{auto-fill-mode}.
 the minibuffer, or completes it and then submits it, depending on the
 ``strictness'' of the completion.  @xref{Completion Exit}.
 
+@cindex cycle completions
+@cindex completions cycling
+@kindex C-o @r{(completion)}
+@findex minibuffer-cycle-completion
+  @kbd{C-o} (@code{minibuffer-cycle-completion}) cycles among the list
+of possible completions.  The first time you hit @kbd{C-o}, it expands
+your partial input in the minibuffer to the first matching completion
+candidate.  Another @kbd{C-o} replaces the minibuffer contents with
+the next completion candidate, and repeating @kbd{C-o} lets you cycle
+among all completions for your initial input, wrapping around when you
+reach the end of the list.  While you're cycling, Emacs remembers the
+initial partial input you started with, and the corresponding set of
+completion candidates.  If you edit a completion candidate in the
+minibuffer after cycling to it, that tells Emacs to forget about your
+previous partial input and compute a new set of completion candidates
+based on your new input the next time you hit @kbd{C-o}.  You can
+invoke @kbd{C-o} with a numeric prefix argument @var{n} to cycle
+@var{n} candidates forward at once.  A negative @var{n} cycles
+backward instead.  A prefix argument of zero (@kbd{C-0 C-o}) switches
+the cycling direction, so the next @kbd{C-o} presses cycle backward.
+
+@kindex C-l @r{(completion)}
+@findex minibuffer-restore-completion-input
+  @kbd{C-l} (@code{minibuffer-restore-completion-input}) restores the
+minibuffer contents to the (partial) input that you last used for
+completion in the current minibuffer.  Commands that complete your
+input, such as @kbd{@key{TAB}} and @kbd{C-o}, record the partial input
+that you provide them for you to later retrieve it with @kbd{C-l}.
+For example, if you type @kbd{M-x bar} and start cycling with
+@kbd{C-o}, only to realize that you want a candidate that matches
+@samp{baz} and not @samp{bar}, then you can type @kbd{C-l} to restore
+the minibuffer input to @samp{bar}, change it to @samp{baz} and
+complete again.
+
+@anchor{Sort Completions}
+@cindex sort completions
+@cindex completions sort order
 @kindex C-x C-v @r{(completion)}
 @findex minibuffer-sort-completions
-  @key{C-x C-v} (@code{minibuffer-sort-completions}) changes the order
+  @kbd{C-x C-v} (@code{minibuffer-sort-completions}) changes the order
 of the completions list.  By default, Emacs sorts the list of possible
 completion candidates in the order that you specify in user option
 @code{completions-sort} (@pxref{Completion Options}).  This command
@@ -857,16 +901,18 @@ completions list buffer, and the second one will switch to it.
 
 @vindex completion-cycle-threshold
   If @code{completion-cycle-threshold} is non-@code{nil}, completion
-commands can cycle through completion alternatives.  Normally, if
-there is more than one completion alternative for the text in the
-minibuffer, a completion command completes up to the longest common
-substring.  If you change @code{completion-cycle-threshold} to
-@code{t}, the completion command instead completes to the first of
-those completion alternatives; each subsequent invocation of the
-completion command replaces that with the next completion alternative,
-in a cyclic manner.  If you give @code{completion-cycle-threshold} a
-numeric value @var{n}, completion commands switch to this cycling
-behavior only when there are @var{n} or fewer alternatives.
+commands such as @kbd{@key{TAB}} can cycle through completion
+alternatives.  Normally, if there is more than one completion
+alternative for the text in the minibuffer, a completion command
+completes up to the longest common substring.  If you change
+@code{completion-cycle-threshold} to @code{t}, the completion command
+instead behaves like @kbd{C-o} (@code{minibuffer-cycle-completion}):
+it completes to the first of those completion alternatives; each
+subsequent invocation of the completion command replaces that with the
+next completion alternative, in a cyclic manner.  If you give
+@code{completion-cycle-threshold} a numeric value @var{n}, completion
+commands switch to this cycling behavior only when there are @var{n}
+or fewer alternatives.
 
 @vindex completions-format
   When displaying completions, Emacs will normally pop up a new buffer
index d2cf1ee92156984389081cb2209873cd286c321b..d81f47418ad2e36eeb8273a1cd3e80403a2d5f57 100644 (file)
@@ -1337,6 +1337,17 @@ first character that is not a word constituent.  @xref{Syntax Tables}.
 This function completes the minibuffer contents as far as possible.
 @end deffn
 
+@deffn Command minibuffer-cycle-completion
+This function expands the current minibuffer contents to the first
+matching completion, and cycles among all matching candidates if you
+call it repeatedly.
+@end deffn
+
+@deffn Command minibuffer-restore-completion-input
+This function restores the last input that a completion command, such
+as @code{minibuffer-complete}, expanded in the current minibuffer.
+@end deffn
+
 @deffn Command minibuffer-complete-and-exit
 This function completes the minibuffer contents, and exits if
 confirmation is not required, i.e., if
@@ -1457,6 +1468,12 @@ keymap makes the following bindings:
 @item @key{TAB}
 @code{minibuffer-complete}
 
+@item C-o
+@code{minibuffer-cycle-completion}
+
+@item C-l
+@code{minibuffer-restore-completion-input}
+
 @item C-x C-v
 @code{minibuffer-sort-completions}
 
@@ -1477,7 +1494,7 @@ This keymap provides access to commands that deal with restricting the
 list of possible completions.  By default, this keymap makes the
 following bindings:
 
-@table @asis
+@table @kbd
 @item n
 @code{minibuffer-narrow-completions-to-current}
 
@@ -1496,8 +1513,8 @@ are bound to @code{exit-minibuffer}, the command that exits the
 minibuffer unconditionally.  By default, this keymap makes the following
 bindings:
 
-@table @asis
-@item @kbd{C-j}
+@table @kbd
+@item C-j
 @code{minibuffer-complete-and-exit}
 
 @item @key{RET}
index f8e2d9974b6a010076a84c3675e3da971d0ca5a0..fdc7d68101eadd1ad4e94b997e3cd2a4515b2fe3 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -782,9 +782,21 @@ minibuffer to change the sort order of the completions list.  If you
 invoke this command with a negative prefix argument ('C-- C-x C-v'),
 it reverses the current order.
 
-*** New minor mode 'completions-auto-update-mode'.
-This global minor mode automatically updates the *Completions* buffer
-as you type in the minibuffer.
++++
+*** New command 'minibuffer-cycle-completion'.
+This command, bound to 'C-o' in the minibuffer, expands the current
+minibuffer contents to the first matching completion, and cycles among
+all matching candidates if you call it repeatedly.  This is similar to
+'minibuffer-complete' ('TAB' in the minibuffer) with non-nil
+'completion-cycle-threshold', except that
+'minibuffer-cycle-completion' always cycles, regardless of the value
+of 'completion-cycle-threshold' and the number of completion
+candidates.
+
++++
+*** New command 'minibuffer-restore-completion-input.
+This command, bound to 'C-l' in the minibuffer, restores the (partial)
+input that you last used for completion in the current minibuffer.
 
 +++
 *** New command 'crm-change-separator'.
index b948cabdd3dbf9af6528a308203e11b6558399ce..a3ab123cc9a272958fc69792c1a8eafaa09de03e 100644 (file)
@@ -1470,6 +1470,7 @@ pair of a group title string and a list of group candidate strings."
 
 (defvar-local completion-all-sorted-completions nil)
 (defvar-local completion--all-sorted-completions-location nil)
+(defvar-local completion--input nil)
 (defvar completion-cycling nil)      ;Function that takes down the cycling map.
 (defvar completion-tab-width nil)
 
@@ -1599,6 +1600,11 @@ when the buffer's text is already an exact match."
                   ('always t))
                 (minibuffer-completion-help beg end))
                (t (minibuffer-hide-completions)
+                  (minibuffer--cache-completion-input
+                   string (car (completion-boundaries
+                                string
+                                minibuffer-completion-table
+                                minibuffer-completion-predicate "")))
                   (when exact
                     ;; If completion did not put point at end of field,
                     ;; it's a sign that completion is not finished.
@@ -1834,7 +1840,9 @@ include as `display-sort-function' in completion metadata."
                                            base-size md
                                            minibuffer-completion-table
                                            minibuffer-completion-predicate))
-             (sort-fun (completion-metadata-get all-md 'cycle-sort-function))
+             (sort-fun
+              (or minibuffer-completions-sort-function
+                  (completion-metadata-get all-md 'cycle-sort-function)))
              (group-fun (completion-metadata-get all-md 'group-function)))
         (when last
           (setcdr last nil)
@@ -1865,6 +1873,11 @@ include as `display-sort-function' in completion metadata."
                           (substring string 0 base-size))
                          all)))))
 
+          ;; Cache input for `minibuffer-restore-completion-input',
+          ;; unless STRING is an exact and sole completion.
+          (unless (and (not (consp (cdr all))) (equal (car all) string))
+            (minibuffer--cache-completion-input string base-size))
+
           ;; Cache the result.  This is not just for speed, but also so that
           ;; repeated calls to minibuffer-force-complete can cycle through
           ;; all possibilities.
@@ -1891,6 +1904,41 @@ include as `display-sort-function' in completion metadata."
        ;; If a match is not required, exit after all.
        (exit-minibuffer)))))
 
+(defun completion-switch-cycling-direction ()
+  "Switch completion cycling from forward to backward and vice versa."
+  (setq completion-all-sorted-completions
+        (let* ((all completion-all-sorted-completions)
+               (last (last all))
+               (base (cdr last)))
+          (when last (setcdr last nil))
+          (setq all (nreverse all))
+          (setq last (last all))
+          (when last (setcdr last (cons (car all) base)))
+          (cdr all))))
+
+(defun minibuffer-cycle-completion (arg)
+  "Cycle minibuffer input to the ARGth next completion.
+
+If ARG is negative, cycle back that many completion candidates.
+If ARG is 0, change cycling direction.
+
+Interactively, ARG is the prefix argument, and it defaults to 1."
+  (interactive "p" minibuffer-mode)
+  (let* ((times (abs arg)))
+    (when (< arg 1) (completion-switch-cycling-direction))
+    (if (< 0 times)
+        (dotimes (_ times) (minibuffer-force-complete))
+      (completion--message "Switched cycling direction"))
+    (when (< arg 0) (completion-switch-cycling-direction))))
+
+(defun minibuffer-restore-completion-input ()
+  "Restore minibuffer contents to last input used for completion."
+  (interactive "" minibuffer-mode)
+  (when completion--input
+    (completion--replace (+ (minibuffer-prompt-end) (cdr completion--input))
+                         (point-max)
+                         (car completion--input))))
+
 (defun minibuffer-force-complete (&optional start end dont-cycle)
   "Complete the minibuffer to an exact match.
 Repeated uses step through the possible completions.
@@ -1921,6 +1969,18 @@ DONT-CYCLE tells the function not to setup cycling."
       (setq this-command 'completion-at-point) ;For completion-in-region.
       ;; Set cycling after modifying the buffer since the flush hook resets it.
       (unless dont-cycle
+        ;; If *Completions* is visible, highlight the current candidate.
+        (when-let ((win (get-buffer-window "*Completions*" 0))
+                   (pm (with-current-buffer "*Completions*"
+                         (save-excursion
+                           (goto-char (point-min))
+                           (when-let ((pm (text-property-search-forward
+                                           'completion--string (car all) t)))
+                             (setq-local
+                              cursor-face-highlight-nonselected-window t)
+                             (goto-char (prop-match-beginning pm))
+                             (text-property-search-forward 'cursor-face))))))
+          (set-window-point win (prop-match-beginning pm)))
         ;; If completing file names, (car all) may be a directory, so we'd now
         ;; have a new set of possible completions and might want to reset
         ;; completion-all-sorted-completions to nil, but we prefer not to,
@@ -2771,6 +2831,10 @@ completions list."
                    (ngettext "" "s" (length styles))
                    (mapconcat #'symbol-name styles "', `"))))
 
+(defun minibuffer--cache-completion-input (string base-size)
+  "Record STRING and BASE-SIZE for `minibuffer-restore-completion-input'."
+  (setq completion--input (cons (substring string base-size) base-size)))
+
 (defun minibuffer-completion-help (&optional start end)
   "Display a list of possible completions of the current minibuffer contents."
   (interactive)
@@ -2784,7 +2848,10 @@ completions list."
                        minibuffer-completion-table
                        minibuffer-completion-predicate
                        (- (point) start)
-                       md)))
+                       md))
+         (last (last completions))
+         (base-size (or (cdr last) 0)))
+    (minibuffer--cache-completion-input string base-size)
     (message nil)
     (if (or (null completions)
             (and (not (consp (cdr completions)))
@@ -2798,9 +2865,7 @@ completions list."
               (completion--message "Sole completion")
             (completion--fail)))
 
-      (let* ((last (last completions))
-             (base-size (or (cdr last) 0))
-             (prefix (unless (zerop base-size) (substring string 0 base-size)))
+      (let* ((prefix (unless (zerop base-size) (substring string 0 base-size)))
              (minibuffer-completion-base (substring string 0 base-size))
              (base-prefix (buffer-substring (minibuffer--completion-prompt-end)
                                             (+ start base-size)))
@@ -3284,9 +3349,8 @@ The completion method is determined by `completion-at-point-functions'."
   :parent minibuffer-local-map
   "TAB"       #'minibuffer-complete
   "<backtab>" #'minibuffer-complete
-  ;; M-TAB is already abused for many other purposes, so we should find
-  ;; another binding for it.
-  ;; "M-TAB"  #'minibuffer-force-complete
+  "C-o"       #'minibuffer-cycle-completion
+  "C-l"       #'minibuffer-restore-completion-input
   "SPC"       #'minibuffer-complete-word
   "?"         #'minibuffer-completion-help
   "<prior>"   #'switch-to-completions
@@ -5615,9 +5679,11 @@ This applies to `completions-auto-update-mode', which see."
 (defun completions-auto-update ()
   "Update the *Completions* buffer, if it is visible."
   (when (get-buffer-window "*Completions*" 0)
-    (if completion-in-region-mode
-        (completion-help-at-point)
-      (minibuffer-completion-help)))
+    ;; Preserve current `completion--input'.
+    (let ((completion--input completion--input))
+      (if completion-in-region-mode
+          (completion-help-at-point)
+        (minibuffer-completion-help))))
   (setq completions-auto-update-timer nil))
 
 (defun completions-auto-update-start-timer ()
index c1fe3032cb5e1380c135eaa8019616b43b7ce95a..f3280dbf40122f49380e09286e6cd59c53461984 100644 (file)
        (let ((executing-kbd-macro t)) ; Force the real minibuffer
          (completing-read "Prompt: " ,collection)))))
 
+(ert-deftest restore-completion-input-test ()
+  (completing-read-with-minibuffer-setup
+      '("foo" "food" "bar" "baz")
+    (execute-kbd-macro (kbd "b TAB"))
+    (should (equal (minibuffer-contents) "ba"))
+    (execute-kbd-macro (kbd "C-l"))
+    (should (equal (minibuffer-contents) "b"))
+    (execute-kbd-macro (kbd "DEL f C-o C-o C-o"))
+    (should (equal (minibuffer-contents) "foo"))
+    (execute-kbd-macro (kbd "C-l"))
+    (should (equal (minibuffer-contents) "f"))
+    (execute-kbd-macro (kbd "M-<down> M-<down>"))
+    (should (equal (minibuffer-contents) "food"))
+    (execute-kbd-macro (kbd "C-l"))
+    (should (equal (minibuffer-contents) "f"))))
+
 (ert-deftest completion-auto-help-test ()
   (let (messages)
     (cl-letf* (((symbol-function 'minibuffer-message)