Selectrum: Alternative multiple candidate select UI?

Created on 7 Mar 2021  Â·  43Comments  Â·  Source: raxod502/selectrum

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 https://github.com/oantolin/embark/issues/166 https://github.com/bdarcus/bibtex-actions/issues/17

Most helpful comment

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.

All 43 comments

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.

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

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?

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.

The prompt gets scrolled for me in this case.

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

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?

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 :laughing:) so you can pick the similar items right away without repeating previous inputs.

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.

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

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.

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

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.

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?

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

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.

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.

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

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.

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

True.

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 #395.

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

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.)

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?

  • Common way to show selected candidates, instead of hide them, at top of list.

    • A choice on selection whether to use the TAB-based completion approach, or a keybinding to select and deselect. The latter could include the select-all request in #395.

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

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

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.

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?

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?

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

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"

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).

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!

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.

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

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.

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?

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?

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.

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.

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.

See https://github.com/bdarcus/bibtex-actions/issues/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:

~~~ elisp
(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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

WorldsEndless picture WorldsEndless  Â·  9Comments

Luis-Henriquez-Perez picture Luis-Henriquez-Perez  Â·  11Comments

ashton314 picture ashton314  Â·  12Comments

tinglycraniumplacidly picture tinglycraniumplacidly  Â·  13Comments

piranha picture piranha  Â·  3Comments