Input Handlers
Input handlers are a mechanism to query a user for one or multiple input parameters via the Command Palette. They replace the older method of input and quick panels (Window.show_input_panel
and Window.show_quick_panel
) for a unified user experience in a single component.
Input Handlers have been added in build 3154 and were first available on the stable channel in version 3.1.
Examples
The following commands provided by Sublime Text's Default
package use input handlers (command names are for the Command Palette):
Command name | File | Description |
---|---|---|
Arithmetic | Default/arithmetic.py | Evaluates a given Python expression (usually numeric). |
View Package File | Default/ui.py | Provides a list of all resource files inside the Packages folder to open. |
Rename File | Default/rename.py | Queries the user for a new file name for the active view. |
You can use the above View Package File command to view the source code of these files.
Input Handler Kinds
There are currently two types of input handlers:
- text input handlers accepting arbitrary text input,
- list input handlers providing a list of options for the user to choose from.
Text input handlers always forward the entered text to the command, while list input handlers can handle any JSON-serializable value, accompanied by a caption for their respective list entry.
Implementing an Input Handler
Because input handlers are using a rather generic interface, adding one to your command may require careful thinking and may not be the most intuitive process.
We will implement an example input handler and explain more gears you can tweak for advanced configuration.
Important
To use an input handler for a command, the command must have an entry in the Command Palette. This is easy to forget, so make sure to remember!
Let's start with a very simple command that inserts the given text into the view. The following two files can be placed in any package folder, including "User".
simple_command.py
:
import sublime_plugin
class SimpleCommand(sublime_plugin.TextCommand):
def run(self, edit, text):
for region in self.view.sel():
self.view.replace(edit, region, text)
Simple.sublime-commands
:
[
{ "caption": "Simple Command", "command": "simple" },
]
The *Command.input
Method
When implementing a command, it receives keyed arguments to its run
method. When a parameter in the signature provides no default value, it can only be called if arguments are provided for all such parameters. Calling a command with too few arguments will fail and cause an exception to be printed to the console.
>>> window.run_command("simple")
Traceback (most recent call last):
File "/opt/sublime_text/Lib/python38/sublime_plugin.py", line 1270, in run_
return self.run()
TypeError: run() missing 1 required positional argument: 'text'
In a situation like this, a command may implement the input
method and return an input handler instance that provides Sublime Text with the necessary information to display an input handler.
import sublime_plugin
class SimpleCommand(sublime_plugin.TextCommand):
def run(self, edit, text):
for region in self.view.sel():
self.view.replace(edit, region, text)
def input(self, args):
return MyTextInputHandler()
The input
function takes an args
parameter that is a dict of all currently known arguments to the command. Since we know that our only required argument (text
) is missing at this point, we won't use the parameter.
We haven't defined MyTextInputHandler
yet, so let's do that.
Subclassing TextInputHandler
To create a simple input handler for text, we create a subclass of sublime_plugin.TextInputHandler
. In our subclass, we can override specific methods. For the most basic functionality, we need name
. Additionally, for convenience, we define placeholder
.
class MyTextInputHandler(sublime_plugin.TextInputHandler):
def name(self):
return "text"
def placeholder(self):
return "Text to insert"
And that's it. Here is what it looks like:
TIP
Of course, you can still call the command like before from a key binding or via the console. When all required arguments are provided, the input handler will be skipped and the command run immediately.
Rendering a Preview
The preview
method is called for every modification of the entered text and allows to show a small preview below the Command Palette. The preview can either be pure text or can use minihtml for a markup-enabled format.
The following snippet extends our input handler from earlier to show the amount of characters that will be inserted:
class MyTextInputHandler(sublime_plugin.TextInputHandler):
def name(self):
return "text"
def placeholder(self):
return "Text to insert"
def preview(self, text):
return "Characters: {}".format(len(text))
There are additional methods that can be overriden. These are described in the documentation.
Using Dynamic Data
You may have noticed that our MyTextInputHandler
class is entirely separate from our SampleCommand
. In the event that we want the input handler to depend on some dynamic data, such as the current view's selection, we will have to provide such values to the input handler's constructor.
The following snippet passes the text command's View
instance to the input handler's constructor. The constructor itself stores the instance in an instance attribute and later accesses it from preview
.
import sublime_plugin
class SimpleCommand(sublime_plugin.TextCommand):
def run(self, edit, text):
for region in self.view.sel():
self.view.replace(edit, region, text)
def input(self, args):
return MyTextInputHandler(self.view)
class MyTextInputHandler(sublime_plugin.TextInputHandler):
def __init__(self, view):
self.view = view
def name(self):
return "text"
def placeholder(self):
return "Text to insert"
def preview(self, text):
return ("Selections: {}, Characters: {}"
.format(len(self.view.sel()), len(text)))
Providing a List Of Options With ListInputHandler
Instead of free form input, you can provide the user with a list of values that they can choose from. This is done by sublassing sublime_plugin.ListInputHandler
and providing an list_items
method that returns a list of values to choose from. This list can either be a list of strings or a list of tuples, where the first element indicates the text to be shown and the second element the value to insert as the command's argument.
Following is a small example command that offers a list of named HTML entities using the built-in html.entities
module:
from html.entities import html5
import sublime_plugin
class InsertHtmlEntityCommand(sublime_plugin.TextCommand):
def run(self, edit, entity):
for region in self.view.sel():
self.view.replace(edit, region, "&" + entity)
def input(self, args):
return EntityInputHandler()
class EntityInputHandler(sublime_plugin.ListInputHandler):
def list_items(self):
return sorted(html5.keys())
def preview(self, value):
return "Character: {}".format(html5.get(value))
TIP
Notice how we don't implement name
here, because Sublime Text can automatically infer the input handler's target argument name from the class name, using the same logic as for command names but stripping "InputHandler" instead.
Reminder
Remember that you need to make the above command available to the Command Palette by specifying it in a .sublime-commands
file.
[
{ "caption": "Insert Html Entity", "command": "insert_html_entity" },
]
Here is what it looks like in action:
Implementing Multiple Input Handlers
When a command requires multiple arguments that the user must provide, things change a bit. Notably, you know must add logic inside input
that returns the appropriate input handler based on which arguments are still missing. The order in which these are returned matters, because input handlers that received input remain visible in the Command Palette to visualize the current input step in a breadcrumbs style. And finally, the input handlers' description
methods will be used to render text for these breadcrumbs. (Since the default behavior is to show the inserted value, this is used only rarely.)
Let's write a command that multiplies two operands.
import sublime_plugin
class SimpleMultiplyCommand(sublime_plugin.TextCommand):
def run(self, edit, operand1, operand2):
result = float(operand1) * float(operand2)
for region in self.view.sel():
self.view.replace(edit, region, str(result))
def input(self, args):
for name in ['operand1', 'operand2']:
if name not in args:
return NumberInputHandler(name)
class NumberInputHandler(sublime_plugin.TextInputHandler):
def __init__(self, name):
self._name = name
def name(self):
return self._name
def placeholder(self):
return "Number"
def validate(self, text):
try:
float(text)
except ValueError:
return False
else:
return True
TIP
In this command, we only used a single input handler class for two parameters by returning an instance variable in the name
function.
The command works as it advertises. It asks for two numbers when invoked from the command palette consecutively. However, it does not show a breadcrumb for the first operand after we confirmed it. This is because the input
command is re-run after the first argument, since we need two arguments, and information about the previous input handler is lost.
TIP
Having problems running this command? Did you add a .sublime-commands
entry for it?
The next_input
Method
To show the before-mentioned breadcrumb, the first input handler needs to know what input handler should be the next and return it in a next_input
method.
You could do so in a static way, but let's try a dynamic approach. Remember that you don't need to ask for the second argument if it was already provided.
import sublime_plugin
class MultiplyCommand(sublime_plugin.TextCommand):
def run(self, edit, operand1, operand2):
result = float(operand1) * float(operand2)
for region in self.view.sel():
self.view.replace(edit, region, str(result))
def input(self, args):
names = [name for name in ['operand1', 'operand2']
if name not in args]
if names:
return MultiNumberInputHandler(names)
class MultiNumberInputHandler(sublime_plugin.TextInputHandler):
def __init__(self, names):
self._name, *self.next_names = names
def name(self):
return self._name
def placeholder(self):
return "Number"
def next_input(self, args):
if self.next_names:
return MultiNumberInputHandler(self.next_names)
def validate(self, text):
try:
float(text)
except ValueError:
return False
else:
return True
In this command, we collect all the arguments we need from the first call and change NumberInputHandler
to MultiNumberInputHandler
that accepts a list of argument names to query. The destructuring assignment in line 19 splits the list into a "first" and "rest", so that the rest of the required arguments can be returned in the next_input
method.
Let's see how it looks when invoked:
TIP
Both NumberInputHandler
and MultiNumberInputHandler
implement a validate
method that returns a boolean if the passed text can be parsed into a floating point number. The effect is that for non-numeric text the input is rejected and nothing happens when pressing ⏎. 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 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, we need to define both methods, validate
and confirm
, and have them accept this additional event
argument, even when we don't need them.
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)})
Here is what it looks like in action:
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
for more details.
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. 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
.
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:
- the same list of choices,
- the accumulated value list (
self.selected
), and - 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. You can download a zipball of it (via DownGit) and extract it into your Packages folder to experiment with them.
Invoking Commands With Input Handlers
When invoking a command with an input handler and without all required arguments from a plugin or key binding, it is advised to use the show_overlay
command. Commands invoked that way will have their input
method called before ST attempts to call run
, resulting in more predictable behavior. Otherwise, Sublime Text will try to run the command as normally (running its run
method) and only check the command's input
method if the call failed because of insufficient arguments.
Examples:
view.run_command(
'show_overlay',
{'overlay': 'command_palette', 'command': 'multiply', 'args': {'operand1': 12}},
)
{
"command": "show_overlay",
"args": {
"overlay": "command_palette",
"command": "multiply",
"args": {"operand1": 12}
},
}
Caveats
As mentioned countless times already, there must be an entry for the Command Palette to be able to use input handlers.
A command's
input
method may be called multiple times until the user can access it.is_visible
andis_enabled
cannot decide their return value based on the given arguments when an input handler is involved. (#3249)