From 03686bcffc3a1a5419a7dea7b839015396da98bc Mon Sep 17 00:00:00 2001 From: Eshel Yaron Date: Sun, 14 Jan 2024 15:47:05 +0100 Subject: [PATCH] New commands for cycling completions and restoring completed input 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 | 68 ++++++++++++++++++++++----- doc/lispref/minibuf.texi | 23 +++++++-- etc/NEWS | 18 +++++-- lisp/minibuffer.el | 88 ++++++++++++++++++++++++++++++----- test/lisp/minibuffer-tests.el | 16 +++++++ 5 files changed, 185 insertions(+), 28 deletions(-) diff --git a/doc/emacs/mini.texi b/doc/emacs/mini.texi index 50cd80e72d4..9f2a7893508 100644 --- a/doc/emacs/mini.texi +++ b/doc/emacs/mini.texi @@ -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 diff --git a/doc/lispref/minibuf.texi b/doc/lispref/minibuf.texi index d2cf1ee9215..d81f47418ad 100644 --- a/doc/lispref/minibuf.texi +++ b/doc/lispref/minibuf.texi @@ -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} diff --git a/etc/NEWS b/etc/NEWS index f8e2d9974b6..fdc7d68101e 100644 --- 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'. diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index b948cabdd3d..a3ab123cc9a 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -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 "" #'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 "" #'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 () diff --git a/test/lisp/minibuffer-tests.el b/test/lisp/minibuffer-tests.el index c1fe3032cb5..f3280dbf401 100644 --- a/test/lisp/minibuffer-tests.el +++ b/test/lisp/minibuffer-tests.el @@ -356,6 +356,22 @@ (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- M-")) + (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) -- 2.39.2