.. include:: /include/substitutions.txt .. include:: /include/external_links.txt .. include:: /include/custom_roles.txt .. _input handlers: ************** 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. Flow of Execution ================= When the command is run, whether it be from: - a :term:`Key Binding` being triggered, - a Menu-Item selection by the user, - the :term:`Command Palette`, or - programmatically (e.g. from a Plugin) by something like this: .. code-block:: python self.view.run_command('command_name', args) where ``args`` is a dictionary that would normally provide the named positional arguments to the Command's ``run(self, edit, ...)`` method. Sublime |nbsp| 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: .. code-block:: python 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 |nbsp| Text makes the Input :term:`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 |nbsp| 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 ``args`` dictionary 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); or - ``None`` which signals the caller that the last argument entry for the ``args`` dictionary has been populated and to go ahead and call the Command and pass the ``args`` dictionary as it is. Note: returning ``None`` can also be done by letting the method run past its end with no ``return`` statement. .. 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]. 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 |nbsp| 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 returns ``None`` then 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 the ``args`` dictionary before the Command is eventually called. See above to see this method's responsibility. 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 ``args`` dictionary 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 the ``args`` dictionary before the Command is eventually called. This is exactly as described above for the ``TextInputHandler`` sub-class. Example ******* To see this running in a simple, running example, use the code below. Also add command ``odat_nurd_example0910`` to a test :term:`Key Binding` with different arguments missing to see how Sublime |nbsp| 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 :term:`User Package`, e.g. ``odatnurd-p101-09-example.sublime-commands``. .. code-block:: json [ { "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 :term:`User Package`, e.g. ``odatnurd-p101-09+10-example.py``. .. code-block:: python 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 '' 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'Inserting: {text}') 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'Inserting at position: {value}') 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'