16. Input Handlers
Input Handlers augment normal Commands by enabling those commands to gather input directly from the user instead of from a fixed input list, e.g. from a Key Binding.
Each Input Handler used by a Command will “fill in” exactly one argument in the
args dictionary that is eventually passed to the Command. That “passing” does
not happen until the user has successfully gone through all the provided
Input Handlers in sequence without aborting with the [Esc] key or other means.
Input Handlers are created as sub-classes of several classes found in
sublime_plugin.py. Their names end with “...InputHandler”, and they all
derive from the CommandInputHandler class, where most of the common methods
(which you can optionally override/redefine) are found. In the order found in
sublime_plugin.py:
BackInputHandler
TextInputHandler
ListInputHandler
The Input Handler object’s name(self) method provides the name of the argument
that gets created for the args dictionary. Its default implementation translates
the name of the class you create into “snake case” and then chops off the trailing
“_input_handler” if it is present. For example, the name “PositionInputHandler” will
create a lower-case argument named position in the args dictionary that
eventually gets passed to the Command. An underscore (‘_’) is inserted into the
name each time the class name transitions from a lower-case to an upper-case
letter. So the class name “AlphaBRAvoCharlieInputHandler” would be translated to the
name “alpha_bRAvo_charlie”.
Important!
Input handlers only work for commands in the Command Palette. Therefore, to use them
with any Command you create, you must also create that Command in a
any_name.sublime-commands file to get the command into the Command Palette, even
though your Command may be invoked from a Key Binding or Menu-Item selection as well.
16.1. Flow of Execution
When the command is run, whether it be from:
a Key Binding being triggered,
a Menu-Item selection by the user,
the Command Palette, or
programmatically (e.g. from a Plugin) by something like this:
self.view.run_command('command_name', args)
where
argsis a dictionary that would normally provide the named positional arguments to the Command’srun(self, edit, ...)method.
Sublime Text attempts to run the command with the args dictionary provided, and if
it then triggers an exception based on unfilled positional arguments, it calls that
command’s input(self, args) input method to ask the Command, “Do you know how I
can get the missing arguments?”
Important!
You trigger the input sequence by providing an incomplete set of arguments in the Command’s `args` dictionary. Example:
class ExampleCommand(sublime_plugin.TextCommand):
def run(self, edit, message, position):
# do something here
See example below.
Once the Command’s input(self, args) method has been called, its job is to detect
what arguments are missing and return to the caller an instance of
the “...InputHandler” class you created in order to gather that exact argument for
your Command. Because the Command can be “called” from any number of sources with
any combination of missing arguments (including by end users who use your package),
the input(self, args) method should check for all possible missing arguments,
in the same sequence you want the user prompted for them.
At this point Sublime Text makes the Input Overlay Panel visible. If the
Command has an input_description(method) method, it will be called to get the
string to use as the prompt to the left of the cursor when the input sequence is in
progress.
Each time the user submits his input (by hitting [Enter]), the Sublime Text tries
again to call the command. If it still gets an exception based on unfilled
positional arguments, it attempts to call the next_input(self, args) method of the
most-recently-used Input Handler object. That method can return one of the following:
an instance of the Input-Handler class you created for your Command, that will gather the next input argument needed in the
argsdictionary for your Command;an instance of
sublime_plugin.BackInputHandler()to cause the input sequence to “go back” to the previous state (e.g. previous input handler); orNonewhich signals the caller that the last argument entry for theargsdictionary has been populated and to go ahead and call the Command and pass theargsdictionary as it is. Note: returningNonecan also be done by letting the method run past its end with noreturnstatement.
Note
The user goes forward in the input sequence by hitting [Enter] and can choose to go backward in the input sequence by hitting [Backspace].
16.1.1. TextInputHandler
Sub-class from sublime_plugin.TextInputHandler to create an Input Handler that
will gather input from the user in a free-form typing format, on one line of text.
These can also have an output-display area. A default value can be provided if
desired, and there is a convenient way you can use to validate input. You can find
an example of this in arithmetic.py that ships with Sublime Text.
Common methods to define for text input include:
initial_text(self)optionally returns text that can be an initial value populating the text field when the user is prompted. If it returnsNonethen the TextBox shown to the user will be empty.next_input(self, args)is called by the Command Palette to get the next Input Handler if there are still more arguments to fill in theargsdictionary before the Command is eventually called. See above to see this method’s responsibility.
16.1.2. ListInputHandlers
Sub-class from sublime_plugin.ListInputHandlers to create an Input Handler that
functions similar to a drop-down list, allowing the user to select from a number of
options. “Fuzzy searching” in the list is provided automatically. A default
selection can be provided if desired.
Common methods to define for list input include:
list_items(self)which should return a list of tuples, one for each item the user can select from, each containing:a string (shown to user to select), and
the value that string represents, that will become the value of that
argsdictionary entry that this Input Handler provides.
next_input(self, args)is called by the Command Palette to get the next Input Handler if there are more arguments to fill in theargsdictionary before the Command is eventually called. This is exactly as described above for theTextInputHandlersub-class.
16.1.3. Example
To see this running in a simple, running example, use the code below. Also add
command odat_nurd_example0910 to a test Key Binding with different
arguments missing to see how Sublime Text uses these things. Comments are added to
overridden methods below to explain what they are for and when to override/redefine
them.
Put this in any-named .sublime-commands file in the root of your User Package,
e.g. odatnurd-p101-09-example.sublime-commands.
[
{
"caption": "OdatNurd Example 09+10 Command",
"command": "odat_nurd_example0910",
"args": {
// "message": "My message.",
// "position": 0
// "after_text": "My additional statement."
}
}
]
Put the following in any-named .py file in the root of your User Package,
e.g. odatnurd-p101-09+10-example.py.
import sublime
import sublime_plugin
import pprint
class MessageInputHandler(sublime_plugin.TextInputHandler):
def name(self):
"""
Name of argument this Input Handler fills.
The default definition for this method converts the name of the class
(e.g. "MessageInputHandler") into just "message". When overridden,
this Input Handler can provide a different name if desired.
"""
return 'message'
def initial_text(self):
"""
Initial text shown in the text entry box. Empty by default.
"""
return "Hello, world!"
def initial_selection(self) -> [(int, int)]:
"""
A list of 2-element Region tuples, defining the initially
selected parts of the initial text.
"""
return []
def placeholder(self):
"""
This placeholder text is shown in the background of the text entry
box (grayed out) whenever it is empty. Empty by default.
"""
return 'Enter message to insert (not "Hello")'
def description(self, text):
"""
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 the user
entered.
"""
return '<msg>'
def preview(self, text):
"""
Called whenever the user changes the text in the entry box. The returned
value (either plain text or HTML) will be shown in the preview area of
the *Command Palette*.
"""
# return f'Inserting: [{text}]'
return sublime.Html(f'<strong>Inserting:</strong> <em>{text}</em>')
def validate(self, text, event):
"""
Called when user hits [Enter] to submit input. If this method returns
`False`, nothing happens. If it returns `True`, the input sequence proceeds.
Default implementation returns `True`.
:param event: Gets passed when `want_event` returns `True`. User hitting
plain [enter] results in `event` containing
`{'modifier_keys': {}}`.
[ctrl+enter] results in `event` containing
`{'modifier_keys': {'ctrl': True, 'primary': True}}`.
[shift+ctrl+alt] results in `event` containing
`{'modifier_keys': {'alt': True, 'ctrl': True, 'primary': True, 'shift': True}}`
"primary" means the View is the first View attached to
its buffer. "primary": False would happen in a View that
is "cloned".
"""
print('validate() running... Event object shown below.')
pprint.pp(event, indent=4)
result = True
if text == 'Hello':
result = False
return result
def cancel(self):
"""
Called as an "event hook" if/when user cancels input sequence with [Esc]
or [Backspace] to go back.
"""
print('User cancelled at MessageInputHandler.')
def confirm(self, text, event):
"""
Called when the input is accepted, after the user has pressed enter and
the text has been validated.
:param event: Gets passed when `want_event` returns `True`. User hitting
plain [enter] results in `event` containing
`{'modifier_keys': {}}`.
[ctrl+enter] results in `event` containing
`{'modifier_keys': {'ctrl': True, 'primary': True}}`.
[shift+ctrl+alt] results in `event` containing
`{'modifier_keys': {'alt': True, 'ctrl': True, 'primary': True, 'shift': True}}`
"""
print(f'Message.confirm(): Got [{text}]. Event object below.')
pprint.pp(event, indent=4)
def want_event(self) -> bool:
"""
Whether the `validate()` and `confirm()` methods should received a
second `Event`-type parameter. Returns `False` by default.
"""
return True
def next_input(self, args):
if 'position' not in args:
return PositionInputHandler()
class PositionInputHandler(sublime_plugin.ListInputHandler):
def list_items(self):
# Option 1: a list of strings to choose from; good for text options.
# return ['0', '50', '-1', '-100']
#
# Option 2: a list of tuples, the user sees the first element, the
# second element is the value.
# return [
# ('Top of the file', 0),
# ('Middle of the file', 50),
# ('Bottom of the file', -1),
# ('Cancel', -100)
# ]
# Option 3: a tuple that contains a list as in option 2, and also
# the index of the item that should be selected by default.
return (
[
('Top of the file', 0),
('Middle of the file', 50),
('Bottom of the file', -1),
('Cancel', -100)
],
2
)
def description(self, value, text: str) -> 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.
"""
return f'Value [{value}]'
def preview(self, value):
"""
Called whenever the user changes the text in the entry box. The returned
value (either plain text or HTML) will be shown in the preview area of
the *Command Palette*.
"""
#return f'Inserting at position: [{value}]'
return sublime.Html(f'<strong>Inserting at position:</strong> <em>{value}</em>')
def initial_text(self):
"""
Initial text as if user typed this in. Will cause "fuzzy filtering"
to be applied to the list.
"""
return ''
def cancel(self):
"""
Called as an "event hook" if/when user cancels input sequence with [Esc]
or [Backspace] to go back.
"""
print('User cancelled at PositionInputHandler.')
def confirm(self, value):
"""
Called when the input is accepted, after the user has pressed enter and
the text has been validated.
:param event: Gets passed when `want_event` returns `True`. User hitting
plain [enter] results in `event` containing
`{'modifier_keys': {}}`.
[ctrl+enter] results in `event` containing
`{'modifier_keys': {'ctrl': True, 'primary': True}}`.
[shift+ctrl+alt] results in `event` containing
`{'modifier_keys': {'alt': True, 'ctrl': True, 'primary': True, 'shift': True}}`
"primary" means the View is the first View attached to
its buffer. "primary": False would happen in a View that
is "cloned".
"""
print(f'Position.confirm(): Got [{value}]. Event object below.')
self.selected_value = value
def next_input(self, args):
if self.selected_value == -100:
return sublime_plugin.BackInputHandler()
elif 'after_text' not in args:
return AfterTextInputHandler()
class AfterTextInputHandler(sublime_plugin.TextInputHandler):
def placeholder(self):
"""
This placeholder text is shown in the background of the text entry
box (grayed out) whenever it is empty. Empty by default.
"""
return 'Any additional text you want to add.'
def confirm(self, text, event):
"""
Called when the input is accepted, after the user has pressed enter and
the text has been validated.
:param event: Gets passed when `want_event` returns `True`. User hitting
plain [enter] results in `event` containing
`{'modifier_keys': {}}`.
[ctrl+enter] results in `event` containing
`{'modifier_keys': {'ctrl': True, 'primary': True}}`.
[shift+ctrl+alt] results in `event` containing
`{'modifier_keys': {'alt': True, 'ctrl': True, 'primary': True, 'shift': True}}`
"primary" means the View is the first View attached to
its buffer. "primary": False would happen in a View that
is "cloned".
"""
print(f'AfterText.confirm(): Got [{text}]. Event object below.')
pprint.pp(event, indent=4)
def want_event(self) -> bool:
"""
Whether the `validate()` and `confirm()` methods should received a
second `Event`-type parameter. Returns `False` by default.
"""
return True
def cancel(self):
"""
Called as an "event hook" if/when user cancels input sequence with [Esc]
or [Backspace] to go back.
"""
print('User cancelled at AfterTextInputHandler.')
class OdatNurdExample0910Command(sublime_plugin.TextCommand):
def run(self, edit, message, position, after_text):
if position < 0:
position = self.view.size()
self.view.insert(edit, position, message + after_text)
def input(self, args):
# Note: the names of these keys being tested for in the `args`
# dictionary is created by the "stem" of the name of the
# Input-Handler class. In this case, "stem" means the part
# of the class name before "...InputHandler" at the end.
if 'message' not in args:
return MessageInputHandler()
elif 'position' not in args:
return PositionInputHandler()
elif 'after_text' not in args:
return AfterTextInputHandler()
def input_description(self):
return 'Insert'