Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alternative multiple candidate select UI? #489

Closed
bdarcus opened this issue Mar 7, 2021 · 48 comments
Closed

Alternative multiple candidate select UI? #489

bdarcus opened this issue Mar 7, 2021 · 48 comments

Comments

@bdarcus
Copy link
Contributor

bdarcus commented Mar 7, 2021

I'm working on this package, which is sort of a port of helm-bibtex to completing-read, aimed at selectrum (and icomplete-vertical).

My core completion function uses completing-read, but I think I need to change it to use completing-read-multiple, as the backend functions all take multiple keys as parameter.

But I don't think selectrum's current UI for this will work, as the completion strings are very long.

Notice what happens in this screenshot at the top:

Now, compare to helm-bibtex, where one does ctrl-space to mark the candidates.

Screenshot from 2021-03-07 02-54-08

Is there any reason this wouldn't be a more general and consistent approach?

If not, is there any workaround you can suggest?

cc oantolin/embark#166 emacs-citar/citar#17

@clemera
Copy link
Collaborator

clemera commented Mar 7, 2021

We could think about combing both approaches, we had a selection feature in the early days see the #74 for more discussion about the current UI.

@clemera
Copy link
Collaborator

clemera commented Mar 7, 2021

Right now the candidates are simply removed from the list, maybe it would be better to leave them there but show them selected?

@clemera
Copy link
Collaborator

clemera commented Mar 7, 2021

Hm, I don't think there would be a benefit, could you describe more what you don't like about the current UI? Maybe you are just annoyed about the scrolling of the prompt as the candidates are so long?

@minad
Copy link
Contributor

minad commented Mar 7, 2021

Yes, the candidates are too long. Given a vertical ui long candidates are common. People are developing applications which produce long candidates as in this case with bibtex.

@clemera
Copy link
Collaborator

clemera commented Mar 7, 2021

The prompt gets scrolled for me in this case.

@clemera
Copy link
Collaborator

clemera commented Mar 7, 2021

Maybe it is a problem when you want to inspect your selections visually or am I missing something?

@bdarcus
Copy link
Contributor Author

bdarcus commented Mar 7, 2021

I took a quick look at #74 (thanks), but it seemed like @oantolin's critiques of the earlier UI (which I never saw) were more implementation than basic concept?

Hm, I don't think there would be a benefit, could you describe more what you don't like about the current UI? Maybe you are just annoyed about the scrolling of the prompt as the candidates are so long?

It's more than "annoyed"; it seems initially completely impractical for this use case :-)

Let me elaborate on the use case.

When writing book or article manuscript, I need to be able to very quickly and easily, so as to avoid interrupting the writing process, select and insert citations.

Sometimes citations have one key, often more.

With the helm-bibtex UI, it's the same UI for one candidate as it is for multiple; the only difference is a keybinding to select multiple.

I can instantly see what I have selected, and so what citation will be inserted at point.

Not so with selectrum. I can only see one selection at a time, and I have to switch UI context to be able to scroll.

Perhaps different selection UIs are optimized for different use cases?

@clemera (who posted this after I started replying)

Maybe it is a problem when you want to inspect your selections visually or am I missing something?

Yes. Say I have 2000 references in my bibtex file(s). I need to be able to select the exact three that I need to insert as a citation at point, and those may be pretty similar to others I don't want (same author, say). Being able to see all I have selected at once allows me to confirm I have the right ones.

Does that explain better?

@clemera
Copy link
Collaborator

clemera commented Mar 7, 2021

I see, another improvement in this use case would be to select multiple items without resetting the input between as discussed in #395 (I forgot about that one and I also needed some explanation there, I just have no such use case 😆) so you can pick the similar items right away without repeating previous inputs.

@clemera
Copy link
Collaborator

clemera commented Mar 7, 2021

The previous UI worked similar to what you are suggesting, the critique by @oantolin back then was also about the UI itself, there are use cases for both it would be nice to allow both in tandem is some way.

@oantolin
Copy link

