The power of snippets

Version 1.1b17 is falling a little behind schedule, so just to signal that I'm still alive, here's a teaser about how the snippet system has been enhanced in the upcoming beta.

2005-10-11: Added paragraph about regexp replacements performed on variables.

A snippet is a piece of text that you'd like to insert in your document, it can however include code to run at insertion time, variables (like selected text), tab stops/placeholders for missing information, and make simple transformations on the data you enter in the placeholders.

There are currently 3 ways to insert a snippet. Either select it from the Automation → Insert Snippet sub menu, press its assigned key equivalent, or type the tab trigger associated with the snippet, followed by tab.

Key equivalents and tab triggers can be restricted to a scope, and multiple snippets can share the same activation method, which results in a menu (unless the scope settles the tie). These aspects are the same for all bundle items and will not be discussed in this article.

To create a snippet, select Automation → Insert Snippet → Edit Snippets…, which will open the bundle editor, and here you can use the plus button in the lower left corner to add a new snippet to the selected bundle. It is recommended that you create a new bundle for your own customizations, e.g. called “Custom”.

Plain text

In the simplest case, you can use snippets to insert text that you don't want to type again and again, either because you type it a lot, or because the actual text to insert is hard to remember.

For example I have snippets for the miscellaneous Apple modifier keys, so rather than go lookup the value of the cloverleaf glyph (⌘), I type command and press tab (since that's my tab trigger) and it expands to ⌘. I have set the scope for this snippet to text.html, so that it only expands in HTML documents (which includes Markdown).

If you use snippets to insert plain text, there's only one thing you should be aware of: $ and ` are reserved characters. So if you want to insert one of these, prefix it with an escape (i.e. \$). An escape not followed by one of these two characters will be inserted as a literal escape character.

Variables

You can insert the value of a variable by prefixing the name of the variable with $. Variables are environment variables which are inherited from the program which launched TextMate. Additional variables can be set in Preferences → Advanced, and TextMate will also provide some variables for the current filename, selected text a.s.o., the full list is available in the Help Book. The most useful variable is probably TM_SELECTED_TEXT. So if for example we want to create a snippet which wraps the selection in a LaTeX \textbf command, we could do:

\textbf{$TM_SELECTED_TEXT}

If no text is selected, the variable will not be set, and nothing will be inserted. Though we can provide a default value by using this syntax: ${«variable»:«default value»}. So the above would be:

\textbf{${TM_SELECTED_TEXT:no text was selected}}

The default value can itself contain variables or shell code. If you want the default text to contain a }, you need to escape it. But all other characters are verbatim.

A variable also supports regular expression replacements using this syntax: ${«variable»/«regexp»/«format»/«options»}. If the variable is not set, the replacement will be performed on the empty string. For example, to prepend a bullet to each non-empty line in the selection (and insert that) we can do:

${TM_SELECTED_TEXT/^.+$/• $0/g}

Shell code

You can use backticks to have shell code executed when the snippet is inserted. The result of running the code gets inserted into the snippet, though with the last newline in the result removed (if present). So for example to create a snippet that wraps the selection in an HTML link, where the URL of that link comes from the clipboard, we can do:

<a href="`pbpaste`">$TM_SELECTED_TEXT</a>

Since this is normal bash code, we can write a small program. E.g. we can let it verify that the clipboard contains only a single line of text like this:

<a href="`
    if [[ $(pbpaste|wc -l) -eq 0 ]]
        then pbpaste
        else echo http://site.com/
    fi
