Input Handlers


Arithmetic command using an input handler

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:

  1. text input handlers accepting arbitrary text input,
  2. 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_plugin.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 (opens new window) 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 (opens new window). 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 (opens new window) 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 (opens new window).

# 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 (opens new window) using the built-in html.entities (opens new window) 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 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):
        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!

# Code Archive

The final code examples presented on this page have been uploaded to a Gist (opens new window). You can download a zipball (opens new window) of it and extract it into a local package of your choice to experiment with them.

# Caveats

  • There is currently no functionality to show an input handler dynamically, i.e. when depending on external or dynamic data. An input handler is only requested when an input parameter is missing. (#3347 (opens new window))

  • A command's input method may be called multiple times until the user can access it.

  • is_visible and is_enabled cannot decide their return value based on the given arguments when an input handler is involved. (#3249 (opens new window))

Last Updated: 11/8/2020, 11:26:49 PM