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

Add "Accumulating Inputs from a List" example #122

Merged
merged 2 commits into from
Feb 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
{ "caption": "Simple Command", "command": "simple" },
{ "caption": "Insert Html Entity", "command": "insert_html_entity" },
{ "caption": "Multiply", "command": "multiply" },
{ "caption": "Accumulate", "command": "accumulate" },
]
51 changes: 51 additions & 0 deletions docs/guide/extensibility/plugins/input_handlers/code/accumulate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import sublime_plugin


class ItemsInputHandler(sublime_plugin.ListInputHandler):
def __init__(self, choices, accumulated=None, selected_index=0):
self.choices = choices
self.accumulated = accumulated or []
self.selected_index = selected_index

self.alt_pressed = False
self.selected = None

def want_event(self):
# Declare that we want to receive the `event` argument.
return True

def validate(self, _value, _event):
# We must override this method because we define `want_event`.
# https://github.com/sublimehq/sublime_text/issues/6258
return True

def list_items(self):
# Each selectable item represents
# all accumulated items plus the new item as a list.
return (
[(item, self.accumulated + [item]) for item in self.choices],
self.selected_index,
)

def confirm(self, value, event):
# Record that the alt key was pressed when confirming
# so that we can return a follow-up input handler
# in `next_input`.
self.alt_pressed = event["modifier_keys"].get("alt", False)
self.selected = value

def next_input(self, _args):
if self.alt_pressed:
selected_index = self.choices.index(self.selected[-1])
return ItemsInputHandler(self.choices, self.selected, selected_index)
return None


class AccumulateCommand(sublime_plugin.WindowCommand):
def input(self, args):
if "items" not in args:
choices = "a b c d e".split(" ")
return ItemsInputHandler(choices=choices)

def run(self, items):
self.window.run_command('insert', {'characters': " ".join(items)})
Git LFS file not shown
98 changes: 97 additions & 1 deletion docs/guide/extensibility/plugins/input_handlers/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,11 +319,107 @@ Try for yourself!
:::


### Accumulating Inputs from a List

It is also possible to accumulate multiple inputs dynamically.
We can achieve this
by using lists as the values
of our ListInputHandler's `list_items` return value.

To allow the user to signal that they want to select more values,
we define the `want_event` method and `return True` from it.
This tells Sublime Text to add an [`event` argument][Event]
to the `validate` and `confirm` methods,
which we use to determine
if a certain modifier key was held
and whether to return another input handler
in the `next_input` method.

:::tip
Due to [a bug in the plugin API][core-6258],
we need to define *both* methods,
`validate` and `confirm`,
and have them accept this additional `event` argument,
even when we don't need them.
:::

<<< ./code/accumulate.py {13-15,25-28,34-35,38-40,47-48}

Here is what it looks like in action:

<video controls src="./images/accumulate.mp4" />

In this example,
we "generate" a list of choices
that we pass to our `ItemsInputHandler`'s constructor (lines 47-48).
These choices will be used as the basis for each prompt.

When providing a list of items for ST to display
in `list_items` (lines 22-28),
we return a 2-element `tuple`.
The first item is the list of items,
which in turn are more 2-element tuples.
The first value of the inner tuples
tells ST what to show inside the item list,
while the second value is what ST will use
when invoking the `validate` and `confirm` methods
and also what will get used as the final value
provided by this event handler.
The second item will become relevant later.
Refer to [the documentation for `list_items`][list_items] for more details.

[list_items]: https://www.sublimetext.com/docs/api_reference.html#sublime_plugin.ListInputHandler.list_items

Next, we take a look at the `confirm` method (lines 30-35).
The method is invoked
with the `value` of the selected list item
but there is the aforementioned additional [`event` argument][Event].
We inspect the `event` to check for the Alt key,
record the result and the selected value in an instance attribute
and move on to `next_input`.

[core-6258]: https://github.com/sublimehq/sublime_text/issues/6258
[Event]: https://www.sublimetext.com/docs/api_reference.html#sublime.Event

As discussed before,
ST calls the `next_input`
to check for the next input handler to open
and this is where the magic happens (lines 38-40).
If the alt modified key has been held while selecting an item,
we return a new instance of *the same input handler class*
and with the following values:

1. the same list of choices,
1. the accumulated value list (`self.selected`), and
1. the index of the just-selected item.

The list of choices is self-explanatory,
the accumulated value list is needed
to generate the next set of items in `list_items`,
and the `selected_index` is used
to open the next input handler
with the previously selected item preselected.
If the alt key has not been held,
we simply return `None`
and conclude the collection of arguments.

In the end, the result of
the last input handler on the stack of each argument
(here: `items`, determined from the class name `ItemsInputHandler`)
will be collected and used in the command's `run` method invocation.
Because we used the *same input handler name* for all our input handlers,
we receive the accumulated list of selected items
from the last instance.

Note that this is just one method of achieving this behavior.
You may find that another works better for you.


## Code Archive

The final code examples presented on this page
[are included in the source git repository][code].
You can download a [zip][] of it (via [DownGit][])
You can download a [zipball][] of it (via [DownGit][])
and extract it into your Packages folder
to experiment with them.

Expand Down