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'.
This commit is contained in:
Eshel Yaron 2024-01-20 09:32:29 +01:00
parent 1ab31ae0e5
commit dc4ce1052b
No known key found for this signature in database
GPG key ID: EF3EE9CA35D78618
5 changed files with 177 additions and 25 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -821,6 +821,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

View file

@ -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'."
"<remap> <minibuffer-complete-and-exit>" #'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