Sublime Forum

[Resolved] I want to insert text from input list

#1

Hi everyone,

I’m very new to suplime text api development and I try to make something very simple. What I want is to code a command to insert inside a view a text among a possible list of values (theses values are read from a file specified inside a setting file, need to find how to do that too …).

When calling this command, I’d like a list to appear like a command panel and I could search inside this list an item I want to add to the view.

  • I found how to add a text at a specific place (thanks hello world).
  • It seems a ListInputHandler is what I need to display the list.
  • It seems I need to use a WindowCommand to use the ListInputHandler object.
  • I don’t want to use a WindowCommand as this command has no sense outside a view.

Well, I’m thinking of something like this, but this sure is false.

import sublime
import sublime_plugin

class ItemInputHandler(sublime_plugin.ListInputHandler):
    def name(self):
        return "item"

    def placeholder(self):
        return "Item"

    def list_items(self):
        items = ["Item 1", "Item 2", "Item 3"]
        return items


class ExampleCommand(sublime_plugin.WindowCommand):
    def run(self, item):
        view = self.window.active_view()
        selections = view.sel()

        item = ItemInputHandler()
        print(item)

        for region in selections:

            pos = region.a
            view.insert(edit, pos, item)

    def input(self, args):
        if "name" not in args:
            return ItemInputHandler()
        else:
            return None

Could someone give me some help ?

0 Likes

#2

There’s two ways to do something like this; one of them is a ListInputHandler and the other is a quick panel.

The ListInputHandler method is only available to commands that appear in the command palette. You can also bind such a command to a key if you want, but it’s important that the command appear in the command palette or it won’t work.

The quick_panel method is available to any command, even if it’s not in the command palette.

Which you use depends on what you’re trying to achieve and how you want to interact with the panel, but either are viable methods.

This isn’t a requirement; any command can take advantage of an InputHandler; the only restriction is that they’re only available for commands in the command palette (because that’s where the input is gathered when you run the command).

In order to use the view.insert() method like in your example, you definitely need a TextCommand because only a TextCommand has access to the edit object you need to pass to the method.

The ListInputHandler that you’re using is good to go. The only note I can make here is that the default return value for the name() method if you don’t define it is based on the name of the class, and because your class is named ItemInputHandler the default value is already item so you don’t need to implement this.

However it doesn’t hurt anything to put it there, and this is also handy for when you want your class name to be more meaningful than the name of the argument it’s returning.

The command itself has a few problems (but you already know that :slight_smile:); the simplest version of that command that does what you want would be:

class ExampleCommand(sublime_plugin.TextCommand):
   def run(self, edit, item):
       self.view.run_command("insert", {"characters": item})

   def input(self, args):
       if "item" not in args:
           return ItemInputHandler()

In addition you need to add this command to the command palette as well; to do that create a sublime-commands file in your User package with the following content (the name of the file doesn’t matter, only the extension does):

[
    { "caption": "Insert Example Text", "command": "example" },
]

The first thing to note is that this is a TextCommand and not a WindowCommand, so that the command is associated with a file and not the window the file is in. In this particular case that doesn’t matter, but the code ends up being cleaner.

This is also using run_command() to execute the insert command, which inserts the text you give it into every selection (which also replaces the selection; essentially it mimics the user typing).

This is a one-line replacement to using iterative calls to view.insert() to do the same thing. In this case because we’re using run_command to do the work, this command could also be a WindowCommand. That line could easily be self.window.active_view().run_command('insert', {"characters": item}) and it would work the same way, but using a TextCommand looks a little cleaner.

The “magic” (such as it is) of using an InputHandler comes from the input method, the fact that the command has to be in the command palette, and extra work that Sublime does behind the scenes for you to make your life easier.

In use, when you pick the Insert Example Text command from the command palette, Sublime will try to run it. However it requires an argument named item which is not being provided.

Normally that would cause an error, but because the command is in the command palette, Sublime will call the input() method on the command and give it the arguments it has, to see if the command supports an input handler.

Here we check to see if there’s an item argument (the only one we understand), and if there’s not, we return back an instance of the Input handler class from your plugin above.

Sublime uses the input handler to get the user to pick an item. You can press Backspace to cancel out of the command to pick a new one from the command palette, you can close the panel, or you can pick an item.

If you pick an item, that sets what the value of the item argument should be. Then Sublime asks the input handler if there are other arguments, and since our class doesn’t say that there are (the default value for that method says there are none), Sublime goes back and tries to run the command again, this time giving it the item argument that was collected.

Now the command can run, and it uses the insert command to do the work. Since Sublime takes care of making sure that it has a value for it’s argument, it doesn’t even have to do anything.

You can also do something like this instead, if you don’t want/need your command to be in the command palette:

class ExamplePanelCommand(sublime_plugin.TextCommand):
    def run(self, edit, item=None):
        items = ["Item 1", "Item 2", "Item 3"]

        if item:
            self.view.run_command("insert", {"characters": item})
        else:
            self.view.window().show_quick_panel(
                items,
                lambda idx: self.pick(idx, items))

    def pick(self, index, items):
        if index >= 0:
            self.view.run_command("example_panel", {"item": items[index]})

In this version, the command is a TextCommand again (for the same reasons as above), and it declares that it takes an argument of item, but the default value is None.

The default is there because if a command takes arguments and you don’t provide them, it fails (except for the above Input Handler stuff). So here we’re doing something similar to what Sublime was doing for us itself previously.

If an item was provided, we can insert it and we’re done. Otherwise we need to open the quick panel to ask the user to pick an item. We give the panel the list of items it should be showing, and tell it what it should do when a selection is made, which is to call our pick method.

The show_quick_panel() method returns immediately, and then our command exits. Meanwhile, there’s a panel in the window that the user can interact with. The user can pick an item or close the panel.

Whichever one of those happens, the handler called with the list of items that were being displayed, plus the index of the item that the user picked. The index will be -1 if the user pressed Escape to cancel out of the panel, so we need to guard against that. Otherwise, we can re-run the command again and this time give it the value.

Both the InputHandler and the quick_panel let you pick from a list with a fuzzy selection, so both will do what you want here. There’s extra setup for the input handler, but the code ends up being a bit cleaner to look at.

On the flip side, a quick panel can display multiple rows of data for each line in the panel (think for example how the Goto Anything panel shows you the file name and the file path in each entry).

Essentially, there are pros and cons to both method, so it all comes down to your preference and what you want to do regarding which one you pick.

2 Likes

#3

Wow ! That’s a beautiful and useful answer !

Thanks a lot, that helped me !!!

0 Likes