Skip to content

Instantly share code, notes, and snippets.

@robmccormack
Last active December 21, 2015 07:59
Show Gist options
  • Save robmccormack/6275300 to your computer and use it in GitHub Desktop.
Save robmccormack/6275300 to your computer and use it in GitHub Desktop.
Tuts+ DRAFT Article

The Joy of Coding a Simple Sublime Text Plugin

Rob McCormack on August 30th 2013 with 48 Comments

######Tutorial Details

Program: Sublime Text
Version: 2.0
Difficulty: Beginner
Estimated Completion Time: 45 minutes

In this tutorial, I'll show you how to create a really simple Sublime Text 2 plugin which inserts TODO style comments and today's date into your code. We'll cover the basics of Sublime Text's:

  1. Plugin programming language (Python)
  2. Snippets
  3. Settings files

Sublime Text is a rising star in the world of coding text editors - available for Windows, Mac and Linux platforms. In the few months I've used Sublime Text, it has made me a better programmer, drastically improved my work-flow efficiency and it makes coding more enjoyable. Coming from a Java-based editor I'm impressed with the blinding speed of Sublime Text. Curiously, there is currently no enforced time limit for the evaluation. Once in a while, you'll get a message encouraging you to register but the software continues to work perfectly.

There are about 2,000 free plugins available. You can install these in just a few seconds with the popular Package Control plugin. Plugins range from programming tools and snippets to screenplay editors. There are even plugins for such things as task managers (GTD) and time tracking/billing. With so much available for free, why bother to write your own plugin?

The TOP 5 reasons to create your own plugin:

  1. With a basic knowledge you can easily customize existing plugins to your needs
  2. For small specific tasks, like the one you're about to learn, a larger plugin can be "too much" and get in your way
  3. You may find, with just a few lines of code, you will create a plugin that you'll use every day
  4. It's fun
  5. It's fun!

######Note:

Sublime Text plugins are written in Python. If you are new to Python, no worries, you'll catch on fast, even if you are only experienced with HTML and JavaScript. Net Tuts+ provides a great Python learning guide: The Best Way to Learn Python.

Objective

Your plugin will insert comments like this:

Monokai Bright theme

Sublime Theme: Monokai Bright theme

Comment Format:

// TODO (Your Name/email) (some note here) - (today's date)

Our plugin is not just a comment pasted into the code. You can interact with the comment by pressing tab to make edits within the comment.

Standardizing comments for TODO's and bug fixes is a good programming technique. Companies like Google will require this exact same technique in their coding Best Practices or Style Guides.

You will see how easy it is to make your plugin accessible from the main menu, a context menu, a hotkey and Sublime Text's best feature of all,the Command Palette. As an added bonus, I'll show you how your plugin can be combined with an existing plugin to further increase your productivity.

###Tutorial Hotkey Nomenclature

Our plugin will work on Windows, Linux and OS X. However, the hotkeys shown are for the Mac OS X platform.

Windows/Linux users will need to use ctrl when they see cmd (OS X) in this tutorial.


###Step 1 - Hello World!

We'll start with the obligatory "hello world!" example with some help from Sublime Text. From the main menu select:

Tools | New Plugin...

A starting template is inserted in a new document:

import sublime, sublime_plugin

class ExampleCommand(sublime_plugin.TextCommand):
    def run(self, edit):
        self.view.insert(edit, 0, "Hello, World!")

It's important to save this file in the proper directory.

Select:

File | Save

with file name:

tododate.py

The save dialog should open to the directory: /Sublime Text 2/Packages/User/ The file must must be saved in this directory.

######Note:

Note the paths to the Packages folder on other platforms:

Windows: %APPDATA%\Sublime Text 2\Packages\User\tododate.py
OS X:    ~/Library/Application Support/Sublime Text 2/Packages/User/tododate.py
Linux:   ~/.Sublime Text 2/Packages/User/tododate.py

Portable Installation: Sublime Text 2/Data/Packages/tododate.py

The Packages/User is a catch-all directory for custom plugins, snippets, macros, etc. Consider it your personal area in the Packages directory. It's very important to be aware of this. Sublime Text 2 will never overwrite the contents of Packages/User during upgrades.

