Subject: Re: bug#76028: 31; completing-read-multiple: Add prompt indicator

From: Daniel Mendler <[email-masked]>
Date: Tue, 04 Feb 2025 19:57:31 +0100

Juri Linkov <[email-masked]> writes:

>> Another more tricky approach would be to attach text properties to
>> crm-separator which carry the additional data. This would be backward-
>> and forward-compatible also for third-party code, but maybe not very
>> elegant. What do you think?
>
> Although text properties are not customizable, this should not be
> a problem for third-party packages.  And crm-separator is a variable,
> not a defcustom.
>
>>>> Another possibility might be to use the completion category and the
>>>> separator string and convert that to an example, e.g., category face
>>>> would yield the prompt "Describe face (face1,face2,...):".
>>>
>>> Maybe then it's possible to provide the separator and its description
>>> in the metadata?
>>
>> This would be more intrusive. Also the question is if the crm-separator
>> is truly bound to the completion table, or if it should be possible to
>> reuse the same table with different separators.
>
> Then it's possible to override the separator.  But anyway it seems
> text properties are the least intrusive approach.

It seems so. The patch gets quite short. I have attached the updated
patch to this mail.

Daniel

>From 0bc194f982a7fa74ab66f0449a5a3bd651a4b300 Mon Sep 17 00:00:00 2001
From: Daniel Mendler <[email-masked]>
Date: Mon, 3 Feb 2025 16:39:49 +0100
Subject: [PATCH] completing-read-multiple: CRM indication and prompt
 customization

The `completing-read-multiple' prompt indicates multi
completion.  The customization option `crm-prompt' configures
the formatting of the prompt.  The variable can be set to "%p"
in order to only display the original prompt, to "[%d] %p" to
display the separator description and the prompt, or to "[CRM
%s] %p" to display a shorter indicator of only the separator
string and the prompt.

* lisp/emacs-lisp/crm.el (crm-prompt): New user option.
(crm-separator): Update value and docstring.
(completing-read-multiple): Use `crm-prompt' to format the
prompt.
---
 lisp/emacs-lisp/crm.el | 26 +++++++++++++++++++++++---
 1 file changed, 23 insertions(+), 3 deletions(-)

diff --git a/lisp/emacs-lisp/crm.el b/lisp/emacs-lisp/crm.el
index 454c3e85074..3a4e5a944ae 100644
--- a/lisp/emacs-lisp/crm.el
+++ b/lisp/emacs-lisp/crm.el
@@ -79,9 +79,22 @@
 
 (define-obsolete-variable-alias 'crm-default-separator 'crm-separator "29.1")
 
-(defvar crm-separator "[ \t]*,[ \t]*"
+(defvar crm-separator
+  (propertize "[ \t]*,[ \t]*" 'separator "," 'description "comma-separated list")
   "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.  The regexp string can carry the text properties `separator'
+and `description', which if present `completing-read-multiple' will show
+as part of the prompt.  See the variable `crm-prompt'.")
+
+(defcustom crm-prompt "[%d] %p"
+  "Prompt format for `completing-read-multiple'.
+The prompt is formatted by `format-spec' with the keys %d, %s and %p
+standing for the separator description, the separator itself and the
+original prompt respectively."
+  :type 'string
+  :group 'minibuffer
+  :version "31.1")
 
 (defvar-keymap crm-local-completion-map
   :doc "Local keymap for minibuffer multiple input with completion.
@@ -272,7 +285,14 @@ completing-read-multiple
                       (unless (eq require-match t) require-match))
           (setq-local crm-completion-table table))
       (setq input (read-from-minibuffer
-                   prompt initial-input map
+                   (format-spec
+                    crm-prompt
+                    (let* ((sep (or (get-text-property 0 'separator crm-separator)
+                                    (string-replace "[ \t]*" "" crm-separator)))
+                           (desc (or (get-text-property 0 'description crm-separator)
+                                     (concat "list separated by " sep))))
+                      `((?s . ,sep) (?d . ,desc) (?p . ,prompt))))
+                   initial-input map
                    nil hist def inherit-input-method)))
     ;; If the user enters empty input, `read-from-minibuffer'
     ;; returns the empty string, not DEF.
-- 
2.47.2