What's new? | Help | Directory | Sign in
Google
                
Search
for
Updated Mar 20, 2008 by pluskid
Labels: Phase-Deploy
HowtoDefineSnippet  
Explains how to define a snippet.

snippets are defined through the function yas/define. Or you can put your snippets in files and use yas/load-directory to load them. There's also yas/compile-bundle to compile your customized snippets into a stand-alone bundle. A stand-alone bundle is convenient to load (just require it) and faster than parsing the directory hierarchy every time.

Functions

yas/define

The basic syntax of yas/define is:

(yas/define MODE KEY TEMPLATE &optional NAME)

The parameters are:

Here's an example:

(yas/define 'c-mode "if" "if (${condition})
{
    $0
}" "if (...) { ... }")

On what's the syntax of TEMPLATE, see later section.

You can have multiple snippets with the same KEY. For example:

(yas/define 'c-mode "if.one-stmt" "if (${condition})
    $0" "if (...) ...")

The tricky here is that when defining a snippet, the KEY is treated like a file name, and the extension is stripped. I.e. .one-stmt is stripped and if becomes the really key. If you define a snippet with exactly the same KEY as an earlier one, it will overwrite the old one. But if you add a different suffix (or extension ), there can be multiple snippets bind to the same KEY.

A popup menu will be shown to let you select which one to expand if there are multiple snippets bind to the KEY. The NAME of the snippets will be shown in the menu, so it is a good idea to keep the NAME descriptive.

yas/define-snippets

You can use yas/define-snippets to define a bunch of snippet for a mode at a time. The basic syntax is:

(yas/define-snippets MODE SNIPPETS &optional parent)

Here's an example:

(yas/define-snippets 'text-mode
  '(("email" "`user-mail-address`" "User's email address")
    ("time" "`(current-time-string)`" "Current Time")
    ("foo" "blablablabla")))

As you see from the example. SNIPPETS is a list of snippet definition. The syntax of each definition is identical to yas/define. The last optional parameter PARENT can be used to specify the parent mode. The search strategy for a snippet is: search in current mode snippet table, then recursively search the parent snippet table if any.

yas/load-directory

The basic syntax of yas/load-directory is:

(yas/load-directory DIRECTORY)

It's very simple to use:

(yas/load-directory "~/.emacs.d/snippets/")

You can also use M-x yas/load-directory to interactively select which directory to load. The directory should contains subdirectories whose names correspond to particular modes. And under those subdirectories are snippets definition for each mode. A typical directory hierarchy look like this:

snippets/
  +-- c-mode
  |     +-- if
  |     +-- for
  |     `-- main
  +-- java-mode
  |     +-- if
  |     `-- class
  `-- ruby-mode
        +-- each
        `-- select

The snippet file name acts as KEY of the snippet. The TEMPLATE and NAME are parsed from the file content. A typical snippet file looks like this:

#name : select { |...| ... }
# --
select { |${1:element}| $0 }

If there's a line of # -- (exactly), then all contents above (includes) that line are treated as comments. And all contents below that line are treated as the TEMPLATE. We can specify variables in comment section. Here's a list of supported variables (only one currently):

The syntax to specify the variable is:

#variable-name : variable-value

However, if there's no # -- line, the contents of the whole file is treated as the TEMPLATE.

Note the newline at the end of file counts as the TEMPLATE content. So please don't add a newline (and don't let your editor to automatically add one for you) at the end of the buffer if you don't want your snippet to insert a newline at the end.

From version 0.2.0, the directory hierarchy can also contains recursive subdirectories. E.g (only show directories):

snippets/
  +-- cc-mode/
  |     +-- c-mode/
  |     +-- c++-mode/
  |     +-- java-mode/
  `-- text-mode/

Here cc-mode will become the parent mode of c-mode, c++-mode and java-mode automatically.

yas/compile-bundle

The basic syntax for yas/compile-bundle is:

(yas/compile-bundle YASNIPPET YASNIPPET-BUNDLE SNIPPET-ROOTS)

The parameters are:

Here's an example:

(yas/compile-bundle "~/.emacs.d/plugins/yasnippet/yasnippet.el"
                    "~/.emacs.d/plugins/yasnippet-bundle.el"
                    '("~/.emacs.d/plugins/yasnippet/snippets"))

Note if you have only one root directory for snippets, you can simple write the last parameter like this:

(yas/compile-bundle "~/.emacs.d/plugins/yasnippet/yasnippet.el"
                    "~/.emacs.d/plugins/yasnippet-bundle.el"
                    "~/.emacs.d/plugins/yasnippet/snippets")

It will

  1. copy contents of yasnippet.el to yasnippet-bundle.el.
  2. add some initialize code. (only (yas/initialize) in fact)
  3. parse the snippets definitions in the directories and append proper yas/define statements to yasnippet-bundle.el to define those snippets.
The result yasnippet-bundle.el will be a stand-alone .el file. Put it in your load-path, and simpley (require 'yasnippet-bundle) in your ~/.emacs. Then the system is automatically initialized and snippets loaded.

Syntax

Here we explain the syntax for the snippet TEMPLATE.

Plain text

When a snippet is expanded. The key is replaced by its template. A template can contains arbitrary text. Except $ and `. They are special characters, needed to be escaped by adding a \ in front. Here's an example for perl-mode:

