Support Ukraine 🇺🇦Help Ukrainian ArmyHumanitarian Assistance to Ukrainians

How to set up UltiSnips for vim?

yarik

Feb 16 2020 at 17:24 GMT

Can someone write an in-depth explanation of how to set up UltiSnips for vim and use it to create code snippets?

1 Answer

yarik

Feb 16 2020 at 17:50 GMT

What is UltiSnips?

UltiSnips is a vim plugin that makes it easy to create snippets in vim.

For example, we could define a snippet for a simple HTML link as

snippet link "Simple HTML link" b
<a href="$1">$0</a>
endsnippet

Then, when editing our HTML, we write link followed by a tab, and this will expand to

<a href=""></a>

with the cursor set inside the href string.

After entering the URL, we can press tab, and the cursor moves inside the tag where we can write the displayed text for the link.

After that, we exit "snippet mode".

Before seeing more complex snippets, let's see how to install UltiSnips.

How to install UltiSnips

We can install UltiSnips using vim pathogen as follows:

cd ~/.vim/bundle && git clone git://github.com/SirVer/ultisnips.git

To also install the default snippets, run:

cd ~/.vim/bundle && git clone git://github.com/honza/vim-snippets.git

After that, insert the following configuration in your .vimrc file before the execute pathogen#infect() line:

let g:UltiSnipsSnippetDirectories = ['~/.vim/UltiSnips', 'UltiSnips']
let g:UltiSnipsExpandTrigger="<tab>"
let g:UltiSnipsJumpForwardTrigger="<tab>"
let g:UltiSnipsJumpBackwardTrigger="<s-tab>"
let g:UltiSnipsEditSplit="vertical"

After closing vim and opening it again, you should be able to use snippets.

If you use a vim plugin manager other than pathogen, follow the instructions specific to that manager for installing a plugin. However, don't forget to add the above configuration in your .vimrc file.

Adding snippets

To add a snippet for the file type that is currently open (for example, a JavaScript snippet if the file open is a JavaScript file), run in vim

:UltiSnipsEdit

Example: JavaScript function snippet

Let's define a snippet that we can use to define a function in JavaScript.

The snippet definition looks like this:

snippet func "Defines a function" b
function $1($2) {
  $0
}
endsnippet

Begin by entering usnip in INSERT mode followed by a tab.

This should expand the snippet used for defining new snippets.

Enter the snippet name func, then press tab, enter the description "Defines a function", press tab, leave the b option without changing it, press tab, and finally enter the JavaScript snippet

function $1($2) {
  $0
}

Then, save the file with :w.

For the snippet to be available, you either need to restart vim or run

:call UltiSnips#RefreshSnippets()

Once the snippet is available, you can enter func in a JavaScript file, followed by a tab, and this should expand to

function () {

}

with the cursor just before the opening parenthesis.

At this point, you can enter the function name, press tab, enter the parameters (if any), press tab, and finally enter the function body.

When you reach the $0, you exit "snippet mode".

If you want to provide a default function name f, you can do this by defining the snippet as

snippet func "Defines a function" b
function ${1:f}($2) {
  $0
}
endsnippet

Example: React component snippet

Let's say we want to define a snippet for a React component that is exported as the default export in a new file, we can do this as

snippet reactcomp "Starter for a new react component" b
/*
 * $1.js
 * ---------------
 * Exports the $1 component.
 */

import React from "react";

const ${2:Component} = () => (
  <${3:div}>
    $0
  </${3/(\w+).*/$1/}>
);

export default $2;
endsnippet

With this snippet definition, we can open a JavaScript file and type reactcomp followed by a tab to create a new React component without much typing.

Note that the $1 appears multiple times in the snippet. This allows to keep all occurances of $1 in sync. Same goes for $2 and $3.

Additionally, we have a substitution for $3:

${3/(\w+).*/$1/}

This ensures that if we type h1 id="main-header" in the first $3, the second $3 only inserts the h1 part without the rest since in a closing tag we only need to enter the tag name.

