← back to all talks and articles

Using YASnippet in Emacs

I’ve long been a proponent of using editor snippets. They help you write code faster, avoid mistakes, and they’re just plain fun to use. Snippets can take a little effort to get set up and get used to — but its positive effects may surprise you.

In Emacs, the most popular package for snippets is YASnippet. It offers a lot of functionality, has good documentation and is mostly compatible with the TextMate snippets of old.

Installing YASnippet

Using YASnippet in Emacs is not too hard. With use-package, you can load YASnippet like so in your ~/.emacs.d/init.el:

(use-package yasnippet
  :ensure t
  :hook ((text-mode
          prog-mode
          conf-mode
          snippet-mode) . yas-minor-mode-on)
  :init
  (setq yas-snippet-dir "~/.emacs.d/snippets"))

This will ensure YASnippet’s minor mode is lazy-loaded in text and programming-related major modes, with snippets stored in your Emacs configuration directory.

Using YASnippet

In the simplest case, YASnippet allows us to expand a shortcut phrase into a longer bit of text by calling the function yas-expand, which is usually bound to . This saves on typing and mistakes. For example, we might define snippets that expand mod to module or qsa to querySelectorAll. But to accomplish that, you hardly need a snippets system. Using YASnippet gives us a few extra features:

  • Snippets are defined per major mode, so that your javascript snippets will not pollute your Ruby files.
  • Snippets can use placeholders for dynamic values.
  • Snippets can contain arbitrary Elisp code for advanced customisation.

Snippet placeholders

Snippet placeholders can be used to quickly edit a few special texts in an inserted piece of code. For example, we might define a snippet for a Ruby class like so:

# key: class
# --
class $1
end

We’ve set it the key property to class, so that typing class will expand the snippet.

The $1 is a placeholder, so that expanding the snippet will put our cursor at this position, so we can immediately type a class name. Inserting $2, $3 will add further stops in our snippet that we can step through with subsequent  presses.

Placeholders can do some nifty things. First, they can have default values:

# key: class
# --
class ${1:MyClass}
end

That’s a great way to offer a hint about what to enter, or to use a sensible default value that can be quickly stepped over.

Second, they can also be nested. Combined with default values, that can be quite useful. For example, we might add an optional second placeholder to our class snippet for a parent class:

# key: class
# --
class ${1:MyClass}${2: < ${3:ParentClass}}
end

In this case, the second placeholder contains the entire parent class syntax. Highlighting that field allows you to easily delete it (along with the third field), if you don’t want it — or you can skip to the next field, $3, and customise the name of the parent class.

Third, placeholders can mirror other placeholders. Only the first placeholder will be edited; subsequent references to the same placeholder will simply repeat the value of the first. For example, we might define a snippet for a private attribute reader method like so:

# key: r
# --
attr_reader :${1:my_attribute}
private :$1

When first expanding the snippet, both instances of $1 will be replaced with my_attribute. Editing the first instance will also update the second.

Embedding Elisp code

Furthermore, we can apply transformations to mirrored placeholders using Elisp:

${1:MyClass} is defined in lib/${1:$(convert-to-snake-case yas-text)}.rb

Expanding such as snippet would result in the following text:

MyClass is defined in lib/my_class.rb

The $(..) syntax in the placeholder marks it as Elisp code to be run, with the yas-text variable containing the contents of the $1 field. Actually implementing the convert-to-snake-case function is left as an exercise to the reader.

Finally, snippets can evaluate and insert arbitrary code — even outside of placeholders. For example, we could include the following in our snippet:

Timestamp: `(current-time-string)`

Code between back-ticks is evaluated as Elisp, so you can output just about anything to your file.

An advanced snippet

Let’s review an advanced snippet for writing a Ruby class definition:

# key: class
# --
${3:# ${4:Class description}}
class ${1:`(let ((fn (capitalize (file-name-nondirectory
                                 (file-name-sans-extension
                                 (or (buffer-file-name)
                                     (buffer-name (current-buffer))))))))
           (replace-regexp-in-string "_" "" fn t t))`}
${2:$(if (string= "" yas-text)
        ""
        (mapconcat
          (lambda (s)
            (setq name
              (if (string-match "\\\\([a-z-A-Z0-9_]+\\\\)" s)
                (match-string 1 s)))
            (format "  attr_reader :%s\n  private :%s\n" name name))
          (split-string yas-text ",")
          "\n")
)}
  def initialize(${2:argument})
${2:$(if (string= "" yas-text)
        ""
        (mapconcat
          (lambda (s)
            (setq name
              (if (string-match "\\\\([a-zA-Z0-9_]+\\\\)" s)
                (match-string 1 s)))
            (format "    @%s = %s" name name))
          (split-string yas-text ",")
          "\n")
)}
    $0
  end
end

This snippet outputs a Ruby class with a placeholder for the class name and the initialiser arguments. The initialiser arguments are mirrored to instance variable assignments and attribute reader methods. This is the relevant Elisp code that turns the argument list into instance variable assignments:

(if (string= "" yas-text)
        ""
        (mapconcat
          (lambda (s)
            (setq name
              (if (string-match "\\\\([a-zA-Z0-9_]+\\\\)" s)
                (match-string 1 s)))
            (format "    @%s = %s" name name))
          (split-string yas-text ",")
          "\n")
)

The first placeholder has a default value that comes from some Elisp code:

(let ((fn (capitalize (file-name-nondirectory
                                 (file-name-sans-extension
                                 (or (buffer-file-name)
                                     (buffer-name (current-buffer))))))))
           (replace-regexp-in-string "_" "" fn t t))

This tries to be clever about the default name for the class we are writing: as per the Ruby conventions, it will convert a snake case filename into a camel case class name.

For example, if we expand this snippet in a file article.rb and enter title, published: true for the $2 placeholder, we end up with the following output:

# Class description
class Article
  attr_reader :title
  private :title

  attr_reader :published
  private :published

  def initialize(title, published: true)
    @title = title
    @published = published
  end
end

The case for using snippets

As I have argued before, the benefits of snippets go beyond the superficial reduction of the number of keystrokes needed to write something. As software developers, we sometimes get tempted to see “a lot of typing” as a problem — one to be solved by libraries and meta-programming. Snippets can help us overcome that, moving the complexity from the run time to write time. Boilerplate code helps keep our code simple while snippets help make it easy. For more about this, see Use your editor to write simpler code.

  • Emacs
  • Editor
Arjan van der Gaag

Arjan van der Gaag

A thirtysomething software developer, historian and all-round geek. This is his blog about Ruby, Rails, Javascript, Git, CSS, software and the web. Back to all talks and articles?

Discuss

You cannot leave comments on my site, but you can always tweet questions or comments at me: @avdgaag.