eval {
    ${1:# do something risky...}
};
if (\$@) {
    ${2:# handle failure...}
}

Note in the line if (\$@) {, the $ is escaped.

Sometimes it is also necessary to escape \ itself. For example, if you want \$, you need to use \\$ instead.

Embedded elisp code

Elisp code can be embedded inside the template. They are written inside back-quotes (`):

`some elisp arbitrary code`

They are evaluated when the snippet is being expanded. The evaluation is done in the same buffer as the snippet being expanded, so here's an example for c-mode:

#ifndef ${1:_`(upcase (file-name-nondirectory (file-name-sans-extension (buffer-file-name))))`_H_}
#define $1

$0

#endif /* $1 */

It get the current buffer-file-name, strip the directory part and extension part, and upcase it to produce the guard macro definition for a header file.

Tab Stops

Tab stops are fields that you can navigate back and forth by TAB and S-TAB. They are written by $ followed a number:

$N

$0 have special meaning of the exit point of a snippet. That is the last place to go when you've traveled all the fields. Here's a typical example:

<div$1>
    $0
</div>

When you expand a snippet, the point is firstly placed at $1. You can optionally insert some text and press TAB to go to $0.

Placeholders

Tab stops can have default values -- a.k.a placeholders. The syntax is like this:

${N:default value}

They acts as the default value for a tab stop. But when you firstly type at a tab stop, the default value will be replaced by your typing.

Mirrors

We refer the tab stops with placeholders as a field. A field can have mirrors. Its mirrors will get updated when you change the text of a field. Here's an example:

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

When you type "document" at ${1:enumerate}, the word "document" will also be inserted at \end{$1}. The best explanation is to see the screencast(YouTube or avi video).

The tab stops with the same number to the field act as its mirrors. If none of the tab stops has an initial value, the first one is selected as the field and others mirrors.

There can also be un-numbered fields. like this:

if (${condition})
{
    $0
}

They'll never have any mirrors, since no tab stops will have the same number to it. Two un-numbered fields act as they have a different number.

Transformations

If the default value of a field starts with $, then it is interpreted as the transformation code instead of default value. A transformation is some arbitrary elisp code that will get evaluated in an environment when the variable text is bind to the inputted text of the field. Here's an example for Objective-C:

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

- (void)set${2:$(capitalize text)}:($1)aValue
{
    [$2 autorelease];
    $2 = [aValue retain];
}
$0

Look at ${2:$(capitalize text)}, it is a transformation instead of a placeholder. The actual placeholder is at the first line: ${2:foo}. When you type text in ${2:foo}, the transformation will be evaluated and the result will be placed there as the transformated text. So in this example, if you type baz in the field, the transformed text will be Baz. This example is also available in the screencast.

Another example is for rst-mode. In reStructuredText, the document title can be some text surrounded by "===" below and above. The "===" should be at least as long as the text. So

=====
Title
=====

is a valid title but

===
Title
===

is not. Here's an snippet for rst title:

${1:$(make-string (string-width text) ?\=)}
${1:Title}
${1:$(make-string (string-width text) ?\=)}

$0

The "=" will be adjusted automatically according to the title string you typed.


Comment by hugows, Mar 14, 2008

Hi! This is great! Do you know why I can't use groups with string-match? Try this snippet:

Primary field: ${1:foo}
Mirror field : $1
Upcase field : ${1:$(upcase text)}
First number : ${1:$(if (string-match "[0-9]*" text) (match-string 0 text) "no match")}
Non-working  : ${1:$(if (string-match "\\([0-9]*\\)" text) (match-string 1 text) "no match")}

Eval and try to type 123abc.. the example on the bottom does not work.. Lets fix it! I'm pumped!

Comment by hugows, Mar 14, 2008

I have several suggestions to you, and if you need I could also code some elisp (would need some pointers to get started, of course) Could we talk on email/irc during weekend, or should I use the google groups?

Comment by pluskid, Mar 17, 2008

@hugows: You can post to the discussion group. Or you can directly email to me. :) But I haven't gotten used to IRC yet.

Comment by pluskid, Mar 17, 2008

@hugows: Hi! I'm sorry I didn't see this comment until now. I'm not notified by commenting on wiki pages. I've setup the notifier now. So I'll be notified by all upcoming comments.

As to the problem, I think it works fine (at least for 0.3.1). Except \ need to be escaped, try:

Primary field: ${1:foo} 
Mirror field : $1 
Upcase field : ${1:$(upcase text)} 
First number : ${1:$(if (string-match "[0-9]" text) (match-string 0 text) "no match")} 
Now-working : ${1:$(if (string-match "\\\\([0-9]\\\\)" text) (match-string 1 text) "no match")}

Works fine for me! :)


Sign in to add a comment