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

ListInputHandler in plugin should select the topmost item by default #6162

Closed
jwortmann opened this issue Oct 11, 2023 · 6 comments
Closed
Assignees
Labels
Milestone

Comments

@jwortmann
Copy link

Problem description

When you create a plugin with a ListInputHandler, there is no reliable way to initially preselect the topmost item, if a non-empty initial_text is provided.

For example consider this plugin:

import sublime_plugin

class InputHandlerTestCommand(sublime_plugin.WindowCommand):

    def run(self, arg1):
        pass

    def input(self, args):
        if 'arg1' not in args:
            return MyListInputHandler()

class MyListInputHandler(sublime_plugin.ListInputHandler):

    def list_items(self):
        return ['matches_because_three_a', 'long_text_that_still_matches_the_input_in_some_way', 'ababab', 'aaa']

    def initial_text(self):
        return 'aaa'

and

[
    { "caption": "Input Handler Test", "command": "input_handler_test" },
]

in a .sublime-commands file, and invoke this command in the command palette.

You can see that the "matches_because_three_a" item will be preselected, because it's the first item in the returned list, even though it is not shown at the top. I think there is no way to preselect the topmost item, because Sublime Text automatically filters and reorders the items according to fuzzy matching (the filtering and reordering is of course desired). If you switch the first and second item in the code above for the returned list, there even will be no item selected at all, because the long one is filtered out by ST.

Preferred solution

If the return value of the sublime_plugin.ListInputHandler.list_items method is a tuple with its 2nd element (the selected index) being -1, or if this index is not provided, then the item which is actually shown at the top of the list should be preselected.

In case of an empty initial_text, this would mean no change in behavior, because there is no reordering. And for a non-empty initial_text a plugin could still enforce the first item of the returned list from list_items to be preselected by explicitly setting the selected index to 0.

Alternatives

Expose or document the exact algorithm which Sublime Text uses for its fuzzy matching, so that a plugin can reimplement it to find the item which will actually end up at the top of the list, and then use that as the selected index in list_items. But this would obviously not be the preferred option.

Another alternative for my specific use case would be to implement either #3338 or possibly #4796, but I think the little tweak suggested here would be useful in general.

Additional Information

No response

@jwortmann
Copy link
Author

A workaround I found is this (add an empty fake item which will be hidden, and then move down one line to select the first item):

import sublime
import sublime_plugin

class InputHandlerTestCommand(sublime_plugin.WindowCommand):

    def run(self, arg1):
        pass

    def input(self, args):
        if 'arg1' not in args:
            return MyListInputHandler()

class MyListInputHandler(sublime_plugin.ListInputHandler):

    def list_items(self):
        sublime.set_timeout(self._select_first_item)
        return [''] + ['matches_because_three_a', 'long_text_that_still_matches_the_input_in_some_way', 'ababab', 'aaa']

    def initial_text(self):
        return 'aaa'

    def _select_first_item(self) -> None:
        sublime.active_window().run_command('move', {'by': 'lines', 'forward': True})

But it is not a satisfying solution, because if the user deletes the text in the input field, then the empty fake item will be visible.

@BenjaminSchaaf
Copy link
Member

You can return a tuple from list_items where the first element is the list of items and the second is the index of the initial selection. You can read the docs on this here: https://www.sublimetext.com/docs/api_reference.html#sublime_plugin.ListInputHandler.list_items

@jwortmann
Copy link
Author

You can return a tuple from list_items where the first element is the list of items and the second is the index of the initial selection.

I know that, and this is even what I described above. Please re-read my issue report. The point is that you cannot know where the specified item ends up in the actual list, when there is a non-empty initial_text. I want to select the topmost item, and prevent the list to be scrolled to a random position when it opens.

@BenjaminSchaaf
Copy link
Member

Ah, thanks for clearing that up. I've confirmed this issue on my end.

@BenjaminSchaaf BenjaminSchaaf self-assigned this Oct 13, 2023
@jwortmann
Copy link
Author

jwortmann commented Oct 13, 2023

Just to clarify, there is no bug in the behavior when the selected index is explicitly given. The specified index should indeed be based on the order of the list returned by the list_items method, so that you can still select a specific item. It's just that if the selected index is not provided (or -1 as default value per implementation in sublime_plugin.ListInputHandler.setup_), then I think it would be much more useful to select the topmost row in the panel, instead of the first item from the list, to avoid a random scroll position.

@BenjaminSchaaf
Copy link
Member

Fixed in build 4157.

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

No branches or pull requests

3 participants