oantolin commented Mar 7, 2021

@bdarcus

I can instantly see what I have selected, and so what citation will be inserted at point.

Is that really true in Helm? Does it move selected candidates closer together or something? I thought that if you had 2000 references in your bibtex file and you wanted to cite references 21, 803 and 1831 you could not see in Helm at a glance which were selected, you'd have to scroll like a madman to see which one are selected. Am I wrong?

EDIT: Well, I guess if all the chosen references have some text in common, you could type that into Helm to bring the selected ones closer to each other.

@bdarcus
Copy link
Contributor Author

bdarcus commented Mar 7, 2021

Is that really true in Helm? Does it move selected candidates closer together or something?

Oh, good question @oantolin. No, it doesn't.

In that workflow, I'm assuming I'd narrow to some manageable subset.

Well, I guess if all the chosen references have some text in common, you could type that into Helm to bring the selected ones closer to each other.

Yes, this.

The most common case is I'm citing a person's work, for example, so I narrow on the references of that person.

@minad
Copy link
Contributor

minad commented Mar 7, 2021

What about moving the selected items to the top such that they are always visible if not scrolled out of sight?

@bdarcus
Copy link
Contributor Author

bdarcus commented Mar 7, 2021

What about moving the selected items to the top such that they are always visible if not scrolled out of sight?

First thought is that would be an improvement on the helm version.

@oantolin
Copy link

oantolin commented Mar 7, 2021

I think that for this particular situation, I'd like the standard completing-read-multiple interface but with each selected candidate displayed as just the bibtex key. The only issue right now is that the text in the minibuffer prompt is too long, no?

@clemera
Copy link
Collaborator

clemera commented Mar 7, 2021

