From 7ce40c4c79c6656bda0e1de65c3f447e014e8aca Mon Sep 17 00:00:00 2001 From: Eshel Yaron Date: Wed, 27 Dec 2023 10:11:36 +0100 Subject: [PATCH] Support interactively sorting minibuffer completions * lisp/minibuffer.el (minibuffer-completions-sort-function): New var. (minibuffer-read-sort-order-with-completion) (minibuffer-completions-sort-orders): New user options. (minibuffer-sort-completions): New command. (minibuffer-local-completion-map): Bind it to 'C-x C-v'. (display-completion-list, minibuffer-completion-help): Take 'minibuffer-completions-sort-function' into account. (completions-header-format): Add '%t' format spec construct, substituted with a description of the current sort order. * lisp/menu-bar.el (minibuffer-local-completion-map): Add menu bar menu entry for sorting completions candidates. * doc/emacs/mini.texi (Completion Commands): Document new command. Improve documentation and indexing of 'minibuffer-complete-and-exit'. (Completion Exit, Completion Options): Update * doc/lispref/minibuf.texi (Completion Commands): Document new user options and command. * etc/NEWS: Announce new feature. --- doc/emacs/mini.texi | 42 ++++++++---- doc/lispref/minibuf.texi | 54 +++++++++++----- etc/NEWS | 7 ++ lisp/menu-bar.el | 5 ++ lisp/minibuffer.el | 135 +++++++++++++++++++++++++++++++++++---- 5 files changed, 203 insertions(+), 40 deletions(-) diff --git a/doc/emacs/mini.texi b/doc/emacs/mini.texi index 72e857a3c0e..d8d57c25007 100644 --- a/doc/emacs/mini.texi +++ b/doc/emacs/mini.texi @@ -353,6 +353,9 @@ arguments that often include spaces, such as file names. @item @key{RET} Submit the text in the minibuffer as the argument, possibly completing first (@code{minibuffer-complete-and-exit}). @xref{Completion Exit}. +@item C-x C-v +Change the order of the list of possible completions +(@code{minibuffer-sort-completions}). @item C-x n n Narrow (restrict) the list of possible completions according to the current minibuffer input @@ -384,6 +387,22 @@ completion is @samp{auto-fill-mode}, but it only inserts @samp{ill-}, giving @samp{auto-fill-}. Another @key{SPC} at this point completes all the way to @samp{auto-fill-mode}. +@kindex RET @r{(completion)} +@findex minibuffer-complete-and-exit + @key{RET} (@code{minibuffer-complete-and-exit}) submits the text in +the minibuffer, or completes it and then submits it, depending on the +``strictness'' of the completion. @xref{Completion Exit}. + +@kindex C-x C-v @r{(completion)} +@findex minibuffer-sort-completions + @key{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 +lets you change the order of the current completions list interactively. +You can invoke it with a negative prefix argument (@kbd{C-- C-x C-v}) to +reverse the current order. + @kindex C-x n n @r{(completion)} @findex minibuffer-narrow-completions-to-current @kbd{C-x n n} (@code{minibuffer-narrow-completions-to-current}) @@ -490,8 +509,8 @@ showing it (@code{kill-current-buffer}). @node Completion Exit @subsection Completion Exit -@kindex RET @r{(completion in minibuffer)} -@findex minibuffer-complete-and-exit +@cindex completion strictness +@cindex strict completion When a command reads an argument using the minibuffer with completion, it also controls what happens when you type @key{RET} (@code{minibuffer-complete-and-exit}) to submit the argument. There @@ -844,15 +863,16 @@ Reference Manual}). The variable @code{completions-header-format} is a format spec string to control the informative line shown before the completions list of candidates, called the @dfn{completions heading line}. Emacs -substitutes @samp{%s} and @samp{%r} constructs that occur in this -string with the number of completion candidates and a description of -the current completions restriction, respectively. @xref{Narrow -Completions}. To suppress the display of the heading line, customize -this variable to @code{nil}. The string that is the value of this -variable can have text properties to change the visual appearance of -the heading line; some useful properties are @code{face} or -@code{cursor-intangible} (@pxref{Special Properties,,Properties with -Special Meanings, elisp, The Emacs Lisp Reference Manual}). +substitutes @samp{%s}, @samp{%t} and @samp{%r} constructs that occur +in this string with the number of completion candidates, the current +completions sort order, and a description of the current completions +restriction, respectively. @xref{Narrow Completions}. To suppress +the display of the heading line, customize this variable to +@code{nil}. The string that is the value of this variable can have +text properties to change the visual appearance of the heading line; +some useful properties are @code{face} or @code{cursor-intangible} +(@pxref{Special Properties,,Properties with Special Meanings, elisp, +The Emacs Lisp Reference Manual}). @vindex completions-highlight-face When @code{completions-highlight-face} names a face, the current diff --git a/doc/lispref/minibuf.texi b/doc/lispref/minibuf.texi index 6178394c50b..27ebadc8ee3 100644 --- a/doc/lispref/minibuf.texi +++ b/doc/lispref/minibuf.texi @@ -1326,17 +1326,42 @@ The list of completions is displayed as text in a buffer named @file{*Completions*}. @end deffn +@deffn Command minibuffer-sort-completions +This function reorders the list of the possible completions. It +prompts you for one of the orders in +@code{minibuffer-completions-sort-orders}, and applies that ordering +to the current completions list. When you call this function +interactively with a negative prefix argument (@kbd{C-- C-x C-v} in +the minibuffer), it reverses the order of the completion candidates. +@end deffn + +@defopt minibuffer-completions-sort-orders +This user option specifies which orders the command +@code{minibuffer-sort-completions} suggests for sorting the +completions list. By default, this includes alphabetical sorting, +sorting by candidate position in the minibuffer history, and no +sorting at all. See the documentation string of this user option for +details about adding new sorting options. +@end defopt + +@deffn Command minibuffer-narrow-completions +This function narrows (restricts) the list of possible completions. +It calls the function that the variable +@code{minibuffer-narrow-completions-function} specifies to get a +completions predicate, and adds that predicate to +@code{minibuffer-completion-predicate}. +@end deffn + @defvar minibuffer-narrow-completions-function The value of this variable is a function that command -@code{minibuffer-narrow-completions} (@kbd{C-x n m} in the minibuffer) -calls to restrict the list of completion candidates. -@code{minibuffer-narrow-completions} calls the function that is the -value of this variable with no arguments, and this function should -return a cons cell @code{(@var{pred} . @var{desc})} where @var{pred} -is a function that takes one argument, a completion candidate, and -returns non-@code{nil} if that candidate should appear in the -@file{*Completions*} list, and @var{desc} is a string describing -@var{pred}. +@code{minibuffer-narrow-completions} calls to restrict the list of +completion candidates. @code{minibuffer-narrow-completions} calls the +function that is the value of this variable with no arguments, and +this function should return a cons cell @code{(@var{pred} +. @var{desc})} where @var{pred} is a function that takes one argument, +a completion candidate, and returns non-@code{nil} if that candidate +should appear in the @file{*Completions*} list, and @var{desc} is a +string describing @var{pred}. @end defvar @defun minibuffer-narrow-completions-by-regexp @@ -1346,14 +1371,6 @@ for a regular expression, and returns a completions predicate that only keeps candidates matching that regular expression. @end defun -@deffn Command minibuffer-narrow-completions -This function narrows (restricts) the list of possible completions. -It calls the function that the variable -@code{minibuffer-narrow-completions-function} specifies to get a -completions predicate, and adds that predicate to -@code{minibuffer-completion-predicate}. -@end deffn - @deffn Command minibuffer-narrow-completions-to-current This is similar to @code{minibuffer-narrow-completions}, except that @code{minibuffer-narrow-completions-to-current} restricts the list of @@ -1404,6 +1421,9 @@ keymap makes the following bindings: @item @key{TAB} @code{minibuffer-complete} +@item C-x C-v +@code{minibuffer-sort-completions} + @item C-x n @code{minibuffer-narrow-completions-map} @end table diff --git a/etc/NEWS b/etc/NEWS index 30bec00ce20..7e42ed68ae6 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -775,6 +775,13 @@ in the minibuffer to restrict the list of possible completions to only include candidates matching the current minibuffer input. See the Info node "(emacs) Narrow Completions" for more information. ++++ +*** New command for reordering the minibuffer completions list. +You can now use 'C-x C-v' ('minibuffer-sort-completions') in the +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. diff --git a/lisp/menu-bar.el b/lisp/menu-bar.el index 8826547921e..d8e4497833a 100644 --- a/lisp/menu-bar.el +++ b/lisp/menu-bar.el @@ -2572,6 +2572,11 @@ It must accept a buffer as its only required argument.") '(menu-item "Restrict to Current Completions" minibuffer-narrow-completions-to-current :help "Restrict completions according to minibuffer input")) + (bindings--define-key map + [menu-bar minibuf minibuffer-sort-completions] + '(menu-item "Sort Completions" + minibuffer-sort-completions + :help "Sort list of completion candidates")) (bindings--define-key map [menu-bar minibuf ?\?] '(menu-item "List Completions" minibuffer-completion-help :help "Display all possible completions")) diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index 4059e81cd6a..0189695463c 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -2172,14 +2172,15 @@ completions." :version "28.1") (defcustom completions-header-format - (propertize "%s possible completions%r:\n" 'face 'shadow) + (propertize "%s possible completions%t%r:\n" 'face 'shadow) "If non-nil, the format string for completions heading line. The heading line is inserted before the completions, and is intended to summarize the completions. The format string may -contain the sequences \"%s\" and \"%r\", which are substituted -with the total count of possible completions and the description -of a description of the current completions restriction, -respectively. If this option is nil, no heading line is shown." +contain the sequences \"%s\", \"%t\" and \"%r\", which are +substituted with the total count of possible completions, the +current completions sort order, and a description of the current +completions restriction. If this option is nil, no heading line +is shown." :type '(choice (const :tag "No heading line" nil) (string :tag "Format string for heading line")) :version "30.1") @@ -2427,6 +2428,44 @@ and with BASE-SIZE appended as the last element." minibuffer-completion-predicate) (when descs (mapconcat #'identity descs ", "))))) +(defvar minibuffer-completions-sort-function nil + "Function for sorting minibuffer completion candidates, or nil. + +When the value of this variable is a function, +`minibuffer-completion-help' uses that function to sort the +completions list instead of using the `display-sort-function' +from the completion table or the value of `completions-sort'. + +`minibuffer-sort-completions' sets the value of this variable to +temporarily override the default completions sorting.") + +(defcustom minibuffer-completions-sort-orders + '((?a "alphabetical" "Sort alphabetically" + minibuffer-sort-alphabetically "sorted alphabetically") + (?h "historical" "Sort by position in minibuffer history" + minibuffer-sort-by-history "sorted by position in minibuffer history") + (?i "identity" "Disable sorting" identity nil) + (?d "default" "Default sort order" nil nil)) + "List of minibuffer completions sort orders. +Each element is a list of the form (CHAR NAME HELP FUNC DESC), +where CHAR is a character that you type to select this sort order +in `minibuffer-sort-completions', NAME is the name of the sort +order, HELP is a short help string that explains what this sort +order does, FUNC is the completions sorting function, and DESC is +a desription that is shown in the *Completions* buffer when the +sort order is in effect. + +FUNC can also be nil, which says to use the default sort order +when you select this sort order." + :version "30.1" + :type '(repeat + (list character string string + (choice function + (const :tag "No sorting" nil) + (const :tag "Use default sort order" identity)) + (choice string + (const :tag "No description" nil))))) + (defun display-completion-list (completions &optional common-substring group-fun) "Display the list of completions, COMPLETIONS, using `standard-output'. Each element may be just a symbol or string @@ -2457,12 +2496,29 @@ candidates." (let ((pred-desc (if-let ((pd (minibuffer--completion-predicate-description))) (concat ", " pd) + "")) + (sort-desc + (if minibuffer-completions-sort-function + (concat + (when-let + ((sd (nth 4 (seq-find + (lambda (order) + (eq + (nth 3 order) + (advice--cd*r + minibuffer-completions-sort-function))) + minibuffer-completions-sort-orders)))) + (concat ", " sd)) + (when (advice-function-member-p + #'reverse minibuffer-completions-sort-function) + ", reversed")) ""))) (with-current-buffer standard-output (goto-char (point-max)) (if completions-header-format (insert (format-spec completions-header-format (list (cons ?s (length completions)) + (cons ?t sort-desc) (cons ?r pred-desc)))) (unless completion-show-help ;; Ensure beginning-of-buffer isn't a completion. @@ -2575,6 +2631,54 @@ The candidate will still be chosen by `choose-completion' unless (with-selected-window window (completions--deselect))))) +(defcustom minibuffer-read-sort-order-with-completion nil + "Whether to use completion for reading minibuffer completions sort order. +If this user options is nil (the default), +`minibuffer-sort-completions' lets you to select a sort order by +typing a single key, which is usually the first letter of the +name of the sort order. If you set this user option to non-nil, +`minibuffer-sort-completions' instead reads the sort order name +in the minibuffer, with completion." + :type 'boolean + :version "30.1") + +(defun minibuffer-sort-completions (arg) + "Sort the list of minibuffer completion candidates. +Prompt for a sort order among +`minibuffer-completions-sort-orders' and apply it to the current +completions list. With negative prefix argument ARG, reverse the +current order instead." + (interactive "p" minibuffer-mode) + (if (< arg 0) + (if (advice-function-member-p + #'reverse minibuffer-completions-sort-function) + (remove-function + (local 'minibuffer-completions-sort-function) #'reverse) + (unless minibuffer-completions-sort-function + (setq-local minibuffer-completions-sort-function + (or (completion-metadata-get + (completion--field-metadata + (car (minibuffer--completion-boundaries))) + 'display-sort-function) + (pcase completions-sort + ('nil #'identity) + ('alphabetical #'minibuffer-sort-alphabetically) + ('historical #'minibuffer-sort-by-history) + ;; It's already a function, use it. + (_ completions-sort))))) + (add-function + :filter-return + (local 'minibuffer-completions-sort-function) #'reverse)) + (setq-local + minibuffer-completions-sort-function + (nth 3 (let ((enable-recursive-minibuffers t)) + (read-multiple-choice + "Sort order" minibuffer-completions-sort-orders + nil nil minibuffer-read-sort-order-with-completion))))) + (when completion-auto-help + (let ((beg-end (minibuffer--completion-boundaries))) + (minibuffer-completion-help (car beg-end) (cdr beg-end))))) + (defun minibuffer-completion-help (&optional start end) "Display a list of possible completions of the current minibuffer contents." (interactive) @@ -2664,13 +2768,19 @@ The candidate will still be chosen by `choose-completion' unless ;; all-completions, not ;; completion-all-completions. Often it's the ;; same, but not always. - (setq completions (if sort-fun - (funcall sort-fun completions) - (pcase completions-sort - ('nil completions) - ('alphabetical (minibuffer-sort-alphabetically completions)) - ('historical (minibuffer-sort-by-history completions)) - (_ (funcall completions-sort completions))))) + (setq completions + (cond + (minibuffer-completions-sort-function + (funcall minibuffer-completions-sort-function + completions)) + (sort-fun + (funcall sort-fun completions)) + (t + (pcase completions-sort + ('nil completions) + ('alphabetical (minibuffer-sort-alphabetically completions)) + ('historical (minibuffer-sort-by-history completions)) + (_ (funcall completions-sort completions)))))) ;; After sorting, group the candidates using the ;; `group-function'. @@ -3091,6 +3201,7 @@ The completion method is determined by `completion-at-point-functions'." "M-" #'minibuffer-previous-completion "M-" #'minibuffer-next-completion "M-RET" #'minibuffer-choose-completion + "C-x C-v" #'minibuffer-sort-completions "C-x n" 'minibuffer-narrow-completions-map) (defvar-keymap minibuffer-local-must-match-map -- 2.39.2