Inserting the selected content into a snippet

Let's say that we have these 2 lines of JavaScript:

console.log(arguments);
return arguments[0];

and we want to make them the body of our new function.

To do so, we first need to change $0 in the func snippet to ${0:${VISUAL}}:

snippet func "Defines a function" b
function ${1:f}($2) {
  ${0:${VISUAL}}
}
endsnippet

Then, in VISUAL mode, we can highlight the two lines that we want to be the body of our function and press tab, followed by func, followed by another tab.

The result is that the two highlighted lines are the body of our function.

Using ${0:${VISUAL}} still allows you to use the snippet as you would with $0, with the additional benefit that you can insert the visually selected lines in place of the $0.

Advanced: Using Python interpolation

In UltiSnips, you can create dynamic snippets by executing Python code.

Even if you don't know Python, you can still write simple Python statements in your snippets by looking at examples.

Let's say that we want to create a snippet with a comment that we insert at the beginning of every JavaScript file.

Let's call this snippet newfile.

If we open a new example.js file, we want to type newfile followed by tab, and have it expand to

/*
 * example.js
 * ----------
 * <Cursor here>
 */

with the cursor position at <Cursor here> where we can write a comment specific to the file.

Notice how the file name in the comment got automatically filled in.

We can define the newfile snippet as follows:

snippet newfile "Starting comment in new file" b
/*
 * `!p snip.rv = snip.fn`
 * `!p snip.rv = len(snip.fn) * "-"`
 * ${0:${VISUAL}}
 */
endsnippet

The expressions `!p ...` are Python interpolations.

The snip.rv is the return value of that interpolation.

So, `!p snip.rv = snip.fn` indicates that the return value is snip.fn, which is the file name within which we have expanded the snippet.

In the example.js file, snip.fn would be example.js.

Then, we want to have a separator made of dashes (--------) that is as long as the file name.

We generate the separator by taking the length of snip.fn using the len function, and then multiply the length by the "-" string.

Besiders rv and fn keys of the snip object, there are more keys that are available in the snippet:

  • snip.basename - The file name without the extension (e.g., example for the example.js file)
  • snip.ft - The file type (e.g., javascript for a .js file)
  • and more...

Additional variables available in the Python interpolation

Besides the snip variable, there are some more useful variables that are available:

  • t - Used to access the values of placeholders (e.g., t[2] is the value of $2)
  • fn - The name of the current file
  • path - The absolute path to the current file
  • match - The match object for the snippet regular expression (this is only available in snippets triggered by regular expressions)

Example: Repeat token snippet

Let's say that we want to write a snippet that will produce 80 asterisks (***...) when we type * 80 followed by a tab.

We can define such snippet like this:

snippet "(\S+) (\d+)" "Repeat token n times" r
`!p snip.rv = int(match.group(2)) * match.group(1)`
endsnippet

First of all, we use the r option instead of the b option. The r option is used when we want the snippet to be triggered by a regular expression match.

The b option, that we've been using so far, indicates that we simply want the snippet name to be on a line on its own with no extra text.

If we specify no option at all, then the snippet name can appear anywhere, and thus pressing tab will always expand it.

The above snippet is triggered by the (\S+) (\d+) regular expression, which matches characters other than whitespace repeated one or more times, followed by a space, followed by digits repeated one or more times.

We use enclosing paretheses to create a capturing group, which gives us access to the specific matches.

Here, match.group(1) corresponds to the first group in the regular expression ((\S+)), and match.group(2) corresponds to the second group ((\d+)).

So, we just convert the digits from string to integer, and then multiply the integer by the token.

We can also use this to say "Hi!" 10 times. We just need to type Hi! 10 followed by a tab.

Conclusion

I hope that in this answer, I clearly explained how to install UltiSnips, define snippets, and use them.

However, I did not cover all the features of UltiSnips. If you want to learn more, read the official UltiSnips documentation.

claritician © 2022