###Step 2 - Running the plugin

Before we go any further, let's make sure this works.

  • Open the Sublime Text's Python console by pressing ctrl + ` (the back tick on keyboard)

This is a Python console that has access to the API and is most useful. If you have errors, they will be reported in the console.

  • At the console prompt type in:

    view.run_command('example')

  • Press return

Hello world! should be inserted at the very top of your plugin. Be sure to Undo this inserted text as it will prevent the plugin from running.

Hello world in sublime

Notice that we are "running" our class, and not our file name. The view.run_command('example') runs our class, as the word example refers to the same word contained in ExampleCommand(). This is how the Sublime Text API works.

###Step 3 - Plugin Anatomy and Coding Standards

Before making changes, it's important to know what is going on with the code. Here is a commented copy of what we have done so far.

import sublime, sublime_plugin
# imports the modules provided by Sublime Text

# this class provide access to the contents of the selected file/buffer via an object
# commands need to implement a .run() method in order to work

class ExampleCommand(sublime_plugin.TextCommand):
    def run(self, edit):
        # simply inserts Hello word! at position 0, the top of the document
        self.view.insert(edit, 0, "Hello, World!")

Warning

If you are new to Python, the first thing you have to know is that code indents are part of the code. There are no ending symbols like } or end if. The code blocks starts and ends based on indentation. Even a single space out of place will prevent your plugin from running (an "indentation" error will be displayed in the Python console). While this seems like a harebrained idea at first, I've grown to appreciate it over time.

For indenting, you may use tabs, or spaces. I use 4 spaces all the time and never tabs. It is considered good practice.

The bit of code which Sublime Text provided to us uses tab's for indentation. We'll let Sublime Text fix that in a nano-second. Here's how:

  1. Click on bottom right of the Sublime Text window - on the text Tab Size: 4
  2. Select Convert Indentation to Spaces
  3. Note: I don't have Indent Using Spaces checked off, as Sublime just does that for me.

Convert tabs to spaces

TIP

If you click on Python, to the right of Tab Size: 4, you can change the language syntax.

Note

This tutorial will follow the Style Guide for Python Code:

http://www.python.org/dev/peps/pep-0008/

The Style Guide suggests that you put all import 's on separate lines and use two lines before every class - so why not make those minor changes now as shown:

import sublime
import sublime_plugin


class Example(sublime_plugin.TextCommand):
    def run(self, edit):
        self.view.insert(edit, 0, "Hello, World!")

Save your plugin with File | Save.

###Step 4 - Making it your own

We're ready to start programming the plugin.

  1. Rename the class to ToDoDate i.e. :

    class ToDoDateCommand(sublime_plugin.TextCommand):

  2. Now in the Python console try:

    view.run_command('to_do_date')

    enter

and Hello world! should again be inserted at the top of your code. Again, undo the inserted text.

Note

If you are wondering about the underscores in 'to_do_date', this is the way that the Sublime API works.

Sublime will take any class that extends one of the sublime_plugin classes, remove the suffix Command and then convert the CamelCase into underscore_notation for the command name. The only thing that we had to do when it was called Example was to lower case it to example since Example was a single word and not CamelCase.

TIP

When working in the Python console, you may use the up / down keys to recall commands.

###Step 5 - Add a menu item to execute the plugin

The least useful way to run a plugin is from Python console. So, let's add a convenient menu item to run our plugin.

The main menu of Sublime is contained entirely in a JSON file:

Packages/Default/Main.sublime-menu

There is nothing stopping us from adding a menu choice to this JSON file, but that's a really bad idea. This file will be over written when Sublime Text is updated, so we need to avoid this by adding our menu customization to a settings file in our Packages/User directory, the same directory where we created tododate.py. This directory is not touched by Sublime Text updates or updates to other plugins.

Let's add our plugin to the Edit menu.

Warning

Be careful with the first step below, you don't want to over-write the file in the Default directory ( Packages/Default/Main.sublime-menu ) or you'll delete all of Sublime Text's main menu.

