Building a SublimeText command from scratch (and other useful bits)

Published
2013-07-18
Tagged

Note: Since I started writing this, SublimeText3 came out. Hopefully I’ve caught any problems ST3 has with my methods.

SublimeText is my current text editor of choice. While I use a few others for specific tasks1, if I’m just going to chuck words in a file I’ll probably find myself using Sublime Text. One of the nice things about ST is the extensive plugin framework: a series of python files in a pretty simple file hierarchy makes for a bunch of hackability.

A while back I decided to make a ST plugin for collecting markdown reference links. I know that this plugin exists in a number of packages, but for whatever stubborn reason I didn’t want to install someone else’s scripts, I wanted to do it myself. While I found a couple of tutorials on the web for this task, there was nothing that really told me everything I wanted to know, so I had to muddle through it myself. Now I know what’s going on, I figure I should write this down, either to help others in their quest to write ST plugins, or simply for future reference.

The many sections of a SublimeText plugin

A basic SublimeText plugin looks like this:

1
import sublime, sublime_plugin
2
3
class FooCommand(sublime_plugin.TextCommand):
4
    def run(self,edit):
5
        //code goes in here

This is a really, really bare-bones plugin. If you save this to SublimeText’s Packages directory (available via Preferences>Browse Packages...), or into a folder within this directory2, it’ll get scanned and added to ST’s list of commands (assuming you save it with the .py extension).