@oantolin Currently the input resets after inserting a candidate with ´TAB` so the previous search matches are gone, if you want to select multiple similar candidates (contained in you current set of matches) you need to input the same text again, for this use case selection would be more convenient.

@bdarcus
Copy link
Contributor Author

bdarcus commented Mar 7, 2021

I think that for this particular situation, I'd like the standard completing-read-multiple interface but with each selected candidate displayed as just the bibtex key.

I had wondered about that. Is that possible?

That could be an elegant solution, particularly if coupled with @minad's on moving to the top of the list.

And if there was some way that I could see which key was associated with which candidate somehow.

@minad
Copy link
Contributor

minad commented Mar 7, 2021

I also thought about that proposal by @oantolin. I don't think it is possible. The candidates are long, there is no way to communicate a short form to the completion system. You could invent an extension but if that can be avoided it would be better.

@oantolin
Copy link

oantolin commented Mar 7, 2021

@clemera makes a good point, that if the candidates you want to select do have some text in common, marking several candidates is more convenient than have to type the text again for each one.

I also don't think my suggestion of using the crm interface but dislaying just the bibtex key in the minibuffer is very practical: I hadn't noticed the bibtex keys aren't displayed with the candidates so using them to summarize the chosen entry isn't a very good idea idea, since you'd still need a way for the user to visually confirm those keys correspond to the entries they meant to select.

One similar thing that could be done is to put a function in the minibuffer's after-change-functions that uses display text properties to display the chosen candidates as just the first word and an ellipsis. (Like how consult--fry-the-tofus makes tofu invisible, @minad.)

@oantolin
Copy link

oantolin commented Mar 7, 2021

Perhaps instead of shortening the chosen candidates they could be displayed one per line (by displaying the crm-separator as (concat crm-separator "\n") or something like that)? This would be a lot like moving the selected ones to the top of list.

@minad
Copy link
Contributor

minad commented Mar 7, 2021

Yes, but this seems less convenient than having multiple candidates listed at top which can be selected/deselected with a keypress.

@oantolin
Copy link

oantolin commented Mar 7, 2021

True.

@bdarcus
Copy link
Contributor Author

bdarcus commented Mar 7, 2021

Right now the candidates are simply removed from the list, maybe it would be better to leave them there but show them selected?

I missed this earlier, but just to clear, yes; that would be a start.

Edit: not sure if this is what you're thinking, @clemera, but it seems like it might be not so hard conceptually to bring these together.

Like, at a basic level (I know there are details and nuances):

  1. Common way to show selected candidates, instead of hide them, at top of list.
  2. A choice on selection whether to use the TAB-based completion approach, or a keybinding to select and deselect directly. At least the latter could include the select-all request in completing-read-multi can't select all, resets after each selection #395.

@bdarcus
Copy link
Contributor Author

bdarcus commented Mar 7, 2021

Just curious: how do you deal with this in icomplete-vertical, @oantolin?

@oantolin
Copy link

oantolin commented Mar 7, 2021

icomplete-vertical has no special behavior for completing-read-multiple, @bdarcus, it behaves exactly as icomplete does. You build a comma-separated list of chosen candidates in the minibuffer with completion for each one. So it's like Selectrum except that it doesn't remove chosen candidates from the candidate list below.

Note that removal of chosen candidates in Selectrum, while probably a good idea, feels a little "wrong" since you are allowed to select the same candidate many times in completing-read-multiple, and Selectrum's UI sort of suggests you aren't allowed. (Selectrum doesn't actually keep you from choosing a candidate multiple times, but you do loose completion for it after the first occurrence ---you can still type it out in file as many times as you want.)

@bdarcus
Copy link
Contributor Author

bdarcus commented Mar 8, 2021

One detail: I saw grouping come up here recently (see #458).

How would this interact with that?

Would, for example, selected candidates be a group?

Could have some advantages, like allowing a selection to be visible even if the user modified the filtering to otherwise hide a candidate?

@clemera
Copy link
Collaborator

clemera commented Mar 8, 2021

@bdarcus
Ideally both use cases would be supported by the UI without having to configure a choice (not sure if that was what you are suggesting in you second point). I'm not sure about mixing selections with the current approach, ideally we have both actions available and also being as intuitive as possible. Maybe for reviewing/editing your selections it would also suffice to have better prompt navigation/editing commands (like we have with sexp commands in file completions)? For keeping the input we could have a second command bound to Shift+ TAB which insert the current candidate + the current input how does that sound? If you want to select a candidate and start completion from scratch you would use TAB and in the other case SHIFT + TAB.

@bdarcus
Copy link
Contributor Author

bdarcus commented Mar 8, 2021

@clemera

Ideally both use cases would be supported by the UI without having to configure a choice (not sure if that was what you are suggesting in you second point).

I was leaving that deliberately vague because unsure. But I'm fine with your preference.

I'm not sure about mixing selections with the current approach, ideally we have both actions available and also being as intuitive as possible.

Yes.

Maybe for reviewing/editing your selections it would also suffice to have better prompt navigation/editing commands (like we have with sexp commands in file completions)?

I'm not following on this.

For keeping the input we could have a second command bound to Shift+ TAB which insert the current candidate + the current input how does that sound?

So in my UI, how would I select two candidates to run the default action?

  • highlight one, do Shift+TAB
  • highlight the other, do Shift + TAB
  • hit return?

What would be the indication they were selected? The two candidates highlighted, at the top of the list, perhaps in a group?

And if I was using some other commands, where it was easier to use the TAB approach, I would:

  • hit TAB, complete one candidate to select
  • , complete another candidate
  • hit return

If that's what you mean, that sounds good.

Edit: and de-select would be do shift + TAB again?

@clemera
Copy link
Collaborator

clemera commented Mar 8, 2021

I'm not following on this.

In file completions you can move and kill path levels using standard sexp commands I was suggesting allowing the same for crm so you could easily move through your "selections" and kill items to "unselect".

So in my UI, how would I select two candidates to run the default action?

You press SHIFT + TAB and the item gets inserted like with TAB but additionally the current input gets inserted so you can proceed from where you left off.

What would be the indication they were selected? The two candidates highlighted, at the top of the list, perhaps in a group?

They would be remove from the list and added to the prompt like with TAB (in addition to that the input would get inserted as mentioned above.

Edit: and de-select would be do shift + TAB again?

To "deselect" you would backward-kill-sexp like killing a path level in file completions.

I'm suggesting not showing visual selections but incorporating the advantages of a selection UI into the current UI.

@bdarcus
Copy link
Contributor Author

bdarcus commented Mar 8, 2021

So then how would this address my concerns with the long completions in that UI?

Yes, I don't have to restart the completions, but as discussed above, that's only one part of the problem.

I'm suggesting not showing visual selections but incorporating the advantages of a selection UI into the current UI.

And what are those advantages for my use case?

@clemera
Copy link
Collaborator

clemera commented Mar 8, 2021

And what are those advantages for my use case?

I thought that you can quickly select similar items which match the same string and inspect/edit your selected candidates (one by one) but it seems that wasn't your main interest, I just tried to projecting what is missing and find a way to add that, I'm sorry if it doesn't match your view on it or if it doesn't solve your pain points, what do you feel is missing? Is it that you can't easily see all selections at once? What is the advantage of seeing all the selections at once?

@clemera
Copy link
Collaborator

clemera commented Mar 8, 2021

So then how would this address my concerns with the long completions in that UI?

If we have sexp commands for candidate navigation within the prompt you can navigate to the start of a candidate using C-M-b

@bdarcus
Copy link
Contributor Author

bdarcus commented Mar 8, 2021

I'm sorry if it doesn't match your view on it or if it doesn't solve your pain points, what do you feel is missing? Is it that you can't easily see all selections at once? What is the advantage of seeing all the selections at once?

I appreciate your openness to continued discussion :-)

Let me wait a bit to answer your questions, and in the interim see if @minad has any thoughts that can bridge the gap.

EDIT: I'll also add an example from my own data of what the actual candidate strings look like (with your help on reddit, I use a different string for the display using the display property, which has a little less data, but is formatted for full-width display):

"✎ climate change,geo211,geography future 2019-02-19 Wallace-Wells, David The Uninhabitable Earth: Life After Warming book wallace-wells_2019"

@clemera
Copy link
Collaborator

clemera commented Mar 8, 2021

EDIT: I just reread your comment above and noticed you already mentioned seeing a single item a time doesn't work out nicely for you.

Here is another idea which is similar to what @minad was suggesting by showing items at the top of the list: We could show a stack of the inserted candidates above the prompt (between the input line and the mode-line) that would grow up when you add another candidate. You could maybe also navigate into that collected stack and pop items back (not sure how this would feel in practice but maybe this would work out).

@bdarcus
Copy link
Contributor Author

bdarcus commented Mar 8, 2021

We could show a stack of the inserted candidates above the prompt (between the input line and the mode-line) that would grow up when you add another candidate.

So that "stack" would be just below the "doom:scratch" line here?

image

So conceptually like the grouping idea I was asking about, but I guess there are technical reasons this might work better?

EDIT: And so, as you select candidates (and I can do shift + TAB to do that?), the UX is they move up, from the candidate list to that stack?

That sounds helpful to me in addressing the core issue; maybe even really cool!

@clemera
Copy link
Collaborator

clemera commented Mar 8, 2021

So that "stack" would be just below the "doom:scratch" line here?

Yup, that would be the idea.

EDIT: And so, as you select candidates (and I can do shift + TAB to do that?), the UX is they move up, from the candidate list to that stack?

Yes, you could also just press TAB (in that case we could delete the input and add the candidate to the stack) when you press SHIFT + TAB the candidate would also move to the stack but your input would stay so you can keep adding items from the current result set.

@bdarcus
Copy link
Contributor Author

bdarcus commented Mar 8, 2021

Sounds good to me.

Only other question I have (which you were contemplating above) is how to make the deselect process as simple and elegant as the select process.

EDIT: And, of course, I'd be eager to test it to see how smoothly it all worked.

Might be so simple as one keypress to migrate to the stack, select item to deselect, and then same shift + TAB to deselect, which removes it? Given the different context, would there be any point in a different key combination?

BTW, here's the documentation section for describing how this works in helm-bibtex and ivy-bibtex for comparison (if I read the latter correctly, the ivy version doesn't have true multi-selection):

https://github.com/bdarcus/helm-bibtex#apply-actions-to-multiple-entries

@clemera
Copy link
Collaborator

clemera commented Mar 8, 2021

Might be so simple as one keypress to migrate to the stack, select item to deselect, and then same shift + TAB to deselect, which removes it? Given the different context, would there be any point in a different key combination?

Maybe removing the last added candidate could be done with DEL which is usually a no-op at the empty prompt. To deselect other candidates than the last one you could navigate to them using C-p and press DEL on them. I will try a POC when I have the time in the meantime PRs are welcome of course, too.

@clemera
Copy link
Collaborator

clemera commented Mar 10, 2021

There is one problem with history and pasting strings into the crm prompt those should both continue to work, mabye some kind of hybrid solution would be best? What about a command which shows the items currently in the prompt temporarily instead of the static visualization we talked about?

@bdarcus
Copy link
Contributor Author

bdarcus commented Mar 10, 2021

There is one problem with history and pasting strings into the crm prompt those should both continue to work, mabye some kind of hybrid solution would be best?

In theory, that seems to make sense.

I know how the prompt approach works now, but how would this alternative in relation?

How do you think to allow the hybrid, without having them conflict? I was just wondering what would happen, for example, if one selected some candidates using the direct approach, and then did TAB for the prompt.

What about a command which shows the items currently in the prompt temporarily instead of the static visualization we talked about?

Are you thinking it would be redundant or waste real estate to disclose statically when using the prompt?

And how would that visualization compare to what we earlier discussed? Where would they be disclosed? The same idea, just that one could toggle?

@clemera
Copy link
Collaborator

clemera commented Mar 11, 2021

I hadn't thought above the concerns I mentioned (navigating history/pasting) before. There is also the case that you use an unknown crm-separator, currently you can simply type it manually but in the above approach you would need to query the user for it on exit.

Maybe a third approach could be to select the candidates like you do currently and on exit get a buffer where you can inspect and change the submitted selections before finally submitting them?

I think this all needs some experimentation, we can discuss more details when we have an implementation to play with.

@bdarcus
Copy link
Contributor Author

bdarcus commented Mar 11, 2021

There is also the case that you use an unknown crm-separator, currently you can simply type it manually but in the above approach you would need to query the user for it on exit.

I was thinking about this yesterday too, as I had to add code to my package to use a different crm-separator (an ampersand, because my long candidate strings can have different punctuation, maybe | or || would be a more general separator for these sorts of candidates?).

Ideally I could tell selectrum what to use in my code (say with selectrum--crm-separator-alist?), as that would be a cleaner UX. As far as I'm concerned, things like crm-separators are internal details that users shouldn't need to worry about.

I think this all needs some experimentation, we can discuss more details when we have an implementation to play with.

Sounds good.

@bdarcus
Copy link
Contributor Author

bdarcus commented Apr 29, 2021

I thought I'd just add another note here on a pain point with the current UI, that I think would be solved with the direction this was going.

I needed to add an initial-value argument to some of my commands to pre-narrow the candidate list.

This works well when selecting one candidate, but does not when selecting multiple, since the initial value only applies to the first.

Also, I just linked here from another issue to do with history for these long candidates, which is another pain point.

@minad
Copy link
Contributor

minad commented Jul 4, 2021

See emacs-citar/citar#160 for an alternative idea for an improved CRM ui, which can be implemented cheaply on top of completing-read.


I thought maybe it is better to give up on crm and use something else based directly on completing-read. Try this:

(defun simple-crm (prompt candidates)
  (let ((selected-list)
        (selected-hash (make-hash-table :test #'equal))
        (items candidates))
    (while
        (let ((item
               (completing-read
                (format "%s (%s/%s): " prompt
                        (hash-table-count selected-hash)
                        (length candidates))
                (lambda (str pred action)
                  (if (eq action 'metadata)
                      `(metadata (display-sort-function . ,#'identity)
                                 (cycle-sort-function . ,#'identity)
                                 (group-function
                                  . ,(lambda (cand transform)
                                       (cond
                                        (transform cand)
                                        ((get-text-property 0 'simple-crm-selected cand) "Selected")
                                        (t "Not selected")))))
                    (complete-with-action action items str pred)))
                nil
                t
                nil
                nil
                "")))
          (unless (equal item "")
            (cond
             ((gethash item selected-hash)
              (remhash item selected-hash)
              (setq selected-list (delete item selected-list)))
             (t
              (puthash item t selected-hash)
              (setq selected-list (nconc selected-list
                                         (list (propertize item 'face 'region
                                                           'simple-crm-selected t))))))
            (setq items
                  (append selected-list
                          (seq-remove (lambda (x)
                                        (gethash x selected-hash))
                                      candidates)))
            t)))
    selected-list))

(simple-crm "Select multiple" '("first" "second" "third" "fourth"))

Fortunately there is also no need to implement a custom completion table. I wonder if such a functionality deserves to be provided by some package.

@oantolin
Copy link

oantolin commented Jul 5, 2021

Your simple-crm is great @minad! I have RET configured to always select the first candidate, so I need to force an empty input to exit it, but that seems perfectly reasonable.

(By the way, I somehow I thought RET in Selectrum and Vertico also always selected the first candidate or the selected candidate if you move the selection, but it seems I was wrong. What does it do? Select the default if non-nil and otherwise select the first candidate? And here you set the default to be the empty string?)

@bdarcus
Copy link
Contributor Author

bdarcus commented Jul 5, 2021

@oantolin

Your simple-crm is great @minad!

Yes, that was my response too!

Since this comment, the thread on the linked issue got kind of long and complicated (though I think interesting!), but I think to summarize:

This should work well for my package, and so I am working on adding it here:

emacs-citar/citar#163

But there are a few minor issues, that probably won't matter in my case.

OTOH, generalizing it for inclusion in Consult (which @minad had been seriously thinking of doing), and/or Vertico/Selectrum, will require more thought, and work, which may not be worth it in the face of other priorities, etc.

This comment gives a good overview of the open questions for him:

emacs-citar/citar#160 (comment)

I don't think he's closing the door on the idea though.

@minad
Copy link
Contributor

minad commented Jul 5, 2021

@oantolin

By the way, I somehow I thought RET in Selectrum and Vertico also always selected the first candidate or the selected badidate of you move the selection, but it seems I was wrong.

In the case of Vertico, RET returns the currently selected candidate (vertico--index). The index is determined either explicitly by the user by moving up/down, then vertico--lock-candidate is set to t, such that even when the input is changed, the current candidate remains locked as long as the candidate still satisfies the filter. In case no candidate is selected explicitly (for example during initialization), either the prompt is selected (-1) or the first candidate is selected (0).

;; select either the prompt or the first candidate
(if (or vertico--default-missing ;; prompt is empty and default is not an element of all completions
        (= 0 vertico--total)     
        (and (= (car bounds) (length content)) ;; input is a valid completion
             (test-completion content minibuffer-completion-table
                              minibuffer-completion-predicate)))
    -1 0)

When the prompt is selected, the prompt string is submitted by RET. In case the prompt string is empty, completing-read returns the default value. Selectrum does something along these lines too.

@minad
Copy link
Contributor

minad commented Jul 5, 2021

@oantolin What do you think about consult-completing-read-multiple as part of Consult, see https://github.com/minad/consult/blob/3d28108d841a698bf2ec42e32fa954adbb88a87a/consult.el#L2154-L2226?

@minad
Copy link
Contributor

minad commented Jul 7, 2021

Solved by consult-completing-read-multiple. Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

4 participants