You may want to make a backup copy of that file, just in case.

  • Check if there is already a file called: Packages/User/Main.sublime-menu in your User directory, and if so open it to make the changes.

  • If no file exists, then create a new file with:

File | New

  • Paste in this code:
[
{
    "id": "edit",
    "children":
    [
            {"caption": "To Do Date", "command":"to_do_date"}
    ]
}
]

Then save the file, in the correct directory, Packages/User/Main.sublime-menu in your User directory.

File | Save

You could pretty much figure this JSON file out on your own, but here are some details:

"id": "edit" refers to the Edit menu

"children": refers to the items under the Edit menu

{"id": "to_do_date"} a unique id for our menu item

{ "command": "to_do_date" } you guessed it! - The reference of our class, the same name we ran at the Python console prompt

TIP

When JSON files contain more than one piece of data (like a second menu choice), the format must follow this example. Note the comma separating the two menu items and there is no comma after the very last }. Sublime Text and plugins use many JSON files for settings files and data files. JSON files can be easily loaded asynchronously and faster than XML files.

[
{
    "id": "edit",
    "children":
    [
        {"caption": "To Do Date", "command":"to_do_date"}
    ]
},
{
    "id": "edit",
    "children":
    [
        {"id": "something_else"},
        { "command": "something_else" }
    ]
}
]

Warning

JSON is a very strict format and can be tricky. Make sure to get all the commas and quotes just right. There are many plugins that can assist you. I use: BracketHighlighter

  • Save the file in your Packages/User directory with name Main.sublime-menu:

    Packages/User/Main.sublime-menu