Note: this is not the same as having it pop up in the Command Palette (that is, the menu you get when you his ⌘+⇧+P). As it stands, you can run your plugin by opening up SublimeText’s console (^+`) and typing view.run_command('command_name'). command_name is equal to the name of the class you just created, in snake_case. If your plugin ends with Command, leave that off. So FooPlugin would be foo_plugin, but FooCommand would just be foo. I’ll go over how to put this in the command palette and make pretty shortcuts for it later on - for the moment we’ll just run it through the command palette.

Running commands through the console

You’ll note that this is a text command. It’s possible to make other commands, but I have the most experience with text commands, so I’ll stick with them for now.

There’s only a few other methods that SublimeText plugins take. The main one that’s of use is the is_enabled method:

1
def is_enabled(self):
2
    return true

This method tells you if a given plugin is enabled. If it’s not, it won’t show up in the command palette ever and generally won’t run. It’s handy because you can combine it with some pretty nifty in-program logic:

1
def is_enabled(self):
2
  return self.view.settings().get('syntax') == 'Packages/Markdown/Markdown.tmLanguage'

This plugin will only run when your syntax is set to Markdown.

Retrieving bits of the document

This is all good only if we can do something in our plugin. Thankfully, SublimeText has a pretty good API for this sort of thing. I think you can do a lot with just getting the document’s contents, scanning through it, and making modifications based on simple logic, so I’m going to go through some of the commands for doing just that.

Most text-related commands are contained within SublimeText’s view object, accessible in text commands via self.view. As an example, if you want to search the document for a particular string, you can use the view‘s find method:

1
view.find('foo',0)

You can do similar things with find_all, which will grab all instances of the string. The string also accepts regular expressions, which is handy.

find and find_all return Regions, which are basically a set of beginning and ending positions. A number of view methods will take regions as arguments, so sometime you just have to pass it from one place to another; if you want to start poking around inside it, the above-mentioned API reference has a pretty good guide to its methods.

Modifying the document

All modifications to a SublimeText document are done within a given Edit. When you undo a change, it undoes all the work of the last edit. This means if you make a command that does fifteen find-and-replaces in your document, but does these all under the one Edit, when you undo that last command it’ll undo all fifteen find-and-replaces.

You can make your own Edit objects if you want to split your command into a number of bits3, but really, most of the time you can just use the edit that gets passed into the run command.

Let’s make a command that will replace every instance of the word “FOO” with “BAR”, as a simple example:

1
import sublime, sublime_plugin
2
3
class FooCommand(sublime_plugin.TextCommand):
4
  def run(self,edit):
5
    for region in self.view.find_all("FOO"):
6
      self.view.replace(edit, region, "BAR")

The only new thing here is calling self.view.replace. This replaces a given region with a string. We wrap this all up in the one edit command.

Between these examples and the API, you should be able to make your command do what you want with the document. It may require some fiddling around, but hopefully you now have enough tools to do a bunch of fiddling and go from there.

Accessibility

So far, your best way of getting to the command is through SublimeText’s control panel. Which, let’s face it, isn’t very user-friendly. You can access most commands through:

  1. The command palette, and sometimes also
  2. A key binding

Command palette

The command palette is populated on a by-syntax basis. If you’re currently using language foo on a file, then you’ll get commands that are defined in Default.sublime-commands (mine is in the Default folder in the plugins directory), and those defined in any files called foo.sublime-commands.

Unlike .py files, SublimeText seems to find sublime-commands files wherever they are in your plugin hierarchy.

sublime-commands files are JSON files that have the following syntax:

1
[
2
  {
3
    "caption": "What you see in the command palette",
4
    "command": "command_name"
5
  },
6
  //More commands
7
]

Generally, I make sure my commands are available via the command palette. Since the command palette is filtered by language, and fuzzy-text searching is ridiculously fast, it doesn’t matter if you stick ten extra commands in there.

Running commands through the palette

Key binding

This is the best way of accessing commands that you use all the time. However, it does require another set of files.

First, you need to put your key bindings in a file called “Default.sublime-keymap”.4 You can’t call it “.sublime-keymap” I’m afraid.

This is also a JSON file, and the layout looks something like this:

1
[{
2
  "keys" : ["super+i"],
3
  "command": "insert_snippet",
4
  "args": {
5
    "name": "/Packages/User/italic.sublime-snippet"
6
  }
7
}]

This is a simple keybinding that maps super+i (which is ⌘+i on OS X, Ctrl+i on Windows/Linux) to a little snippet. Now, wherever you are, you can type super+i and have your text italicised.

If you don’t want to use the snippet outside of the keybinding, you can always specify it inline with the contents key:

1
[{
2
  "keys" : ["super+i"],
3
  "command": "insert_snippet",
4
  "args": {
5
    "contents": "*${1:$TM_SELECTED_TEXT}*$0"
6
  }
7
}]

There is a slight problem here - regardless of where you store your keybinding file, this keybinding will work wherever you are. All is not lost, however, since you can limit where key bindings apply with contexts. Here’s an example of a keybinding with a context:

1
[{
2
  "keys" : ["super+i"],
3
  "command": "insert_snippet",
4
  "args": {
5
    "name": "/Packages/User/italic.sublime-snippet"
6
  },
7
  "context": [{
8
    "key": "selector",
9
    "operator": "equal",
10
    "operand": "text.markdown"
11
  }]
12
}]

A context is an array of criteria that need to be fulfilled for Sublime Text to trigger a key binding. A handy list of possible contexts is available here. While the syntax is a little bulky, by using contexts you can ensure that your keybindings only trigger when you want them to.

Packaging and storing

Based on this guide, you should be able to make a series of SublimeText commands, give them applicable names in the comand palette, and assign shortcuts as required. You should also be able to limit the scope in which a command applies, so it doesn’t show up in your command palette (and the keybinding doesn’t trigger) when you don’t want it to. The last thing to do is to store and organise commands in packages.

You can get to your packages directory in SublimeText by selecting Sublime Text→Preferences→Browse Packages… from the menu. In OS X this is ~/Library/Application Support/Sublime Text 3/Packages. Sublime Text loads files from folders in the package directory in the following order:

  1. Files in the Default folder
  2. Files in other folders, alphabetically
  3. Files in the User folder

Files will overwrite previous key bindings, commands, etc. So if you have the key super+i bound in keymaps in Default, APackage and BPackage, hitting super+i in SublimeText will trigger the command as specified in BPackage.

It’s generally good practise to put “miscellaneous” snippets, commands, key bindings etc. in the User directory, but once you have two or three files for one language you might want to split them off and put them in their own folder. If you want to transfer your files between computers (or share them with others), you can make a sublime-package file in the following manner:

  1. Zip up the directory containing your files.
  2. Change the extension from .zip to .sublime-package

You can place this in the Packages directory and SublimeText will read it as if it were a folder.

But where’s all the default packages?

In Sublime Text 3, the Packages directory looks practically abandoned compared to ST2. This is because all the default packages are stored alongside the program itself. In OS X you can find the packages inside the app bundle under MacOS/Packages. These are all stored as sublime-package files, so if you want to examine the contents of them you just have to make a copy somewhere else, then rename to .zip and unzip them.

Conclusion

Hopefully at the end of this you can create commands for Sublime Text 3, bind them to the command palette or key combinations, and store them properly. Some of this I’m still learning (and the release of ST3 has meant I’ve had to re-learn some things), but this guide contains most of the stuff I had no luck googling or ended up figuring out by myself. SublimeText’s API reference is pretty robust, so between this guide and the references linked to throughout the article, you should be able to work out how to get SublimeText to do what you want.


  1. Notably: NVAlt is my “note archive” where I put anything I’ll need to find again, while Byword is my favoured writing app on iOS 

  2. But no deeper - ST only checks the Packages directory, and directories inside of it, for plugins. Anything buried further it ignores. 

  3. Details in the API again 

  4. You can technically get away with calling it “Default (OSX).sublime-keymap”, “Default (Windows).sublime-keymap” or “Default (Linux).sublime-keymap”, but then presumably it’ll only work on that OS. Handy? Sometimes.