Skip to content

Commit ea0f891

Browse files
committed
Add "Accumulating Inputs from a List" section
Provide and discuss an example for accumulating multiple input values in a single list, utilizing the 2-tuple return value of `list_items`, event info when specifying `want_event`, and a sort of self-overriding `next_input` return value. I initially made this during a discussion on Discord: https://discord.com/channels/280102180189634562/280157067396775936/1192776891183140955
1 parent ead7b81 commit ea0f891

File tree

4 files changed

+151
-0
lines changed

4 files changed

+151
-0
lines changed

docs/guide/extensibility/plugins/input_handlers/code/Input Handlers.sublime-commands

+1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
{ "caption": "Simple Command", "command": "simple" },
33
{ "caption": "Insert Html Entity", "command": "insert_html_entity" },
44
{ "caption": "Multiply", "command": "multiply" },
5+
{ "caption": "Accumulate", "command": "accumulate" },
56
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import sublime_plugin
2+
3+
4+
class ItemsInputHandler(sublime_plugin.ListInputHandler):
5+
def __init__(self, choices, accumulated=None, selected_index=0):
6+
self.choices = choices
7+
self.accumulated = accumulated or []
8+
self.selected_index = selected_index
9+
10+
self.alt_pressed = False
11+
self.selected = None
12+
13+
def want_event(self):
14+
# Declare that we want to receive the `event` argument.
15+
return True
16+
17+
def validate(self, _value, _event):
18+
# We must override this method because we define `want_event`.
19+
# https://github.com/sublimehq/sublime_text/issues/6258
20+
return True
21+
22+
def list_items(self):
23+
# Each selectable item represents
24+
# all accumulated items plus the new item as a list.
25+
return (
26+
[(item, self.accumulated + [item]) for item in self.choices],
27+
self.selected_index,
28+
)
29+
30+
def confirm(self, value, event):
31+
# Record that the alt key was pressed when confirming
32+
# so that we can return a follow-up input handler
33+
# in `next_input`.
34+
self.alt_pressed = event["modifier_keys"].get("alt", False)
35+
self.selected = value
36+
37+
def next_input(self, _args):
38+
if self.alt_pressed:
39+
selected_index = self.choices.index(self.selected[-1])
40+
return ItemsInputHandler(self.choices, self.selected, selected_index)
41+
return None
42+
43+
44+
class AccumulateCommand(sublime_plugin.WindowCommand):
45+
def input(self, args):
46+
if "items" not in args:
47+
choices = "a b c d e".split(" ")
48+
return ItemsInputHandler(choices=choices)
49+
50+
def run(self, items):
51+
self.window.run_command('insert', {'characters': " ".join(items)})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:369c64b5371943e299f4d4e6efed8775fa4c7e89dba75e1fb0747ad4b5daadab
3+
size 293113

docs/guide/extensibility/plugins/input_handlers/index.md

+96
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,102 @@ Try for yourself!
319319
:::
320320

321321

322+
### Accumulating Inputs from a List
323+
324+
It is also possible to accumulate multiple inputs dynamically.
325+
We can achieve this
326+
by using lists as the values
327+
of our ListInputHandler's `list_items` return value.
328+
329+
To allow the user to signal that they want to select more values,
330+
we define the `want_event` method and `return True` from it.
331+
This tells Sublime Text to add an [`event` argument][Event]
332+
to the `validate` and `confirm` methods,
333+
which we use to determine
334+
if a certain modifier key was held
335+
and whether to return another input handler
336+
in the `next_input` method.
337+
338+
:::tip
339+
Due to [a bug in the plugin API][core-6258],
340+
we need to define *both* methods,
341+
`validate` and `confirm`,
342+
and have them accept this additional `event` argument,
343+
even when we don't need them.
344+
:::
345+
346+
<<< ./code/accumulate.py {13-15,25-28,34-35,38-40,47-48}
347+
348+
Here is what it looks like in action:
349+
350+
<video controls src="./images/accumulate.mp4" />
351+
352+
In this example,
353+
we "generate" a list of choices
354+
that we pass to our `ItemsInputHandler`'s constructor (lines 47-48).
355+
These choices will be used as the basis for each prompt.
356+
357+
When providing a list of items for ST to display
358+
in `list_items` (lines 22-28),
359+
we return a 2-element `tuple`.
360+
The first item is the list of items,
361+
which in turn are more 2-element tuples.
362+
The first value of the inner tuples
363+
tells ST what to show inside the item list,
364+
while the second value is what ST will use
365+
when invoking the `validate` and `confirm` methods
366+
and also what will get used as the final value
367+
provided by this event handler.
368+
The second item will become relevant later.
369+
Refer to [the documentation for `list_items`][list_items] for more details.
370+
371+
[list_items]: https://www.sublimetext.com/docs/api_reference.html#sublime_plugin.ListInputHandler.list_items
372+
373+
Next, we take a look at the `confirm` method (lines 30-35).
374+
The method is invoked
375+
with the `value` of the selected list item
376+
but there is the aforementioned additional [`event` argument][Event].
377+
We inspect the `event` to check for the Alt key,
378+
record the result and the selected value in an instance attribute
379+
and move on to `next_input`.
380+
381+
[core-6258]: https://github.com/sublimehq/sublime_text/issues/6258
382+
[Event]: https://www.sublimetext.com/docs/api_reference.html#sublime.Event
383+
384+
As discussed before,
385+
ST calls the `next_input`
386+
to check for the next input handler to open
387+
and this is where the magic happens (lines 38-40).
388+
If the alt modified key has been held while selecting an item,
389+
we return a new instance of *the same input handler class*
390+
and with the following values:
391+
392+
1. the same list of choices,
393+
1. the accumulated value list (`self.selected`), and
394+
1. the index of the just-selected item.
395+
396+
The list of choices is self-explanatory,
397+
the accumulated value list is needed
398+
to generate the next set of items in `list_items`,
399+
and the `selected_index` is used
400+
to open the next input handler
401+
with the previously selected item preselected.
402+
If the alt key has not been held,
403+
we simply return `None`
404+
and conclude the collection of arguments.
405+
406+
In the end, the result of
407+
the last input handler on the stack of each argument
408+
(here: `items`, determined from the class name `ItemsInputHandler`)
409+
will be collected and used in the command's `run` method invocation.
410+
Because we used the *same input handler name* for all our input handlers,
411+
we receive the accumulated list of selected items
412+
from the last instance.
413+
414+
Note that this is just one method of achieving this behavior.
415+
You may find that another works better for you.
416+
417+
322418
## Code Archive
323419

324420
The final code examples presented on this page

0 commit comments

Comments
 (0)