So we want to create another file (if it doesn't already exist) in our User directory, with the same name:

Packages/User/Main.sublime-menu

TIP

To navigate to preferences you can select Browse Packages … from your Preferences menu (On Windows/Linux preferences is located as a top level menu).

Preference menu

Your Edit menu should look something like mine:

Edit menu

Let's try running our plugin from the Edit menu.

1- Create a new file so we have a blank document File | New

2- Select Edit | To Do Date and see if Hello World! is inserted at the top of the new file

TIP

If you want to add a plugin to the context menu (right-click) menu, the process is exactly the same, except the file you would use is:

Packages/User/Context.sublime-menu

###Step 6 - Goodbye to "Hello World!"

Time to get rid of Hello World! as it has served its age-old purpose. Besides, it had a bad habit of adding the text at the top of the document - very annoying. The code below will insert a comment, at the location of the cursor.

import sublime
import sublime_plugin


class ToDoDateCommand(sublime_plugin.TextCommand):
    def run(self, edit):
        self.view.insert(edit, self.view.sel()[0].begin(),
                         "TODO YourName NoteHere")

Note:

self.view.sel()[0].begin() looks confusing, but it just inserts our comment at the current position of the cursor. The [0] is part of a Python list (array). Since Sublime Text has multiple cursors, a list of values must be returned. The zero position in the list will insert the text at the first position of the cursor.

TIP

It was optional, but keeping with PEP 8 -- Style Guide for Python Code, I wrapped the text so it doesn't exceed the recommended 79 character limit.

You can wrap any line in Python, if you wrap it within the ()'s. Otherwise you would use the \ character to continue a statement to the next line.

###Step 7 - Adding today's date

Python like all languages, offers many built-in date functions.

We need to set up a variable called d to hold the formatted date string.

d = datetime.datetime.now().strftime("%d %b %Y (%a)")

This simply formats today's date in the any format. I choose the date format: 16 Aug 2013 (Fri). You may choose other formats, refer to: Python's date format strings.

Our variable d is concatenated to our string, with + d as shown below.

But before our plugin will do anything with Python's built-in functions, we have to import Python's datetime module - as shown.

import sublime
import sublime_plugin
import datetime


class ToDoDateCommand(sublime_plugin.TextCommand):
    def run(self, edit):
        d = datetime.datetime.now().strftime("%d %b %Y (%a)")
        self.view.insert(edit, self.view.sel()[0].begin(),
                         "TODO yourname note @ " + d)

Now, running our plugin from the Edit menu in a new document we get:

TODO YourName NoteHere @ 16 Aug 2013 (Fri)

TIP

We have not asked the plugin to insert a comment prefix, (like //) since the type of comment prefix is language dependent. But you can use the hotkey cmd + / to add the appropriate comment to any language you are working with.

For example, cmd + / will add // in a .js file , <!-- --> in a html file and # in a Python file.

cmd + / is also a toggle, where it can add/remove comments - very handy!

Here is a commented version of our snippet, if you want to understand the code a bit better:

import sublime
import sublime_plugin
import datetime


class ToDoDateCommand(sublime_plugin.TextCommand):
    def run(self, edit):
        # full date, with abbreviated in ()    
        d = datetime.datetime.now().strftime("%d %b %Y (%a)")
        self.view.insert(edit, self.view.sel()[0].begin(),
                         "TODO yourname note @ " + d)

        # insert the variable 'd'  at current cursor position
        # since multiple cursors are supported, get the first position 
        # with:  view.sel()[0]

###Step 8 - Understanding Sublime Text's Snippets

We could stop here, but let's add the behavior of interactive snippets.

Sublime Text snippets can save you a ton of time and reduce syntax errors. The best part is they are easy to create and can be interactive, not just the old "copy and paste" snippets of years gone by. If you are not familiar with snippets, check out: http://docs.sublimetext.info/en/latest/extensibility/snippets.html

Snippets by themselves do not allow things like automatically inserting the current date or inserting Python code, so we need to draw upon our plugin to accomplish this.

To understand what we will be doing with our plugin, let's first create a simple snippet which will insert our name and date by typing in the information manually. Note, this snippet exists completely independent of our plugin.

To create a snippet, choose:

Tool | New snippet...

Then, copy and paste this:

<snippet>
    <content><![CDATA[
TODO ${1:yourname} - ${2:note} @ ${3:date}.
]]></content>
    <!-- Optional: Set a tabTrigger to define how to trigger the snippet -->
    <tabTrigger>tododate</tabTrigger>
    <!-- Optional: Set a scope to limit where the snippet will trigger -->
    <!-- <scope>source.python</scope> -->
</snippet>

Then, save it with: File | Save

With the file name: tododate.sublime-snippet

Once again, make sure it is saved in your User directory.

Now, create a new document to try it out, by typing in:

tododate then the tab key

Your cursor will rest on a highlighted yourname, try typing on it with your own name to over-write the default text. Then:

tab

This will take you to the note portion, where you can type some more. Now press:

tab will take you to the date portion, where you can type in today's date.

Now that you've witnessed the behavior, you should be able to figure out what the numbers, 1, 2 and 3 do in the `{...} brackets.

TODO ${1:yourname} - ${2:note} @ ${3:date}.

The text following the 1: is the default text (yourname).

TIP

But wait, there's more! You could just type the first part of the trigger for the snippet:

todo then press ctrl + space

and a list of available snippets will pop up. tab to select the one you want.

TIP

Sublime Text will display all available snippets from the menu.

Tools | Snippet

This list may vary on the type of document you are working on.

If this is all we wanted to do, snippets would do perfectly well, but to inject Python code (inserting today's date), we will need to combine the power of snippets with the capabilities of a plugin.

###Step 10 - Injecting snippets into a plugin

It might mess with your brain a bit, but the updated code below is a combination of our Python code with the proper format from the part of the snippet we require.

import sublime
import sublime_plugin
import datetime


class ToDoDateCommand(sublime_plugin.TextCommand):
    def run(self, edit):
        d = datetime.datetime.now().strftime("%d %b %Y (%a)")
        self.view.run_command("insert_snippet",
            {"contents": "TODO ${1:yourname} - ${2:note} @ " + d })

To explain:

        self.view.run_command("insert_snippet",
            {"contents": "TODO ${1:yourname} - ${2:note} @ " + d })

This line of code runs the Sublime Text API command insert_snippets. What follows in the {...} is the same snippet format we learned. The bit of the snippet ${3:date} was removed and replaced it with + d, which of course, inserted the variable d which contains our formatted date string (i.e. we no longer need to manually type in the date).

Once again, we wrapped the rather long line to stay within the 79 character limit.

NOTE

If we deleted the snippet file tododate.sublime-snippet, our plugin would still function. The snippet was only shown to learn the syntax we needed for our plugin.

In a future tutorial, I will show how you can go much further with Python executing in a Plugin.

###Step 11 - Accessing plugins from hotkeys (optional)

We can assign a hotkey to our plugin by modifying a file in the User directory, again staying away from the Default directory.

1- Default (OSX).sublime-keymap (or the correct file that comes up from Preferences User)

To assign a hotkey…

[
    { "keys": ["super+shift+7"], "command": "to_do_date" }
]

This will assign: Cmd + Shift + 7 (Mac OS X)

to tododate.py

same as: + + 7 (Mac OS X)

###Step 12 - Accessing plugins from Command Palette (optional)

  • Have you had trouble remembering hotkeys?
  • Do you sometime search endlessly through Sublime Text's menu items to find something?

If so, adding a plugin to the Command Palette is a good idea. You'll never have trouble finding something in the Command Palette due to it's powerful fuzzy searching capabilities. Fuzzy searching means that by typing just some of the letters of your search term, Sublime Text will display similar choices which don't have to match the search exactly. Usually, after just a few letters, Sublime Text selects what I'm looking for as the first choice.

The process is similar to adding to the menu, so I'll summarize

  1. Create a file if it doesn't already exist, in the User directory called Default.sublime-commands be sure it is in: /Packages/User/Default.sublime-commands
  2. Copy and paste this text into:
[
    {
        "caption": "ToDo Date insert",
        "command": "to_do_date"
    }
]
  1. Save the file

  2. Check to see if it shows up in Sublime Text's Command Palette as shown:

Command Palette

###Step 13 - BONUS! Combining with an existing plugin

Sublime​TODO is a plugin by Rob Cowie which extracts TODO-type comments from open files and project directories. For larger projects, it's one of my favorite plugins.

https://github.com/robcowie/SublimeTODO

You can see it in action here on Tuts +:

http://webdesign.tutsplus.com/tutorials/applications/quick-tip-streamline-your-todo-lists-in-sublime-text-2/

SublimeTODO

Since our tododate.py plugin writes the exact same style of comments, it works beautifully with the SublimeTODO.

I use my tododate.py plugin with this all the time. For example, my TODO and BUGS are scattered through dozens of file in dozens of directories in my MVC framework (http://web2py.com). It only takes me a second to create a page called TODO Results (as shown in the screenshot above), and I can click on the file description and jump immediately to that file and the line number where the TODO or BUG exists.

As with most plugins, they're meant to be customized. Working with just the basic knowledge from this tutorial, you'll be able to customize SublimeTODO.


###Go Write Some Plugins!

The Sublime plugin community is very active and the interest is overwhelming. Learning resources for writing plugins are a little sparse. You may find the best way to go further might be to study or modify existing plugins.

I have a public Trello of the Sublime Text's plugins I use:

https://trello.com/b/43EyVOuE/sublime-text-setup

With a bit of time, and a few lines of code, you've created a useful plugin. Now it's your turn - go write some plugins. The time you invest will certainly pay off. Have fun!




Submission for Publication

Aug 18, 8:22pm Submitted by Rob to: http://www.formstack.com/forms/envato-tuts__content_pitch


Note: My work has been published in PC Magazine 7 times, and various other technical journal. Please see my LinkedIn CV.


Good basic tutorials on writing plugins for Sublime Text are very hard to find, so I wrote this especially for Net Tuts + with hopes of it being published.

I'm very familiar with WordPress, so I could easily put it into your WP format.

Thanks for considering my tutorial. Rob McCormack (click on view profile for complete CV) http://ca.linkedin.com/in/mrmccormack


The Joy of Coding a Simple Sublime Text Plugin

In this tutorial, I'll show you how to create a really simple Sublime Text 2 plugin which inserts TODO style comments and today's date into your code. We'll cover the basics of Sublime Text's:

  1. Plugin programming language (Python)
  2. Snippets
  3. Settings files

Full DRAFT article at: https://gist.github.com/robmccormack/6275300

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment