From 18a025e9ba54477da01f27fed18612f3a356217c Mon Sep 17 00:00:00 2001 From: Eshel Yaron Date: Sat, 20 Jan 2024 09:32:29 +0100 Subject: [PATCH] New command 'crm-complete-and-insert-separator' * lisp/emacs-lisp/crm.el (crm-complete-and-insert-separator): New command. (completing-read-multiple-mode-map): Bind it to 'C-,'. (crm-canonical-separator, crm-common-separators): New variables. (completions-multi-mode): Adapt mode line lighter. (crm-complete-and-exit): Only suggest in 'M-x' in 'minibuffer-mode'. (crm-change-separator): (Re)set canonical separator. * doc/lispref/minibuf.texi (Minibuffer Completion): Update. * doc/emacs/mini.texi (Completion Multi): New subsection. (Completion Commands): Fix markup. (Completion) * doc/emacs/emacs.texi (Top): Add menu entry. * etc/NEWS: Announce 'crm-complete-and-insert-separator'. --- doc/emacs/emacs.texi | 1 + doc/emacs/mini.texi | 61 ++++++++++++++++++++- doc/lispref/minibuf.texi | 18 ++---- etc/NEWS | 7 +++ lisp/emacs-lisp/crm.el | 115 +++++++++++++++++++++++++++++++++++---- 5 files changed, 177 insertions(+), 25 deletions(-) diff --git a/doc/emacs/emacs.texi b/doc/emacs/emacs.texi index b9be2de49fd..5b0a5b98d80 100644 --- a/doc/emacs/emacs.texi +++ b/doc/emacs/emacs.texi @@ -295,6 +295,7 @@ Completion * Completion Exit:: Completion and minibuffer text submission. * Completion Styles:: How completion matches are chosen. * Narrow Completions:: Restricting completion candidates. +* Completion Multi:: Providing multiple inputs at once. * Completion Options:: Options for completion. Help diff --git a/doc/emacs/mini.texi b/doc/emacs/mini.texi index 84f648dd999..64d3e81d50f 100644 --- a/doc/emacs/mini.texi +++ b/doc/emacs/mini.texi @@ -293,6 +293,7 @@ Completion}. * Completion Exit:: Completion and minibuffer text submission. * Completion Styles:: How completion matches are chosen. * Narrow Completions:: Restricting completion candidates. +* Completion Multi:: Providing multiple inputs at once. * Completion Options:: Options for completion. @end menu @@ -489,8 +490,8 @@ there are. @kindex C-x / @r{(completion)} @findex minibuffer-set-completion-styles - @kbd{C-x /} (minibuffer-set-completion-styles) lets you set the -completion styles for the current minibuffer. @xref{Completion + @kbd{C-x /} (@code{minibuffer-set-completion-styles}) lets you set +the completion styles for the current minibuffer. @xref{Completion Styles}. This command prompts you for a list of completion styles, and sets that list as the effective completion styles for following completion operations in the current minibuffer. With a plain prefix @@ -822,6 +823,62 @@ If you invoke this command with a prefix argument (@kbd{C-u C-x n w}), it removes all restrictions without prompting, regardless of how many there are. +@node Completion Multi +@subsection Read and Complete Multiple Inputs + +@cindex multiple inputs, with completion + Some commands read @emph{multiple inputs} from the minibuffer at +once. For example, @kbd{M-x describe-face} prompts you for @emph{one +or more} face names, and displays a help buffer describing all given +faces. @xref{Faces}. This works just like reading a single inputs, +except that you can type several inputs in the minibuffer, separating +them with @dfn{input separators}. You can use completion to fill in +the individual inputs, as usual. + +@cindex input separators, for reading multiple inputs +@cindex separator pattern, for reading multiple inputs + The input separator is typically a comma (@samp{,}), so if a command +@kbd{M-x foo} reads multiple inputs, and you type @kbd{bar,baz,spam +@key{RET}} in the minibuffer, then @code{foo} gets a list of three +inputs: @samp{bar}, @samp{baz} and @samp{spam}. More generally, Emacs +treats a part of your minibuffer input as an input separator when it +matches the current @dfn{separator pattern}---a regular expression +that may change from command to command. The default separator +pattern, which most commands use, matches a comma along with any +surrounding spaces or tabs. When reading multiple inputs, the +@file{*Completions*} buffer displays the @samp{Multi} indicator in the +mode line. You can hover over that indicator with the mouse to get +help about the current input separator pattern. + + The following commands are available in the minibuffer while reading +multiple inputs: + +@kindex C-x , +@findex crm-change-separator + @kbd{C-x ,} (@code{crm-change-separator}) prompts you for a regular +expression, and sets the current input separator pattern to that +regular expression. Use this command if the default separator pattern +is inconvenient or mistakes a part of your input to be a separator. +With a prefix argument, that is if you type @kbd{C-u C-x ,}, this +command also replaces all current input separators in the minibuffer +with a new separator that you provide. + +@kindex C-, +@findex crm-complete-and-insert-separator + @kbd{C-,} (@code{crm-complete-and-insert-separator}) completes +partial inputs in the minibuffer, and then inserts a new input +separator at the end of the minibuffer and puts point after it, for +you to type another input. In order to insert a separator for you, +this command must get a hold of a string that matches the current +separator pattern. Some commands that read multiple inputs specify a +so-called @dfn{canonical separator}, in which case @kbd{C-,} uses the +canonical separator. Otherwise, this command tries to find an +appropriate separator by looking at your current input, and applying +some heuristics that work for common separator patterns. In case +@code{C-,} cannot figure out which separator to insert by itself, it +prompts you for a separator and remembers your choice as the canonical +separator for the current minibuffer. + @node Completion Options @subsection Completion Options diff --git a/doc/lispref/minibuf.texi b/doc/lispref/minibuf.texi index d81f47418ad..eff81be37cf 100644 --- a/doc/lispref/minibuf.texi +++ b/doc/lispref/minibuf.texi @@ -1270,20 +1270,14 @@ The value of this variable is a regular expression that matches @code{completing-read-multiple} input separators. By default, this is set to @samp{[ \t]*,[ \t]*}, which means that a comma, possibly surrounded by spaces or tabs, separates -@code{completing-read-multiple} inputs. +@code{completing-read-multiple} inputs. You can also set this +variable to a cons cell @code{(@var{regexp} . @var{canonical})}, where +@var{regexp} is the regular expression for matching separators, and +@var{canonical} is a ``canonical'' separator string that Emacs uses +when it inserts a separator in behalf of the user. If @var{canonical} +does not match @var{regexp}, @var{canonical} is ignored. @end defvar -@deffn Command crm-change-separator -This command, bound to @kbd{C-x ,} in the minibuffer during -@code{completing-read-multiple}, changes the current input separator. -It prompts for a new separator regular expression, and sets the local -value of @code{crm-separator} to that regular expression. With a -prefix argument, this command also prompts for a replacement string -(that should match the new separator) and replaces all of the existing -separators in the minibuffer with that replacement string. -@end deffn - - @node Completion Commands @subsection Minibuffer Commands that Do Completion diff --git a/etc/NEWS b/etc/NEWS index ffee287b230..ee02f90bf57 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -809,6 +809,13 @@ This command lets you change the separator that strings. 'completing-read-multiple' binds 'C-x ,' to 'crm-change-separator' in the minibuffer. ++++ +*** New command 'crm-complete-and-insert-separator'. +This command, bound to 'C-,' in 'completing-read-multiple' +minibuffers, completes partial inputs that are already in the +minibuffer, and inserts a new separator at the end of the minibuffer +for you to insert a another input. + +++ *** New command 'minibuffer-set-completion-styles'. This command, bound to 'C-x /' in the minibuffer, lets you set the diff --git a/lisp/emacs-lisp/crm.el b/lisp/emacs-lisp/crm.el index fe69d073593..f9a2a1f049c 100644 --- a/lisp/emacs-lisp/crm.el +++ b/lisp/emacs-lisp/crm.el @@ -85,18 +85,35 @@ (defvar crm-separator "[ \t]*,[ \t]*" "Separator regexp used for separating strings in `completing-read-multiple'. -It should be a regexp that does not match the list of completion candidates.") +It should be a regexp that does not match the list of completion candidates. + +This can also be a cons cell (REGEXP . CANONICAL), where REGEXP +is the separator regexp used for matching input separators, and +CANONICAL is a canonical separator string that Emacs uses when it +inserts a separator for you. If CANONICAL does not match REGEXP, +it is ignored. See also `crm-complete-and-insert-separator'.") + +(defvar crm-common-separators '(",") + "List of strings often used to separate multiple minibuffer inputs. + +See also `crm-complete-and-insert-separator'.") (defvar crm-current-separator nil "The value of `crm-separator' for the current minibuffer.") +(defvar crm-canonical-separator nil + "Canonical separator for `completing-read-multiple'. + +This can either a string that matches `crm-current-separator', or +nil when there is no canonical separator.") + (defun crm-complete-and-exit () "If all of the minibuffer elements are valid completions then exit. All elements in the minibuffer must match. If there is a mismatch, move point to the location of mismatch and do not exit. This function is modeled after `minibuffer-complete-and-exit'." - (interactive) + (interactive "" minibuffer-mode) (let ((bob (minibuffer--completion-prompt-end)) (doexit t)) (goto-char bob) @@ -177,20 +194,91 @@ for REP as well." (goto-char (minibuffer-prompt-end)) (while (re-search-forward crm-current-separator nil t) (replace-match rep t t))) - (setq crm-current-separator sep) + (setq crm-current-separator sep crm-canonical-separator rep) (when (get-buffer-window "*Completions*" 0) ;; Update *Completions* to avoid stale `completion-base-affixes'. (minibuffer-completion-help))) +(defun crm-complete-and-insert-separator () + "Complete partial inputs and then insert a new input separator. + +If `crm-canonical-separator' is non-nil and matches the regular +expression `crm-current-separator', then this command uses +`crm-canonical-separator' as the separator. Otherwise, this +command tries to find an appropriate separator by matching +`crm-current-separator' against your current input and against +the list of common separators in `crm-common-separators', and if +that fails this command prompts you for the separator to use." + (interactive "" minibuffer-mode) + (let ((bob (minibuffer--completion-prompt-end)) + (all-complete t) + (enable-recursive-minibuffers t)) + ;; Establish a canonical separator string, so we can insert it. + (setq crm-canonical-separator + (or + ;; If `crm-canonical-separator' matches, use it. + (and (stringp crm-canonical-separator) + (string-match-p crm-current-separator + crm-canonical-separator) + crm-canonical-separator) + ;; If there's some separator already, use that. + (and (save-excursion + (goto-char bob) + (re-search-forward crm-current-separator nil t)) + (buffer-substring-no-properties (match-beginning 0) + (match-end 0))) + ;; If any common separator matches, use it. + (seq-some (lambda (sep) + (and (string-match-p crm-current-separator sep) + sep)) + crm-common-separators) + ;; Ask the user for help. + (read-string-matching crm-current-separator + "Separate inputs with: "))) + (while + (and all-complete + (let* ((beg (save-excursion + (if (re-search-backward crm-current-separator bob t) + (match-end 0) + bob))) + (end (copy-marker + (save-excursion + (if (re-search-forward crm-current-separator nil t) + (match-beginning 0) + (point-max))) + t))) + (goto-char end) + (setq all-complete nil) + (completion-complete-and-exit + beg end (lambda () (setq all-complete t))) + (goto-char end) + (not (eobp))) + (looking-at crm-current-separator)) + (when all-complete + (goto-char (match-end 0)))) + (when all-complete + (if (looking-back crm-current-separator bob) + ;; Separator already present, show completion candidates. + (minibuffer-completion-help) + (insert crm-canonical-separator))))) + (define-minor-mode completions-multi-mode "Minor mode for reading multiple strings in the minibuffer." :lighter (:eval - (propertize " Multi" 'help-echo - (concat - "Insert multiple inputs by separating them with \"" - (buffer-local-value 'crm-current-separator - completion-reference-buffer) - "\"")))) + (let ((canonical + (buffer-local-value 'crm-canonical-separator + completion-reference-buffer))) + (propertize + (concat + " Multi" + (when canonical (concat "[" crm-canonical-separator "]"))) + 'help-echo + (concat + "Insert multiple inputs by separating them with \"" + (or canonical + (buffer-local-value 'crm-current-separator + completion-reference-buffer)) + "\""))))) (defun crm-completion-setup () "Enable `completions-multi-mode' in *Completions* buffer." @@ -205,7 +293,8 @@ for REP as well." (defvar-keymap completing-read-multiple-mode-map :doc "Keymap for `completing-read-multiple-mode'." " " #'crm-complete-and-exit - "C-x ," #'crm-change-separator) + "C-x ," #'crm-change-separator + "C-," #'crm-complete-and-insert-separator) (define-minor-mode completing-read-multiple-mode "Minor mode for reading multiple strings in the minibuffer." @@ -235,7 +324,11 @@ contents of the minibuffer are \"alice,bob,eve\" and point is between This function returns a list of the strings that were read, with empty strings removed." - (let ((crm-current-separator crm-separator)) + (let ((crm-current-separator + (if (consp crm-separator) + (car crm-separator) + crm-separator)) + (crm-canonical-separator (cdr-safe crm-separator))) (split-string (minibuffer-with-setup-hook #'completing-read-multiple-mode -- 2.39.2