Writing Sublime Plugins Sample
Writing Sublime Plugins Sample
Josh Earl
This book is for sale at http://leanpub.com/writing-sublime-plugins
This version was published on 2014-03-28
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and
many iterations to get reader feedback, pivot until you have the right book and build traction once
you do.
2013 - 2014 Josh Earl
Contents
Contact Me . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
ii
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
iii
15
4 Hello World . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18
II Event listeners . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
5 Introducing the KeyBindingInspector plugin . . . . . . . . . . . . . . . . . . . . . . . .
23
6 Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
24
7 Implementation overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
27
28
30
10 Dead end . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
36
Contact Me
Writing Sublime Plugins is a work in progress. If youd like to let me know about an error (Im sure
there are a few), or if you just have a question or want to offer some constructive feedback, email
me at [email protected].
Introduction
Who is this book for?
If youve been kicking around an idea for a Sublime Text plugin, but youre not sure how to get
started, this book is for you. So if youre a Sublime plugin newbie looking to get started quickly,
youve come to the right place.
On the other hand, if youre already an experienced plugin developer, you might not find much new
information here. If you decide to read on, Im hoping youll at least glean a few bits of knowledge
that will help you down the road.
Introduction
iv
write your own. Well build the plugin step by step, and youll see how each new concept fits into the
bigger picture. Youll see how to troubleshoot common issues encountered with plugin development
and how to debug your code. Well also see how to give your plugin a finished, professional feel by
supporting multiple versions of Sublime, providing keyboard shortcuts and integrating it into the
Sublime application menu.
By the time were finished, youll understand the entire process of creating a plugin, from the first
lines of code to publishing your plugin on Package Control.
This book isnt meant to serve as the central store of all plugin development knowledge. The Sublime
API is too expansive and flexible to cover adequately in an introductory level book. Part four
provides some help if you want to learn more, including an overview of what code is available
in your Sublime installation for you to review and where to go for more information.
And the code that youll need to delete is highlighted using strike-through:
Introduction
def SayHello():
print "Hello!"
To save space, Ive replaced the bodies of methods and classes that arent changing in a given section
with ellipses:
def SayHello():
...
Ill give instructions with the idea that youre following along exactly. Of course, youre free to name
things differently than I do, or organize your files differently. But using the same steps I use will
mean fewer mental gymnastics for you as we progress.
Processing text
Sublime provides a broad set of APIs for processing text. Most text processing operations fall into
one of two categories. The first group of API calls allow you to query the text on the screen and
return a result set, similar to the way jQuery allows you to interact with an HTML document.
The second set provides a set of actions that you use to manipulate a piece of text once youve
selected it.
Querying text
Sublime provides several helper methods that allow you to select regions of text in the buffer by
running queries and retrieving a result set.
For example, you can query by language type. Since Sublime applies syntax highlighting to code
files, it has a limited understanding of the structure of a language. When you open a file containing
multiple languages, Sublime parses the file and applies syntax highlighting selection scopes to
various sections of the document. These scopes work like CSS selectors, allowing you to specify what
colors and fonts to use for each one. Similar to how jQuery allows you to query by CSS selectors,
you could query an HTML file using scopes to get a list of embedded JavaScript blocks.
When Sublime opens a code file for a supported language, it parses the file and compiles a list of
symbols. If youre dealing with JavaScript, for example, the symbols list would include function
definitions. You can get a list of these symbols easily.
Similarly, Sublime provides convenient access to its list of known word completions. When Sublime
opens a file, it parses out a list of words to use as suggestions for its auto-complete feature. You can
access this list.
Another way to select text is by specifying a set of coordinates that represent the starting and ending
points of the selection.
Or you can select based on positional values such as the words start or end, or the start or end
points of the current line.
Manipulating text
Once you have a text selection or set of selections, youll need to perform an editing operation on
the text. Sublime includes methods that allow you to delete regions change the syntax highlighting
applied to them. You can also split a region of text into individual lines or fold or unfold the region.
Or you can choose to run another query to break the region up further.
Quick panel
Youre familiar with the quick panel from using the command palette. It allows you to display a
filterable list of items. The user can filter the items by typing. Fuzzy matching is built in.
Status bar
The status bar appears at the bottom of the screen, although the user can choose to hide it. You can
use the status bar to display text messages to the user or for basic ASCII art style animations like a
simple progress bar.
Gutter icons
If youve used Sublimes bookmarks feature, youve seen gutter icons in action. You can programmatically set and remove icons, and you can use the default icons that ship with the theme or you
can package your own in your plugin.
Dialogs
You can create dialog and confirmation boxes and display them to the user, as well as respond to
the users choice. Starting with Sublime Text 3, dialogs look like native operating system dialogs.
Console
The console is primarily useful during plugin development, when you can use it to try out snippets
of Python code and see error messages.
But users can also use it to invoke your plugin and manipulate plugin settings directly. You can also
use it to output debugging messages to help users troubleshoot issues they might encounter with
your plugin.
Menus
Most of Sublimes menus are open for extension. You can add entries that open files, run pluginrelated commands, and even run external programs.
The menus you can modify include the main application menu and the context-specific menus that
display when the user right-clicks a Sublime interface element such as files and folders in the side
bar or tabs in the tab well.
10
Popout menu
The most familiar use of a pop-out menu is Sublimes completion feature:
You can create pop-outs programmatically to support inline selection from a list of options.
Output panel
The output panel is a temporary buffer that allows you display output from a command. Sublime
uses it to display the output from build commands. It appears at the bottom of the screen and it
disappears when it loses focus.
Input panel
Plugins can prompt for user input using an input panel. An input panel appears at the bottom of the
Sublime window and usually consists of a long text input field:
11
The AdvancedNewFile plugin uses an input panel to allow the user to specify a file path and name
for a new file, then creates the file and any parent folders that dont already exist.
Keyboard shortcuts
Good keyboard shortcuts are a critical user interface element, and Sublime allows plugins to define
their own shortcuts. Shortcuts are created declaratively using JSON-formatted file.
User settings
If your plugin makes assumptions or choices for the user, you can allow the user to override your
decisions by creating user settings and making your preferences the defaults. User preferences are
created declaratively via a JSON-formatted file.
https://github.com/skuroda/Sublime-AdvancedNewFile
12
The Git plugin includes a setting that allows users to specify the path to their preferred Git
executable.
Another use for user settings is to allow you to include additional the user to enable a mode in your
plugin that you want to default to off. Many plugins include a debug setting that prints verbose
output to the console.
Background threads
Long-running operations like calling web services or accessing the file system can cause Sublimes
user interface to lock up until they complete, so Sublime allows plugins to shift these tasks to
background threads. Using background threads allows you to display status updates to the user
and allow the UI to continue to respond to user interactions.
Events
Sometimes youd like your plugin to run automatically in the background, rather than requiring the
user to invoke it explicitly. Sublime provides a number of event hooks that allow you to listen for
important occurrences and act accordingly.
Sublime provides events that fire immediately before and after a file is saved, when text is modified,
when text is selected, or when the window loses focus.
The SublimeOnSaveBuild plugin uses an event handler approach to run the currently selected build
task every time the user saves a file.
Windows
An instance of Sublime can have multiple windows, each with its own set of tabs or its own project
open. Plugins are able to detect which Sublime window is active, access a list of all windows, and
even close windows.
Projects
You can access the contents of the current project file, which is a JSON file. You can also make
changes to the project file and save them.
This capability would enable you to create a plugin that automatically adds a custom build task to
project files.
https://github.com/kemayo/sublime-text-git
https://github.com/alexnj/SublimeOnSaveBuild
13
Tabs
Sublime allows plugins to manage tabs, including open and close them, and moving them between
panes, and changing the currently focused tab group. Youre also able to check whether a tab contains
unsaved changes.
The Origami plugin uses the tab management API calls to allow users to easily navigate between
panes and shuffle files around in a multi-pane layout.
Editing area
Sublime supports a coordinate system that allows plugins to determine the visible area of the screen.
This system also allows you to scroll up and down to offscreen areas of a file or center a specific
point on the screen.
Clipboard
You have ready access to read the contents of the clipboard, and you can write to the clipboard as
well.
The Markdown2Clipboard plugin uses this capability to let users convert Markdown to HTML with
a simple copy and paste.
System resources
Plugins can access the host machines resources by importing relevant Python modules. This
provides a lot of power and flexibility.
The os module, for example, allows your code to work with the file system and makes it easy to
create, open, read, save or delete files.
External programs
Sublime plugins can also invoke command line programs, which opens up a whole world of potential
integrations with Ruby and its myriad gems, Node.js modules, shells like bash and PowerShell, and
traditional command line tools such as git and rsync.
The JSHint plugin uses Node.js to run the contents of the current file through the JSHint syntax
checker, which you can easily install through the Node.js package management tool.
https://github.com/SublimeText/Origami
https://github.com/fanzheng/Sublime.Markdown2Clipboard
https://github.com/uipoet/sublime-jshint
14
Plugins can also invoke GUI tools. The SideBarEnhancements plugin adds a command to the side
bar that allows the user to open a .psd file in PhotoShop just by right-clicking the file name.
And plugins can also call web services to perform tasks. Package Control is an excellent example of
this. The Gist plugin uses the GitHub APIs to allow users to create and edit gists directly in Sublime.
https://github.com/titoBouzout/SideBarEnhancements
https://github.com/condemil/Gist
Missing APIs
In addition to the restrictions imposed by the fact that Sublimes core is native code, there are a
number of API hooks that plugin developers have clamored for but just dont exist yet. These are
probably not deliberate omissions, and they seem likely to appear in future versions of Sublime. The
following is a sampling of the most frequently requested features from plugin developersand the
Sublime users who want plugins based on these features.
At the top of this wish lists is more API calls to manage the side bar and mini map. Plugin developers
have asked for more access to theme the side bar, as well as apply custom icons to files and folders.
Support for drag-and-drop file management is also missing.
16
Likewise, theres very little you can do with the mini map with it, and developers have asked for
more control over its appearance.
In the editing window itself, theres no way to set custom background colors behind individual pieces
of text. And Sublime doesnt support a concept of tool tips, bits of text that appear when you hover
over a portion of code, which are frequently used to add documentation and hints in other editors.
Many developers also want the ability to allow user input from the Sublime console. Users can run
Python commands from the console, and developers can display program output there, but theres no
way to run a plugin interactively via the console, accepting user input before continuing execution.
Plugin loading
The way Sublime loads plugins also imposes some restrictions.
For starters, the order in which your plugin is loaded is outside of your control. Sublime compiles a
list of installed plugins and performs some sorting internally to determine when each plugin should
load. This can cause cause unpredictable conflicts. If another plugin loads after yours and happens
to remap one of your keyboard shortcuts, the other plugin wins.
Sublime Text 3 takes some steps to reduce the chance of conflicts by introducing function
namespacing. In Sublime Text 2, you could easily declare global functions that had the potential
for naming collisions.
Another restriction is that you shouldnt attempt to call the Sublime API while your plugin is loading.
Most of the time, your plugin will be loading when the user is launching Sublime, and to keep the
load times minimal you shouldnt try to make API calls. The application is also launching, so you
cant depend on the application state.
There are a few exceptions to this: Its permissible to use a few API methods that allow you to check
which version of Sublime is running, which allows you to support multiple versions.
Sublime Text 3 actively enforces this and ignores calls to the Sublime API that occur while the plugin
is loading.
Command sandboxing
As well see shortly, commands are at the core of most Sublime plugins, and Sublime places several
restrictions on commands that effectively sandbox them from each other.
While commands can invoke other commands, youre limited in what you can pass between
commands. Only Sublime can pass live Python objects to a command. Any data that you need
to send to another command must be in the form of a JSON-formatted string.
17
4 Hello World
Before we dive into writing a full-featured plugin, lets first look at a simple example so we can see
the basic structure of a Sublime plugin. You know its inevitable, so lets get right into Hello World.
This is a fully functional plugin, but before we try it out, lets rename the command class to
HelloWorldCommand:
import sublime, sublime_plugin
class ExampleCommand(sublime_plugin.TextCommand):
class HelloWorldCommand(sublime_plugin.TextCommand):
def run(self, edit):
self.view.insert(edit, 0, "Hello, World!")
Now were ready to take the plugin for a spin, but first we need to install it.
Hello World
19
These modules define the Python base classes that plugins derive from, as well as the methods that
make up the Sublime API. (In Python, importing a module makes its functions available for use in
the current file, similar to the way youd link in a JavaScript library on a web page or use a require
in Ruby.)
Next, we define the actual plugin:
class HelloWorldCommand(sublime_plugin.TextCommand):
Hello World
20
This line includes several elements worth pointing out. First, note that the plugin extends the
class sublime_plugin.TextCommand by passing the name of the base class in the class definition.
In Python, extending a class is similar to inheriting from a class in other languages. Sublime
defines several base classes for plugins to use, and plugins that manipulate text generally extend
TextCommand.
Next, take a look at how the plugin class is named. The name uses camel casing (first letter of each
word capitalized) and ends in Command. When Sublime loads plugins, it examines all files with a
.py extension, looking for classes that end in the word Command and that extend one of Sublimes
Command classes. As it loads each plugin, it converts each command name by dropping the word
Command from the end and converting the remainder from camel case to snake case (all lowercase,
with underscores to separate words). In our example, HelloWorldCommand becomes hello_world.
In addition to extending a Command class and respecting Sublimes naming conventions, a plugin
must also define a run method:
def run(self, edit):
The run method is the jumping-off point for your code. Sublime calls this method when the
command is invokedafter that, youre free to do just about anything, including calling other Python
modules and classes and using the Sublime API to make your plugin go.
The run method takes two parameters, both Python objects, that are passed in by Sublime. In Python,
the first parameter of any method is self, a reference to the classs current instance. The second
parameter, edit, is an instance of the Edit class. This class allows you to bundle multiple text changes
together so they can be rolled back as a group if necessary.
Thats it for the boilerplate plugin code. The final line is where we get down to business and add
our plugins features:
self.view.insert(edit, 0, "Hello, World!")
To actually say Hello, World! we need to insert some text into a tab.
Since all TextCommands allow you to manipulate text in the editing window, each TextCommand has
an attribute that points to the currently active tab, self.view. Here were accessing the view to call
its insert method, which takes three parameters: the edit object for the text command, an index
number, and the string to insert. The index indicates what point to insert the string, and a value of
0 means to insert the text at the beginning of the document.
21
Hello World
At this point, the plugin is just a simple command with no user interface. But you can execute any
Sublime command manually by invoking it from the console.
Before you run the command, though, open a fresh tab to give our TextCommand a clean view to
work with.
Then click View | Show Console and enter the following:
view.run_command("hello_world")
Were again referencing the current view and calling the run_command method, passing the name of
the command to execute as a string.
If the plugin is properly installed, youll see our greeting at the top of your tab:
Incidentally, a TextCommand always needs a view to work with, so if wed called run_command when
no tabs were open, Sublime will create a new tab.
II Event listeners
5 Introducing the
KeyBindingInspector plugin
Now that you understand the basics of what makes up a Sublime plugin, its time to dig in and start
writing something useful.
The inspiration for the plugin that were going to build came from a Twitter follower who wanted to
know if there was a way to tell what any given keyboard shortcut was supposed to do. While it seems
like an easy question to answerjust press it!its often not that simple. Many keyboard shortcuts
only fire under certain conditions, or behave differently depending on the type of file youre editing.
For example, the SmartMarkdown plugin adds keyboard shortcuts that are only active when youre
editing a Markdown file. When youre editing a blog post with SmartMarkdown installed, the Tab
key will fold a section when the cursor is positioned on a headline; otherwise it just indents the text.
The KeyBindingInspector plugin is the result of my attempts to answer this question. I say
attemptsas youll see, it took me two tries to arrive at a workable solution. But I decided to
include the false start as well because it illustrates how to listen for and handle events from the
editor rather than responding to user instructions. It also exposes some limits in Sublimes APIs,
and that knowledge might prove useful as you write your own plugin.
6 Setup
To kick things off, lets create, install and test a basic Hello World plugin. Its always a good idea to
test that youve got the simplest possible case working before you get too far into a new project so
you dont waste time later debugging code thats not working, only to find that you saved your
plugin in the wrong folder or something.
Click Tools | New Plugin. Sublime opens a new tab with the plugin boilerplate.
Click File | Save to open the file save dialog. Sublime preselects your Packages directory. Create a
new folder named KeyBindingInspector, then save the plugin file as KeyBindingListener.py.
Pop open a new tab, then open the Sublime console by clicking View | Show console. Test your new
plugin with the following command:
view.run_command("example")
25
Setup
It certainly helped me on more than one occasion. When I was halfway through writing KeyBindingInspector, I thought I noticed that the plugin was taking forever to run. What did I break? I started
to wonder.
Since I was using version control, I just reverted to an older version of the plugin and tested the
performance. As it turned out, my computer was just running slowly that daythe old version took
just as long to run as the latest code. I was able to set my performance concerns aside and get back
to work.
A how-to on Git or another version control tool is out of scope for this book. If youre looking for an
easy way to get started, check out GitHub for Mac and GitHub for Windows. They provide a nice
graphical user interface that lets you perform basic Git commands and easily view your changes.
And of course, there are several excellent Sublime plugins that allow you to interact with git without
leaving Sublime. The Git plugin is a favorite as of this writing.
http://mac.github.com/
http://windows.github.com/
https://github.com/kemayo/sublime-text-git
Setup
26
Regardless of which tool you select, youll need to initialize a new git repository inside the
KeyBindingInspector folder, then add and commit your new plugin file.
7 Implementation overview
My initial idea for this plugin involved printing the command name to Sublimes status bar each
time the user pressed a keyboard shortcut. Lets get a quick overview of whats required to make
this approach work.
First, we need a way to react when a keyboard shortcut is triggered. Sublime allows plugins to
respond to events by creating an event listener. To meet the plugins requirements, we need to be
notified every single time a keyboard shortcut is triggered, regardless of whether the command its
bound to will actually execute.
Once our event listener gets called, we need to somehow retrieve the name of the command thats
going to execute in response. I was a little fuzzy about how this might be doable; I hoped that Sublime
kept a global map somewhere of keyboard shortcuts and command names somewhere that I could
access.
Another avenue I considered was piggybacking off of Sublimes command logging feature, which
prints the name of each command to the console as it executes. Perhaps I could scrape the console
history and get the name of the most recent command from that. This felt like a hack, though, and
one that was likely to be fragile.
A slightly better option might be to grab the last entry from Sublimes command history and display
that. That still seemed fragile, though, and I also didnt like that relying on history means my plugin
is forever stuck in reactionary mode, responding to commands after they happened and trying to
guess which one was triggered by the most recent key binding.
Once we get the command name, the rest is easy. Well do some simple formatting to convert the
command name from snake case to something more aesthetically pleasing, then insert a message
into the status bar.
Finally, well add a setting to allow the user to toggle the plugin on and off so it wouldnt be a
constant drag on Sublimes performance.
29
The actual name of an event listener class isnt significant, but it shouldnt end in Command.
Now delete the run method, and in its place define an on_window_command method:
import sublime, sublime_plugin
class KeyBindingListener(sublime_plugin.EventListener):
def run(self, edit):
self.view.insert(edit, 0, "Hello, World!")
def on_window_command(self, window, name, args):
print("The window command name is: " + name)
print("The args are: " + str(args))
By defining the on_window_command method, were notifying Sublime that wed like to be notified
whenever a WindowCommand executes. Well cover WindowCommand in more detail later, but you can
think of them as commands that dont operate on the contents of an existing tab. Examples of
window commands include opening and closing layout panes and tab groups, switching tabs, and
creating a new tab.
As a brief aside, youll need to use Sublime Text 3 for this portion of the tutorial. I didnt realize it
when I was working through this example originally, but on_window_command is new in Sublime Text
3. Id intended to target Sublime Text 2 with the initial version of this plugin, but while I was working
31
on this part I was still getting my bearings and didnt realize that Id chosen an implementation that
wouldnt be backward compatible.
The on_window_command method accepts four arguments. The first, self, is a reference to the current
instance of KeyBindingListener. The window argument points to the active Sublime window. The
last two are the most relevant for our purposes: name is a string containing the name of the command
that is executing, and args is a list of arguments that the command will receive, which might include
things like context rules that determine whether a command should fire.
The last two lines just print the name of the currently executing command to the Sublime console,
as well as the list of the arguments that the command will receive when it executes.
Lets try out the new plugin. Since were modifying a previously installed plugin, all that you need
to do to activate the plugin is save the file. Sublime will load our new event listener automatically.
Open the console by clicking View | Show console or with the keyboard shortcut Ctrl+. Showing
the console is a window command, so it triggers our plugin, and when the console appears, youll
see a message similar to the following:
The window command name is: show_panel
The args are: {'panel': 'console', 'toggle': True}
To trigger another window command, click View | Side Bar | Hide Side Bar. The side bar closes,
and you see the following output:
The window command name is: toggle_side_bar
The args are: None
Handling events
Now that we have a basic event handler working, we need to determine which events to handle in
order to achieve our goals for this plugin. Remember that we want the plugin to execute every time
the user presses a keyboard shortcut, then let the user know what command the shortcut is firing.
At first glance, on_window_command and its counterpart, on_text_command, seem perfect for our
purposes. Lets add an event handler for on_text_command now and see if this pair of events will
meet our requirements.
Below the definition for on_window_command, add a definition for on_text_command:
32
Save the plugin to reload it, and were ready to test again.
First, try triple-clicking a line of text. This fires a text command that selects the entire line, and you
should see a message in the console similar to this:
The text command name is: drag_select
The args are: {'event': {'x': 661.1171875, 'y': 181.07421875, 'button': 1}}
Now position the cursor somewhere in your plugin code and press Ctrl+Space, which triggers the
autocomplete popup. Youll see the following message:
The text command name is: auto_complete
The args are: None
Not bad. Were triggering key bindings and getting information about the commands that Sublime
is executing, and it seems like were well on our way.
Theres a catch, though. Try launching the command palette by pressing Ctrl+Shift+P on Windows
and Linux or Shift+Cmd+P on Mac. The command palette appears, and in the console youll see
nothing. What black magic is this?
To get a better understanding of what is going on, lets enable command logging. Run the following
command in the Sublime console:
sublime.log_commands(True)
Now every time a command executes, its name is printed to the console. Open the command palette
again, and this time youll see the command name and its arguments:
command: show_overlay {"overlay": "command_palette"}
33
As it turns out, some Sublime key bindings dont execute window or text commands. The show_overlay command is defined in Sublimes native core. While the keyboard shortcuts for showing
the command palette are bound in the same .sublime-keymap files as other Sublime commands, the
command itself isnt exposed to event listeners in the same way as commands that are defined in
Python classes.
And this is just one example. There are other commands that are defined in the same way, and theyre
all beyond the reach of our plugin code. Our plan to use on_window_command and on_text_command
seems to have hit a dead end.
And now that we see how these events operate, its clear that theres another fatal flaw in this design.
These events only fire when a command is triggered, but we want our plugin to fire whenever a key
binding is pressed, regardless of whether its already bound to a command yet.
Maybe on_window_command and on_text_command just arent the best choices. What other events
might be useful?
Most of Sublimes other events are tied to opening, closing and saving files or activating or
deactivating the Sublime window, shifting to and from another application. But there are two
events that seem like they could be useful: on_modified and on_query_context. Can we use these
to supplement our existing events?
To see on_modified in action, add an on_modified handler:
import sublime, sublime_plugin
class KeyBindingListener(sublime_plugin.EventListener):
def on_window_command(self, window, name, args):
print("The window command name is: " + name)
print("The args are: " + str(args))
def on_text_command(self, window, name, args):
print("The text command name is: " + name)
print("The args are: " + str(args))
def on_modified(self, view):
print("The on_modified event fired!")
Save the file. Now lets see when this event is triggered. In the plugin file, hit Enter twice to insert
a couple of blank lines, then type def to trigger Sublimes autocompletion for the Python function
snippet. Press Tab to insert the snippet. In the console, youll see something like the following:
The
The
The
The
The
34
The on_modified event fires any time you make a change to a text bufferadding or removing
characters. It will fire when a command inserts text into the editing pane, such as a snippet or
autocompletion. But it doesnt help us with the key binding plugin were trying to write.
And what about on_query_context? To get a feel for how this event works, make sure youve deleted
the code inserted by the def snippet, then add these lines to your plugin file:
import sublime, sublime_plugin
class KeyBindingListener(sublime_plugin.EventListener):
def on_window_command(self, window, name, args):
print("The window command name is: " + name)
print("The args are: " + str(args))
def on_text_command(self, window, name, args):
print("The text command name is: " + name)
print("The args are: " + str(args))
def on_modified(self, view):
print("The on_modified event fired!")
def on_query_context(self, view, key, operator, operand, match_all):
print("The on_query_context event fired!")
Save the file. Now open a new tab, then use the command palette to execute the Set Syntax:
JavaScript command. Type the following into your unsaved JavaScript file:
function () {
Check the console output, and youll see something like this:
The
The
The
The
The
35
The on_query_context event fired before the insert_snippet command executed. When youre
editing a JavaScript file, typing a { character may seem like its inserting an opening brace, but its
actually a keyboard shortcut that uses a snippet to insert the opening and closing curly braces, then
position the cursor between them.
This key binding is defined in Sublimes default key binding file, and it looks like this:
// Auto-pair curly brackets
{ "keys": ["{"], "command": "insert_snippet",
"args": {"contents": "{$0}"}, "context":
[
{ "key": "setting.auto_match_enabled", "operator": "equal",
"operand": true },
{ "key": "selection_empty", "operator": "equal",
"operand": true, "match_all": true },
{ "key": "following_text", "operator": "regex_contains",
"operand": "^(?:\t| |\\)|]|\\}|$)", "match_all": true }
]
},
The key here is the context array. Before the snippet executes, Sublime checks the surrounding
text to make sure its appropriate to insert a closing curly bracket. The rules specified in the key
bindings context array determine whether the bound command should fire. These context rules
give you fine-grained control over when a key binding should execute. For example, you could bind
the same shortcut to insert different snippets based on the language of the file youre editing.
In this case, the context rules specify that the users settings must allow auto closing of braces, the
user must not have any text selected, and the text immediately following the cursor must match a
specific set of characters.
If you look at the signature of on_query_context, you can see that each of the parameters matches
a property in the objects in the context array.
Thats all interesting, but does it help us with our key binding plugin? The on_query_context event
clearly fires when keyboard shortcuts are triggered, so in that sense it seems like it might be useful.
However, its only triggered when a key binding includes a context element to restrict when it can
operate. Many keyboard shortcuts dont define a context and thus wont trigger this event.
10 Dead end
It looks like were at an impasse in our attempt to create this plugin using event listeners. Sublime
simply doesnt expose the right hooks for us to respond to every single keyboard shortcut.
And even if we were able to capture every keyboard shortcut, theres still the problem of mapping
shortcuts to commands, and my research didnt turn up any way to query Sublime to find out what
command a shortcut will trigger.
Fortunately, theres another promising approach we can try. It wont work quite the same way, but
itll allow the user to accomplish the same thing in the end.
Ready to switch gears?