abo-abo / swiper

Ivy - a generic completion frontend for Emacs, Swiper - isearch with an overview, and more. Oh, man!
https://oremacs.com/swiper/
2.31k stars 338 forks source link

ivy with completing-read-multiple (or how to make multiple choices with completing-read) #2895

Open jkitchin opened 3 years ago

jkitchin commented 3 years ago

There are two related questions in this issue:

  1. ivy doesn't seem to handle completing-read-multiple (or at least not the same as it does completing-read). I get a completion buffer, but you have to press TAB to see the candidates, and the keypresses to select candidates are a little more verbose than one might like, e.g. I press TAB to see them, RET on a candidate, press , then TAB to see them again, RET in another candidate, and finally RET to get them. I feel like I should be able to mark candidates and press RET, and use keys like C-M-m/n/p to select them with RET to select them, and no TAB required.

For calls to completing-read, ivy does a beautiful job for selecting a candidate. I have not found a way to make it select multiple candidates though, and I wondered if this is just impossible. It does not work, for example to mark candidates and press return, and it does not work to use keys like C-M-m to select and continue.

Anyway, I wondered if I am missing something in ivy that would make these feasible?

Thanks,

EDIT:

I came up with a solution that uses a modified version of ivy-completing-read. The modifications include augmenting the keymap to make keys like C-M-m/n/p mark candidates instead of acting on them, and at the end if there is anything in ivy-marked-candidates return those candidates instead of a single string. This lets me get a string, or a list of strings from one call.

Now that it is done, I am not sure it is actually a good idea, because it is not a drop in replacement for completing-read since it can return a string, or a list of strings (which surely would break a lot of code after the completing-read call).

However, it does look like an easy way to extend ivy support to completing-read-multiple. Is that something that would be interesting?

basil-conto commented 3 years ago

ivy doesn't seem to handle completing-read-multiple (or at least not the same as it does completing-read)

I guess that's because completing-read-multiple can't be customised as easily as, say, completing-read via completing-read-function, and because there's less interest in completing-read-multiple, or people who use it (like me) don't mind it's current behaviour.

I feel like I should be able to mark candidates and press RET, and use keys like C-M-m/n/p to select them with RET to select them, and no TAB required.

I don't think you can get those perks without rewriting (or emulating) completing-read-multiple in terms of ivy-read (that's the whole premise of the Counsel library).

Anyway, I wondered if I am missing something in ivy that would make these feasible?

Marking and non-exiting completion are already possible, but AFAIK only via the ivy-read API. I recall counsel-org-tag or one of those commands using that kind of UI, for example.

Is that something that would be interesting?

I don't see why not :). Bonus points for submitting an upstream feature request or patch via report-emacs-bug to make completing-read-multiple more tweakable by packages ;). Thanks.

ssnnoo commented 2 years ago

vertico and selectrum seem to have completing-read-multiple support. would be nice if ivy had too :-)

jkitchin commented 2 years ago

I feel I have changed my thinking on the need for ivy to support completing-read-multiple. Since I opened this issue, I have tried using completing-read-multiple, and it is awkward to me compared to being able to mark entries (now supported in ivy) and act on the list.

For example, you can run this:

(ivy-read "t: " '(a b c d) 
      :multi-action (lambda (x) (insert (string-join x ",")))
      :action (lambda (x) (insert x)))

Type C-o and then use m to mark some entries. When you press Return, it will insert a comma delimited list of the marked entries, and if you don't mark any the selected entry.

You also have non-exiting completion as @basil-conto noted, so that is another route to the same end. With these options, completing-multiple-read seems deprecated.

I don't mind if we close this issue.

minad commented 2 years ago

You also have non-exiting completion as @basil-conto noted, so that is another route to the same end. With these options, completing-multiple-read seems deprecated.

FWIW we came to the same conclusion in the context of the alternative completion packages Vertico and Embark. Embark also offers a non-exiting embark-act-all command which makes CRM obsolete. Furthermore CRM is not used very much in Emacs or the ecosystem. It seems like a dead end.

kisaragi-hiu commented 2 years ago

While completing-read-multiple might not be used much, org-set-tags-command uses it to this day and it is very much not deprecated despite the lack of popularity. Although it might not be worth it to offer it as a choice over ivy-mark, it is still worth it to provide a wrapper.

Code hidden because I haven't assigned my copyright and I don't want this to be blocked from inclusion
(defun k/ivy-completing-read-multiple (prompt collection
                                              &optional
                                              predicate require-match initial-input
                                              history def inherit-input-method)
  "A drop-in replacement of `completing-read-multiple' utilizing `ivy-mark'.

PROMPT, COLLECTION, PREDICATE, REQUIRE-MATCH, INITIAL-INPUT,
HISTORY, DEF, and INHERIT-INPUT-METHOD are all forwarded to
`ivy-completing-read'; they are described by `completing-read'.

\\<ivy-minibuffer-map>\\[ivy-done] submits the marked entries; if
no entries are marked, this works the same as
`ivy-completing-read' except the entry is returned in a list for
consistency.

Use \\[ivy-mark] to mark items, \\[ivy-unmark] to unmark, and
\\[ivy-toggle-marks] to toggle all items."
  (let ((answer
         (ivy-completing-read
          prompt collection predicate require-match
          initial-input history def inherit-input-method)))
    (or (->> ivy-marked-candidates
             (--map (substring it (length ivy-mark-prefix)))
             (--map (ivy--call-cand it)))
        (and (not (equal "" answer))
             (list answer)))))

A wrapper can be written that returns ivy-marked-candidates (processed like how ivy--call-marked does it) after a normal ivy-completing-read is done if it is non-nil (some candidates are marked); if no candidates are marked, it can just return the value selected by ivy-completing-read, wrapped in a list to be consistent with the original behavior.