Matthew DiLoreto

A place to keep track of some of my things.

Making the Yasnippet *​new-snippet​* Buffer More Helpful

Yasnippets are a powerful text templating system for emacs, with a convenient interface based on snippets.

The templating language itself is… fine, but it can be difficult to remember exactly what the syntax is, and yasnippet doesn’t help you out that much when you run yas-new-snippet:

,# -*- mode: snippet -*-
,# name:
,# key:
,# --

That’s all you’re presented with. You have to remember the difference between name and key (key is the abbreviation you type before invoking yas-expand, name is just a human-readable description of the snippet), and then you type the snippet to be inserted below.

I’ve used yasnippets for a couple years now, and I realize I’m never going to remember the entire templating syntax here. I mean, it isn’t anything crazy, but there are plenty of idiosyncrasies to make each read-through of the documentation feel like the first time (a bad sign for any kind of syntax you want to be familiar to users).

So I came up with a simple solution, just have the initial buffer created by yas-new-snippet be more useful:

# key: what the user types to expand this snippet
# name: what the user sees the snippet listed as

# For more information visit
# http://joaotavora.github.io/yasnippet/snippet-development.html#org9801aa7

## Embed elisp - back quotes:
# ifndef ${1:_[backquote](upcase (file-name-nondirectory (file-name-sans-extension (buffer-file-name))))[backquote]_H_}

# NB: DON'T MODIFY THE BUFFER INSIDE BACKQUOTES!


## Tab stops - $N
# $0 is the last, $1 is the first, $2 is second, etc.


## Placeholders - ${N:default}
# ${N:default value}


## Mirrors - just reuse a tabstop with placeholder
# \begin{${1:enumerate}}
#     $0
# \end{$1}


## Transforming Mirrors - $(
# ${2:foo}
# ${2:$(capitalize yas-text)}


## Transforming fields - ${N:name:$(elisp)} or ${N:$$(elisp)}
# #define "${1:$$(upcase yas-text)}"


## Choose a value from a list of options
# <div align="${2:$$(yas-choose-value '("right" "center" "left"))}">


## Useful things bound inside evaluated elisp
# | variable         | description                                             |
# |------------------+---------------------------------------------------------|
# | yas-text         | the current text of this field                          |
# | yas-field-value  | the current text of $1 (or $2, etc.)                    |
# | yas-modified-p   | whether the field is modified                           |
# | yas-choose-value | user chooses from a list of options                     |
# | yas-verify-value | Verify that the current field value is in POSSIBILITIES |
# --

