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

New UI component like quick panel but where the plugin controls results when user is typing #4796

Open
keith-hall opened this issue Aug 28, 2021 · 7 comments

Comments

@keith-hall
Copy link
Collaborator

Problem description

Currently, a quick panel and a ListInputHandler can only show predefined entries. When the user types, ST filters those entries using fuzzy matching.
For some use cases, it would be useful to have a similar UI component to those, but when the text in the input widget at the top changes, a callback should be invoked so that the plugin would be able to dynamically update/remove/add the results/items in the list.

Specifically, the use-case I have in mind is when querying an XML document using the XPath query language. The user could type in the XPath expression, and the plugin would show items that match, which the user can preview/navigate to with the up/down arrow keys as normal or click on one/press Enter to select it. ST should do no filtering/fuzzy matching of it's own here.

Preferred solution

A new API added to allow a plugin to show a "dynamic input panel".

a new sublime_plugin.DynamicChoiceInputHandler class:

Methods Return Value Description
name() str The command argument name this input handler is editing. Defaults to foo_bar for an input handler named FooBarInputHandler
get_list_items(text) [ListInputItem] or ([ListInputItem], int) This method should return the items to show in the list based on the text which has been input - it will be called when the panel is first opened and every time the text changes. The returned value may be a list of items, or a 2-element tuple containing a list of items, and an int index of the item to pre-select.
placeholder() str Placeholder text is shown in the text entry box before the user has entered anything. Empty by default.
initial_text() str Initial text shown in the filter box. Empty by default.
initial_selection() [tuple] A list of 2-element tuples, defining the initially selected parts of the initial text
preview(value) str or sublime.Html Called whenever the user changes the selected item. The returned value (either plain text or HTML) will be shown in the preview area of the Command Palette.
validate(value) bool Called whenever the user presses enter in the text entry box. Return False to disallow the current value.
cancel() None Called when the input handler is canceled, either by the user pressing backspace or escape.
confirm(value) None Called when the input is accepted, after the user has pressed enter and the item has been validated.
next_input(args) CommandInputHandler or None Returns the next input after the user has completed this one. May return None to indicate no more input is required, or sublime_plugin.BackInputHandler() to indicate that the input handler should be popped off the stack instead.
description(value, text) str The text to show in the Command Palette when this input handler is not at the top of the input handler stack. Defaults to the text of the list item the user selected.
want_event() bool If the validate() and confirm() methods should receive a second parameter, an event Dict

Alternatives

Continue to use ugly hacks like showing an input panel (which spans across the whole editor at the bottom instead of just in the active group), showing a quick panel when the user types/pastes something in said input panel, then switching input focus back to the input panel. And when the user types again, close the quick panel, reopen it with new items, refocus the input panel... And deal with the up/down arrow keys sometimes changing the quick panel selection, sometimes moving the caret in the input panel...? 😅
image

Additional Information (optional)

In case this seems familiar, I am splitting it off from the feature request at #3338 (comment), because it seems for the LSP use case, ST should still do filtering, but for this use case, ST should do no filtering, just allow the plugin to update the list of entries displayed while the user is typing.

You can take a look at the gifs in the XPath plugin's readme for an idea of how it looked in ST3 with just one group in the layout.

It would be great to hear from others to see if this API could be put to good use in other plugins too :)

@UltraInstinct05
Copy link
Contributor

UltraInstinct05 commented Aug 29, 2021

Instead of a whole new API for this (which doesn't seem any different from a ListInputHandler apart from that get_list_items), maybe the ListInputHandler can provide an on_change callback like Window.show_quick_panel and every time a char is entered, update the list items ? (I am not sure, will that re show the handler with updated values or do we need to execute list_items again with updated values with an hypothetical on_change ? ).

Edit 1: I guess it would be something similar to preview except it won't be for showing an HTML/string preview

@keith-hall
Copy link
Collaborator Author

You're right that the API isn't much different, but as the functionality differs quite a lot, I felt a discrete API would make more sense - also to prevent the chance of regressions for the current use of list input handers, and to keep the API simpler rather than needing documentation like "if x then this behavior, otherwise that behavior" etc.

@rwols
Copy link

rwols commented Aug 29, 2021

The critical issue is that get_list_items(text) must also allow returning a promise for it to be useful, in the same way that one can now return a promise for on_query_completions.

What I described in #3338 avoids a promise-based API by letting the plugin handle that via update calls.

@keith-hall
Copy link
Collaborator Author

The critical issue is that get_list_items(text) must also allow returning a promise for it to be useful

Maybe I'm missing something, but the current ST3 compatible hacky implementation of the XPath plugin works fine without promises, so this would still be a major improvement even without them. But sure, we could change the proposed API slightly to avoid the need for promises here too. I can see how that or promises may make it more useful.

But then, another question arises. If the user continues to type and it takes a while to get the new list of items to show, what should be displayed meanwhile? For XPath, it could be useful to show a loading icon or something instead of the previously valid list of items, or to visually dim the list to show it isn't current based on the provided input.

@rwols
Copy link

rwols commented Aug 31, 2021

We can just look at how VSCode does it no?

Peek.2021-08-29.13-28.mp4

The Goto Symbol In Project (aka Workspace Symbols) in VSCode refreshes the list on every keystroke asynchronously. Note how there's a small delay between me typing the identifier and the result list refreshing. There is no UI indication that this is async.

@UltraInstinct05
Copy link
Contributor

This is actually a duplicate of #908, but I'd prefer this issue since it goes into more detail and outlines some use cases.

@FichteFoll
Copy link
Collaborator

If the user continues to type and it takes a while to get the new list of items to show, what should be displayed meanwhile?

Cancel/Discard any but the latest promise.

As for XPath in particular, would it be reasonable to build the xpath query entirely in the command palette but with multiple consecutive input handlers for each part of the query? Can we utilize the auto complete popup there as well? I imagine that would be quite a bit of effort to get right but supposedly it could be achieved with existing tools.

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

No branches or pull requests

4 participants