`">$TM_SELECTED_TEXT</a>

Inside shell code, the only character you need to escape is the backtick.

Tab stops

After insertion, the caret will be placed after the last character of the snippet. This is not always desirable, and we can change that by using $0 to mark where we want the caret to be. So if for example we make an HTML div-snippet, and want the caret to end between the opening and closing tags, we could make a snippet like this:

<div>
    $0
</div>

Often though, we want to fill in text multiple places in the snippet, and we can provide multiple tab stops by inserting $1-$n. The caret will start at $1, then when pressing tab, it will move to $2, and $3 on next tab etc. until there are no more tab stops. If you do not explicitly set $0, it will be at the end of the snippet.

So we could e.g. change the above to:

<div$1>
    $0
</div>

This allows us to fill in an argument, and then tab on to $0.

Placeholders

Like variables, tab stops can also have default values (and are generally referred to as placeholders, when they do). The syntax is the same: ${«tab stop»:«default value»}. And the default value can contain both text, shell code, and other placeholders. So we can refine the above further by letting it be:

<div${1: id="${2:some_id}"}>
    $0
</div>

Inserting this snippet will insert a div tag with the id argument selected, and we can then decide either to overtype the argument (e.g. delete it), and press tab again to reach $0, or we can press tab immediately to get to the second tab stop (the value part of the argument), and edit that.

When you edit the placeholder text, any embedded tab stops will be removed.

Mirrors

There are times when you need to provide the same value several places in the inserted text, and in these situations you can re-use the tab stop to signal that you want it mirrored at that location. So for example to create a LaTeX environment with a snippet, we could do:

\begin{${1:enumerate}}
    $0
\end{$1}

After inserting this snippet, enumerate will be selected, and if we edit it, the changes will be reflected in the \end part as well.

Transformations

There are situations where we do want our placeholder text mirrored, but it needs slight changes. For example in objective-c the setter/getter methods of the foo instance variable looks something like this:

- (id)foo
{
    return foo;
}

- (void)setFoo:(id)aValue
{
    [foo autorelease];
    foo = [aValue retain];
}

The problem here is that in one instance we title case foo. We can solve this by doing a regular expression substitution on a tab stop (when mirroring it). The syntax for that is: ${«tab stop»/«regexp»/«format»/«options»}. In the format string we can use \U to switch to uppercase (until \E), or \u to make next character uppercase. So here's how we can make a snippet for an objective-c getter/setter method:

- (${1:id})${2:foo}
{
    return $2;
}

- (void)set${2/./\u$0/}:($1)aValue
{
    [$2 autorelease];
    $2 = [aValue retain];
}

Here $0 in the format string refers to the entire match, and has nothing to do with the tab stops.

Another feature of format strings are that they can do conditional insertions, based on a capture. The syntax for that is: (?«n»:«did match»:«didn't match»). If capture n did match, the left hand side is inserted, otherwise the (optional) right hand side is inserted.

We may want to use that, if we switch to a more verbose getter:

- (void)set${2/./\u$0/}:($1)aValue
{
   $1 oldValue = $2;
   $2 = [aValue retain];
   [oldValue release];
}

The problem here is when we provide a pointer type, for example if $1 is NSString *, the getter becomes:

- (void)setFoo:(NSString *)aValue
{
   NSString * oldValue = foo;
   foo = [aValue retain];
   [oldValue release];
}

To avoid that extra space, we can check if the type ends with *, and only if it doesn't, insert a space in front of oldValue, here's the result:

- (void)set${2/./\u$0/}:($1)aValue
{
   ${1/( \*)?$/(?1:$1: )/}oldValue = $2;
   $2 = [aValue retain];
   [oldValue release];
}

Back to HTML

A useful HTML snippet is “Wrap selection in tag”, it can look like this:

<${1:p}>$TM_SELECTED_TEXT</$1>

Selecting some text and then inserting this snippet, will wrap the selected text in a <p> tag, with the p selected, allowing us to specify another tag name (and press tab when done, to move past the entire thing).

However, if we also want to provide an attribute for the tag, that attribute will be mirrored in the end tag. Again we can turn to regexp substitution on the mirror, and change the snippet to:

<${1:p}>$TM_SELECTED_TEXT</${1/([^ ]+).*/$1/}>

What this does is, put everything up till the first space in capture register one, match the rest of the string, and substitute it all with capture register one. Effectively chopping off the attribute part of the start tag.

And back to C again

In longer functions/methods/sources I tend to insert comments of the following form:

/* ================================= */
/* = Clipboard history data source = */
/* ================================= */

So far I've been using a macro which turns the current line into such a comment box, but now that snippets can do transformations, I have created this neat snippet:

/* ==${1/./=/g}== */
/* = ${1:${TM_SELECTED_TEXT:Section name}} = */
/* ==${1/./=/g}== */

It inserts a comment box like the above, using the selection as the title (or “Section name” if there is no selection). After insertion, the title is selected, and if overwritten, the top and bottom lines will adapt in length to the new text.

30 Comments

  1. 25 Aug 2005 | # Thomas Balthazar wrote…

    Hi,

    I just wondered if it was possible to use such a snippet : ${1:p}>$TM_SELECTED_TEXT</$1> with a tab trigger.

    Pressing tab replace the selected text by a tab, so, is there a tricky way to do that?

    Thanks, Thomas.

  2. 25 Aug 2005 | # Allan Odgaard wrote…

    That snippet is not very useful when activated using a tab trigger, since that rules out a selection.

    But a snippet can also be activated using a key equivalent. There's already a “Wrap selection in tag” included with 1.1b16 on ⌃⇧W.

  3. 26 Aug 2005 | # John wrote…

    Please give use native FTP support (like B*2edit).

    oh and faster sytanx highlighting !

    Thanks,

    John

  4. 26 Aug 2005 | # Stefan wrote…

    These snippets are great :-)

    I don't see any point for native (S)FTP support in TM, but would like to suggest a few other things:

    1. Documentation. Right now I'm trying to figure out how to write language bundles and how to assign styles to syntax elements. All I found on that topic is either outdated or does not explain it in details. So… what TM really needs is documentation.

    2. Web preview is also a great feature, but why does this window disappear when I switch to a different document? For example: working on a html file, switching to the corresponding css file boom preview is gone. Thats annoying. Could you include two options to either make the preview sticky (shows always the same file) or follow the actual document.

    3. The handling of "non-editable" documents is imho counter-intuitive. When you open a folder and select files, they open. But when you select a file TM doesn't recognice as text, nothing happens. No beep, no warning, no sign of what is going on at all. Wouldn't it be a convenient thing to show a preview (like the preview application or finder) of files that can not be edited? A single special preview tab would be sufficient.

  5. 26 Aug 2005 | # Mark wrote…

    SFTP in an editor is one of those things were if you have ever used it, you can fall in love with it. Otherwise you may just not get the point of it.

    I for one love the functionality of it. You can sync up a lot of code on your local machine and on the server. Make modifications to multiple files. Test on local machine. Then upload it all to the server using just one program. It is very handy.

    In fact, TM did have native SFTP support on its target list for 1.1. This was on the home page but has now been removed. Another target was to support CVS, but this is not on the home page as well any more.

    This is somewhat distressing for me because I bought TM because I need a replacement for Dreamweaver. TM would offer everything I used in Dreamweaver if TM also supported SFTP (CVS support was a good bonus).

    So, are native SFTP and CVS out now? :(

  6. 27 Aug 2005 | # Allan Odgaard wrote…

    John: Not sure why you'd ask for faster syntax highlight. It's even multi-threaded so to not stall in the case of complex grammars applied to humongous files. Though even in that case, it takes only a couple of seconds to parse 43,649 lines of C++ (a good megabyte) on my machine — and that's with a declarative grammar system unmatched in flexibility by any text editor I know of.

    Stefan: as for documentation, the Help Book should be up-to-date on writing language grammars, except it says Theme Editor instead of Fonts & Colors (located in Preferences). For more background you may want to check out introduction to scopes and language grammars here on this blog.

    But yes, documentation is falling behind, and it's something I do plan to work a lot on in the nearest future — that's part of the reason to why I am writing these essay style blog posts (practice, and a source for cut'n'paste).

    But keep in mind that outdated documentation is the price you pay for rapid progress :)

    Your other points are valid. But you're probably aware that TM is still very young, so lack of polish in one area is only because resources are being spent on making other areas better.

    Mark: I removed mention of (s)ftp and CVS for mainly two reasons:

    1. 1. I had to spend a lot of time replying to inquires about progress, ETA, and so on.

    2. 2. It is too much of a restraint to have to commit to certain features in certain versions.

    Especially the latter point is keen to this — 1.1 has been in beta for far too long, and really needs to be released as final (even if it lacks features which were initially on the public roadmap).

    That said, (s)ftp is planned for 1.2, and I of course apologize to anyone who have felt mislead by the initial public roadmap. You can be sure that I have learned the lesson and won't make any solid promises for the future, while still trying to be completely open as to what my plans are.

  7. 29 Aug 2005 | # Dennis Roberts wrote…

    Love the editor and am a registered user. My only comment is make sure all those examples above make it into the documentation I don't want to have to remember this page when the new version comes out.

    Thanks for an excellent product.

  8. 29 Aug 2005 | # Nate Beaty wrote…

    Thanks for the examples on Snippet use. That really helps to get an understanding of what TM is capable of and how to adapt it to one's needs.

    I hate to sound off with what seems to be the more annoying group on the Future Features cheerleading squad, but if TM had native S/FTP support, I'd drop $50 on it in a heartbeat — and sound off a hearty sayonara to my least-favorite long-time cohort, Dreamweaver (which just crashed on me, losing an hour of work). I've tried the Remote Edit via Transmit or Cyberduck, but I want to simultaneously save to my local copy when I'm editing something, and the lack of integration with the Project mode of TM makes it worthless.

    The Local/Remote switch in the Project view with S/FTP support works fantastic for a lot of Webdesigners' workflows (those who don't code with a group, and who aren't looking for version control). Open file, edit, Cmd-Shift-U to upload/save locally. If you need to do any remote maintenance, you can switch to Remote View and rename/delete/change permissions, etc. Greatly simplifies what I do most in my day.

    That said, I love the feel and power of Textmate and am eagerly awaiting what so many of you say is unnecessary for TM.

  9. 31 Aug 2005 | # Anonymous wrote…

    Not that I've tried it, but couldn't you create a command that does an (s)ftp or an scp and assign it to Cmd-Shift-U?

  10. 01 Sep 2005 | # Andre Thenot wrote…

    If you can ssh/scp to your remote host, set up automatic login and use rsync:

    rsync -az SOURCEDIR HOST:DESTDIR

    For those not familiar with this unix tool (included in OS X), it synchronizes two directories on different hosts, and only sends the files that have changed. Furthermore, it compresses the transfer so you save on bandwidth.

    Type man rsync in Terminal for more details or see http://www.jdmz.net/ssh/ (instructions are for Redhat linux but it's exactly the same on OS X).

    I think TextMate is fine as it is (after all, it's a text editor), no need to bloat it with network stuff when other tools do it very well.

    A.

  11. 02 Sep 2005 | # Mark wrote…

    Thanks Allan for the response. I completely understand your motives for removing the items from the home page. I just thought it meant that they were gone for good. Great to see that is not the case. Thanks! and Thanks for TM :)

  12. 02 Sep 2005 | # Nate Beaty wrote…

    Andre: I have used rsync, usually when I need to do a larger sync such as backing up my server to my Powerbook's firewire drive.

    But when I make lots of little consecutive changes on a file and want to save to local/upload to remote on each change, rsync seems like overkill.

    re: "create a command that does an (s)ftp or an scp and assign it to Cmd-Shift-U" — I'd love to have this set up, but it seems like the command would have to map the site/project I'm editing to a remote host, and then execute the command. My brain is too tired & isn't offering solutions on how to do that.. perhaps someone more knowledgeable with shell and TM could school me? (This seems like it would be simple to do..)

    I've been trying to figure out a way to do this very thing for a while because I love TextMate, and if I could make it work in my workflow, I'm so ready to drop the evil bloated Dreamweaver.

  13. 03 Sep 2005 | # Percy wrote…

    Anyone who complains about TM's syntax highlighting in any way is clearly out to lunch. In all the years of my web programming, and of all the editors I have tried (believe me, I've tried a lot of them), not a single one could come anywhere close to competing with some of the more basic features of TM's syntax highlighting.

    Anyway, thanks for this post, I learnt a few new things about snippets. I didn't know I could nest variables, I remember thinking how neat that would be if I could… :)

    I must say though, there's one thing I remember from the brief time I used XCode for writing PHP code… I remember discovering at some point that using Ctrl + left/right arrows behaved differently that Option + left/right. I soon discovered that the Option key basically broke the "words" into even smaller "blocks", so instead of skipping over an entire function named something like str_repeat() or whatever, it would actually move between underscores, and it would move to the beginning of a case change in a function name (for example: myFunctionNameIsLong, Option+arrow would stop at F, N, I and L and vice versa. I thought that was a huge huge feature that most people don't think to use — But I remember that after I found out about it, that I was using it quite often. Too many times I find that Option+left/right goes farther than I want, like a lot of my CONSTANT_NAMES_LOOK_LIKE_THIS and I have to resort to my mouse or holding down the arrow keys for a while to get somewhere in the middle of it.

    Anyway thanks for a great product. Love it! Best editor I've ever used.

  14. 03 Sep 2005 | # Martin wrote…

    Hey, if anybody of you really miss SFTP support, get Cyberduck it lets you edit any remote file in a local editor and uploads it if you save.

    Not the real thing, but very handy. :) Martin

  15. 03 Sep 2005 | # Allan Odgaard wrote…

    Percy: TextMate can treat underscores as word separators. By default this is bound to ctrl-option left/right — also works with delete/backspace and shift for selecting.

    But the current key choice isn't very ergonomic, and it doesn't work with camelCase, only_underscores.

  16. 04 Sep 2005 | # Will Mitchell wrote…

    I want to echo Thomas Balthazar's request for a way to combine tab triggers with the TM_SELECTED_TEXT variable. I think it's great that Textmate provides multiple ways to access snippets, including menus and key-combos. But I've found tab triggers to be the easiest to use by far, because they require no navigation and are often much easier to remember than key combos.

    How about providing an alternate way to enter the trigger text, outside the flow of the text window? For example, a user could select some text, hit a key-combo to pull up a tiny window to enter the trigger, type trigger, and hit tab to insert it. The window could be about the size of tooltip. This would fit naturally with the way tab triggers are usually entered, and would only require the user to learn one additional key combo.

    What do you think?

    Thanks! Will Mitchell

  17. 04 Sep 2005 | # Allan Odgaard wrote…

    Will: there have been some suggestions/considerations about allowing commands, macros, and snippets to be executed using a system similar to the file chooser on ⌘T — this would allow snippets to be inserted via the keyboard, but without having to remember the key equivalent for the snippet, yet preserve any potential selection.

  18. 05 Sep 2005 | # Will Mitchell wrote…

    Allan,

    That sounds ideal to me. The file chooser interface is a good interface. Keep up the good work!

    Will

  19. 14 Sep 2005 | # Samuel Agesilas wrote…

    Allan,

    This is HOT!!! I love the new snippets!!! Excellent work!!! This has to be the best editor on OS X. Keep it coming man! Keep it coming!!!

    -sam

  20. 14 Sep 2005 | # Samuel Agesilas wrote…

    Allan,

    Btw… Thanks for supporting Actionscript!!!!

    -sam

  21. 08 Oct 2005 | # Robert Hicks wrote…

    CVS support would be nice…but I think FTP/SFTP should be left to a separate utility. Focus on the editing…!

  22. 11 Oct 2005 | # Andreas Wahlin wrote…

    I second that command+T snippet insert thingie, much power goes to waste because of powerfully wasted memory :)

  23. 24 Oct 2005 | # Shane Celis wrote…

    I just registered TextMate today, and I feel very good about that. In the last few weeks it's grown on me; I've been able to extend it in the direction I need it to go, and snippets are awesome! Every time I enter my magic keyword and hit tab I feel a renewed sense of awe for TextMate. This thing actually works, and it's comprehensible, and I can extend it to do what I want so easily that I've grown into it. That is cool, and I wanted to share that with you guys.

    Keep up the good work.

    -Shane

  24. 25 Oct 2005 | # Nick wrote…

    I love snippets.

    This seems completely obvious once you know it, but it's worth nothing that if you'd like a return after your snippet text — or if you're cleaning up messy HTML and want some returns — just format the snippet as you'd like it. No need to futz with \n escape codes or anything; put the formatting returns right in the snippet and voila, it works.

  25. 07 Feb 2006 | # Andi wrote…

    hi all! I wonder, everybody is talking about those snippets but I cant get it working – how is the procedure of inserting a snipped? I wonder that I dont find any information about that – what is the key combination that I must press. I saw something called "Next Completion" with a strange sign behind it – whats that? Thanks for any tipp or hint!

    • andi
  26. 07 Feb 2006 | # Allan Odgaard wrote…

    Andi: From this article: There are currently 3 ways to insert a snippet. Either select it from the Automation → Insert Snippet sub menu, press its assigned key equivalent, or type the tab trigger associated with the snippet, followed by tab.

    For more info, see the tab triggers section of the manual.

  27. 07 Feb 2006 | # Andi wrote…

    ok! I am working with rails and when I f.e. enter 'logi' and press TAB nothing happen, only a normal tab! I am in a rb source file. Is a TAB-Trigger something different than a TAB? Also what is this "^" sign is standing for a key. On my keyboard it is below the ESC key but it also dont work. For params[] I need to press ^P but this also not work.

    thx -andi

  28. 07 Feb 2006 | # Allan Odgaard wrote…

    The logi tab trigger has a scope selector of source.ruby.rails. This means that you need to change the language (shown in the status bar) to Ruby on Rails for it to work.

    The ⌃ glyph represents the control modifier key (as shown in the terminology section of the manual).

  29. 07 Feb 2006 | # Allan Odgaard wrote…

    i.e. by defualt, Rails snippets do not work for Ruby sources.

    For that, one should instead select that these files should be Ruby on Rails sources (which is done simply by selecting Ruby on Rails from the language pop-up in the status bar, while a *.rb source is open).

  30. 07 Feb 2006 | # Andi wrote…

    ahhh… GREAT – thats it … thx a lot!

    TextMate is a very cool editor – I love it so far – keep up the good work! Must buy Textmate now – my eval. copy is off in 15 days :)

    -andi

Comments closed, you can use the mailing list for discussion.