$0
${1:default value}
${1:$(capitalize yas-text)}
${2:$$(yas-choose-value '("right" "center" "left"))}

I saved this new prompt in my emacs directory in a file called yasnippet-prompt.txt. This is essentially just a pared-down version of the official docs, and includes some useful examples to get started with.

To use this as the yas-new-snippet prompt, I won’t bother customizing the yasnippet group or anything like that (I’m not sure they even expose the prompt as a customization variable), but this is emacs-lisp! I don’t need the yasnippet author’s permission or cooperation to use their code any way I see fit, and it’s trivial to replace the original yas-new-snippet command with a version that does what I want “the manual way”, the way I would accomplish the goal myself… manually.

(defvar matt/yas-new-snippet-prompt-file (doom-path "yasnippet-prompt.txt"))

(defun matt/yas-new-snippet-with-example ()
  (interactive)
  (funcall-interactively 'yas-new-snippet)
  (erase-buffer)
  (insert-file matt/yas-new-snippet-prompt-file))

(map! :map yas-minor-mode-map [remap yas-new-snippet] #'matt/yas-new-snippet-with-example)

The steps are:

  1. call the normal command
  2. erase the useless buffer
  3. insert the helpful buffer
  4. remap the keybinding for yas-new-snippet to always refer to my version instead.

And that’s it!

It’s very cathartic to have the editor do exactly what I want the way I want it to, and emacs is the only software available that even comes close to this level of program-ability.

Comparing to Jetbrains

I also use the Jetbrains IDEs, which are some of the most widely used editors and arguably the most powerful IDEs for many programming languages, and they have a feature called Live templates which is analogous to yasnippets.

Live templates use the Apache Velocity Templating Engine to program and expand templates, and that system is WAY more complex than yasnippet’s simple syntax. And yasnippets are way more powerful than Live Templates, because instead of stupid, STUPID arbitrary limitations on what are “valid” transformations in templates, yasnippet treats me like an adult and just lets me use any elisp inside the snippets.

Seriously, look at this silliness from the Jetbrains documentation:

Functions used in live template variables The following functions can be used to define live template variables:

Function Definition
capitalize(<String>) Capitalizes the first letter of a string.
capitalizeAndUnderscore(<String>) Capitalizes all the letters of a string, and inserts an underscore between the parts.
clipboard() Returns the contents of the system clipboard.

Granted there are a bunch of possibilities there, but seriously, you shouldn’t have to (and cannot possibly) enumerate all the possible editing operations I might want to do with my code.

Whereas with yasnippets my imagination is my only limitation:

  • Maybe I want to give snippets choices based on live information, e.g.
    ${2:$$(yas-choose-value (get-list-of-live-docker-images))}
    ${2:$$(yas-choose-value (browse-url-and-extract-table-info))}
    
  • Maybe I want to conditionally format a field based on the value of another field.
  • Maybe I want to recursively run snippets!

with Jetbrains you have a couple-dozen choices and that’s it, forever, no more extending the system.

Obviously I understand why the Jetbrains editor necessitates this type of system (they cannot run arbitrary user code mixed in with all these privileged editing functions), and the editor would definitely blow up if they gave you complete freedom, but that’s what makes IDEs suck and emacs awesome. It’s built in a way that the default assumption of every single function is that it can be used by whomever whenever for whatever reason.

This freedom is the reason for emacs superiority, full stop.

It’s the proper counter to “well VS Code has most of the same stuff and it has modern conveniences (and I don’t have to learn a whole programming language just to use it)”. Those people always ask “What can you really do with emacs that I can’t do with VSCode?”, and the true answer is “Whatever I want”. I’m not even a RMS fan or super zealous about the FOSS ideology or anything, this is just good software. It’s certainly the most free software.

FOSS ideologues don’t get that most people don’t really want or need that much freedom, and that’s fine. They wouldn’t get much value from using emacs.

But, what I’ve learned this past year is that you won’t ever leverage the freedom emacs affords without writing emacs-lisp. I’m sorry… I’m convinced there’s just no way around it. Sure, you can use emacs just fine without writing any code (heck I did for a long time…), but then you will always be limited to what other people think you want.

The truth is that editing with emacs and learning emacs-lisp are two distinct processes, very long and arduous, that only together unlock the power of emacs.

Finally committing to really learning emacs-lisp and the systems that compose emacs leveled-up my editing beyond what the other systems can provide me, but it’s been a really intense investment.

Over the 4 years I’ve used emacs it has consumed every text-editing activity I do (blogging, side projects in all sorts of different programming languages, day-planning, organizing all my various things with org-mode) except professional web development. I still use Jetbrain’s WebStorm for that, because it has so many features out-of-the-box I use daily that I haven’t configured emacs for (yet), but even many parts of that are slowly losing to my ever-growing emacs configuration, such as git interaction with magit.

That might seem ironic given all the chest-thumping for emacs, but I don’t think it’s hypocritical. I see emacs as one of the few callus-forming instruments available to programmers, as Rich Hickey would say in his “Design, Composition and Performance” talk (he makes this particular point at the 32:00 mark).

It hurts to use sometimes, takes ages to learn, and is quirky, but there’s no limit to your potential with it, and if you’re willing to invest wisely it pays dividends. So no, I don’t have to use emacs for professional coding work just yet, as I’m not quite enough of a virtuoso to make it play better than WebStorm.