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:
- Plugin programming language (Python)
- Snippets
- 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:
- With a basic knowledge you can easily customize existing plugins to your needs
- For small specific tasks, like the one you're about to learn, a larger plugin can be "too much" and get in your way
- You may find, with just a few lines of code, you will create a plugin that you'll use every day
- It's fun
- 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.
Your plugin will insert comments like this:
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 seecmd
(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.
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
}
orend 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:
- Click on bottom right of the Sublime Text window - on the text
Tab Size: 4
- Select
Convert Indentation to Spaces
- Note: I don't have
Indent Using Spaces
checked off, as Sublime just does that for me.
TIP
If you click on
Python
, to the right ofTab Size: 4
, you can change the language syntax.
Note
This tutorial will follow the Style Guide for Python Code:
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.
-
Rename the class to
ToDoDate
i.e. :class ToDoDateCommand(sublime_plugin.TextCommand):
-
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 toexample
sinceExample
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 yourUser
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 nameMain.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 yourPreferences
menu (On Windows/Linux preferences is located as a top level menu).
Your Edit menu should look something like mine:
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 hotkeycmd + /
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 pressctrl
+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
- Create a file if it doesn't already exist, in the
User
directory calledDefault.sublime-commands
be sure it is in:/Packages/User/Default.sublime-commands
- Copy and paste this text into:
[
{
"caption": "ToDo Date insert",
"command": "to_do_date"
}
]
-
Save the file
-
Check to see if it shows up in Sublime Text's Command Palette as shown:
###Step 13 - BONUS! Combining with an existing plugin
SublimeTODO 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 +:
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!
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
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:
- Plugin programming language (Python)
- Snippets
- Settings files
Full DRAFT article at: https://gist.github.com/robmccormack/6275300