SnippetConverter is a Neovim plugin that allows you to convert snippets (such as VSCode snippets, UltiSnips snippets etc.) between different formats. This allows users to reuse their snippets when switching to a different snippet engine by first converting them to a supported format. The aim of this plugin is to make it easier for users to create and share snippets as they become "snippet-engine-agnostic". Additionally, it provides the ability to modify individual snippets using a few lines of Lua code.
SnippetConverter supports conversion between the folowing formats:
LuaSnip snippets can contain a custom luasnip
key. That is why vscode_luasnip
has been added as a separate format. For example, if converting an UltiSnips snippet,
and the option A
is present, the luasnip.autotrigger
key will be set to true
.
-
vsnip: a superset of VSCode snippets (vsnip snippets can contain Vimscript code)
-
YASnippet (an Emacs snippet engine)
The following table shows which snippets can be converted to another format (the first column denotes the source format):
Source format / Target format | vscode | vscode_luasnip | vsnip | ultisnips | snipmate | snipmate_luasnip | yasnippet |
---|---|---|---|---|---|---|---|
vscode | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
vscode_luasnip | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
vsnip | ✓ [1] | ✓ [1] | ✓ | ✓ | ✓ [1] | ✓ | ✓ [1] |
ultisnips | ✓ [2] | ✓ [2] | ✓ [3] | ✓ | ✓ [4] | ✓ [4] | ✓ [2] |
snipmate | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
snipmate_luasnip | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
yasnippet | ✓ [5] | ✓ [5] | ✓ [5] | ✓ [5] | ✓ [5] | ✓ [5] | ✓ [5] |
Legend:
✓: All snippets can be converted - no exceptions.
✓ [1]: All except snippets with vimscript code.
✓ [2]: All except snippets with python / vimscript / shell code or regular expression triggers.
✓ [3]: All except snippets with python / shell code or regular expression triggers / transformations.
✓ [4]: All except snippets with python / shell code or regular expression triggers.
✓ [5]: All except snippets with Emacs-Lisp code or transformations.
💡 Note that source and target format can be the same. This is useful if you only want to filter certain snippets or apply transformations to them without converting them to a different format.
In order to convert snippets from one supported format to another, create a
template with the input / output formats and paths and pass it to the setup
function
(see Creating templates).
Then run the command :ConvertSnippets
. A floating window will pop up that shows you further information
about the status of the conversion such as syntax errors.
By default, all templates that have been passed to setup
will be executed sequentially.
If you only want to run a single template or a selection of them, pass their names to the
command (separated by spaces):
:ConvertSnippets template_a template_b
If you don't want the UI to be shown, use headless mode:
:ConvertSnippets headless=true
Alternatively, you can change the default option headless
globally using the default_opts
table
(see Configuration).
A template is simply a table that describes the input / output formats and paths of a conversion.
Templates must be passed to the setup
function as a list:
local template = {
sources = {
ultisnips = {
-- Folders or files in the runtimepath
"./vim-snippets/UltiSnips",
"./latex-snippets/tex.snippets",
},
vsnip = {
-- Absolute paths to snippet directories or files
vim.fn.stdpath("config") .. "/vsnip-snippets",
},
}
}
require("snippet_converter").setup {
templates = {
template,
-- other templates...
}
}
It can contain any of the following keys:
name: string?
An optional name that can be passed as an argument to the :ConvertSnippets
command.
If not specified, a default value (the index of the template in the templates
table passed to setup
) will be used.
sources: table <string, string[]>
A table with a list of paths per source format.
For a list of available source formats, see Supported snippet formats.
The paths can either be absolute paths or relative paths to folders or files in your Neovim runtimepath. For the latter, prefix the path with ./
.
All snippet files that match any of the given paths will be parsed and converted to the specified output formats. However, a path that also matches any output path of the same template will be ignored! This is to avoid reconverting snippets that have already been converted in a previous run.
If the source format is "yasnippet", directories will be searched recursively due to the special directory structure expected by YASnippet.
output: table<string, string[] | table>
A table with a list of paths per output format where the converted snippets will be stored.
Each path must be an absolute path to a directory. If a directory does not exist, it will be created.
Can optionally contain an opts
table with additional options (see Output format options).
See Recommended output paths for advice on how to choose a suitable output path.
transform_snippets: snippet -> snippet | nil
An optional transformation function, see Transforming snippets.
sort_snippets: (snippet -> snippet) -> boolean
An optional sorting function, see Sorting snippets.
Choosing the correct output paths is important to make the converted snippets available to your snippet engine. For details, always refer to the documentation of your snippet engine.
-
LuaSnip will load VSCode or SnipMate snippets if they are stored in your Neovim runtimepath. Note: you need to call
require("luasnip.loaders.from_vscode").load(opts)
orrequire("luasnip.loaders.from_snipmate").load(opts)
in your config.To load snippets from locations outside of your runtimepath, pass a list of paths to the
opts.paths
table. Example: use{ paths = "./vscode_snippets" }
to load snippets atvim.fn.stdpath("config") .. "/vscode_snippets"
.Therefore, the following is a suitable output path for your generated snippets:
vim.fn.stdpath("config") .. "/vscode_snippets"
For VSCode snippets, SnippetConverter will automatically generate the required
package.json
file in the root directory.
- By default, UltiSnips will look for snippets inside
UltiSnips
andsnippets
folders in your runtimepath (the latter is used for SnipMate snippets). Thus, a valid output path is:This can be changed by modifying the global table variablevim.fn.stdpath("config") .. "/UltiSnips"
vim.g.UltiSnipsSnippetDirectories
.
- Similarly to LuaSnip, vsnip can load VSCode snippets that are present in a
snippets
folder in your runtimepath. (SnippetConverter supports snippets that use the Vimscript interpolation feature of vsnip.) Custom snippets can be added at the location ofvim.g.vsnip_snippet_dir
(which is~/.vsnip
by default) by running the:VsnipOpen
command. So the following are valid output paths:vim.g.vsnip_snippet_dir -- or vim.fn.stdpath("config") .. "/snippets"
- SnipMate can find snippets inside
snippets
folders in your runtimepath, so a valid output path would be:vim.fn.stdpath("config") .. "/snippets"
You can set additional options by passing an opts
table to the output format.
Currently there is only one option which is useful for vscode
or vscode_luasnip
output formats:
output = {
vscode_luasnip = {
vim.fn.stdpath("config") .. "/vscode_snippets",
opts = {
-- Whether a package.json file should be generated in the output root directory.
-- This is useful if you only want to modify existing VSCode snippets without overwriting the package.json file.
-- Default: `true`
generate_package_json = false,
},
},
}
Before snippets are converted, it is possible to apply a transformation on them. Transformations can be used to either discard specific snippets or modify them arbitrarily. They can be specified per template or globally (the global transform function will be run last).
The transformation function takes as parameters the snippet
itself and a helper
table that provides additional utilities for transforming the snippet.
If false
is returned, the current snippet is discarded. If a non-nil value is returned, the snippet is replaced with the result of parsing the returned value
(the snippet format must match the source format). For VSCode snippets, this must be a table, for other formats it is a string (see examples).
It is possible to return multiple snippets.
💡 It is possible to return multiple snippets. This allows you to turn an existing snippet into multiple new ones. [An example would be great here, let me know if you have one to share!]
In contrast to loaded snippets, any errors that occur during parsing will be shown to the user immediately.
The available keys in the snippet table are listed below. Optional keys can be nil.
Key | Type | Source formats | Optional? |
---|---|---|---|
trigger | string | All | No |
description | string | All | Yes |
body | table | All | No |
scope | table | vscode / vscode_luasnip / vsnip |
Yes |
path | string | All | No |
line_nr | int | All except vscode / vscode_luasnip / vsnip |
No |
options | string | ultisnips |
Yes |
custom_context | string | ultisnips |
Yes |
priority | number | vscode_luasnip / ultisnips / snipmate_luasnip |
Yes |
autotrigger | boolean | vscode_luasnip |
Yes |
The helper
table contains the following entries:
Key | Type | Explanation |
---|---|---|
source_format | string | The input format of the snippet. |
target_format | string | The output format of the snippet. |
dedent | function | A function that takes a single string as an argument and removes leading whitespace from all lines. The indentation is inferred from the first line. This allows for nicer formatting. |
Optionally, a table of options can be returned as a second value which may contain any of the following items:
Key | Type | Default | Description |
---|---|---|---|
replace | boolean | true |
If true , the current existing snippet will be replaced with the new one(s), otherwise it will be kept. |
format | string | source_format | The format of the snippet which determines the parser to be used. Useful if you prefer a particular syntax over another. |
Examples
Modify a specific UltiSnips snippet (this effectively reverts this vim-snippets commit - see the related issue #1396):
transform_snippets = function(snippet, helper)
if snippet.path:find("vim-snippets/.*/tex%.snippets") and snippet.trigger == "$$" then
return helper.dedent([[
snippet im "Inline Math" w
$${1}$
endsnippet
]]),
-- These are the default options (could be omitted)
{ replace = true, format = "ultisnips" }
end
end
Here is an example for VSCode snippets (this needs to match the default syntax of VSCode
snippets, e.g. use prefix
instead of trigger
):
transform_snippets = function(snippet, helper)
if snippet.trigger == "if" then
return {
["if"] = {
prefix = "if",
body = { "if ${1:condition} then", "\t$0", "end" },
}
}
end
end
Delete all snippets with a specific trigger:
transform_snippets = function(snippet, helper)
if snippet.trigger == "..." then
return false
end
end
Remove all auto-triggers from UltiSnips snippets:
transform_snippets = function(snippet, helper)
if snippet.options and snippet.options:match("A") then
snippet.options = snippet.options:gsub("A", "")
end
end
By default, when converting snippets, the output snippets will appear in the same order as they were defined in the input files. Snippets defined in JSON format (such as VSCode and vsnip snippets) will be sorted alphabetically due to the way JSON files are read by Neovim (the order of JSON keys is not preserved).
You can control the sorting behaviour by passing a sort_snippets
function to the template or setup functions.
This function takes as parameters the two snippets to compare and must return a boolean.
When true
is returned, the first snippet will be placed before the second one, otherwise the second one comes before the first one.
Examples
The following example will sort the snippets by their trigger in ascending order:
sort_snippets = function(first, second)
return first.trigger < second.trigger
end
A more advanced example puts snippets with a priority value at the top of the output file, sorting them by their priority in descending order, then by their trigger in ascending order:
sort_snippets = function(first, second)
if (first.priority or -math.huge) > (second.priority or -math.huge) then
return true
end
return first.trigger < second.trigger
end,
You can pass a settings table to the setup
function in order to overwrite the default settings or options:
require("snippet_converter").setup {
settings = {
-- ...
},
default_opts = {
-- ...
},
}
Default config:
DEFAULT_CONFIG = {
settings = {
ui = {
use_nerdfont_icons = true,
},
},
default_opts = {
headless = false,
},
}
Here are the available settings:
settings.ui.use_nerdfont_icons: boolean
Specifies whether Nerd Fonts icons should be used for the icons in the floating window. Set this to false
if you are not using a Nerd Font - otherwise the icons will not be displayed correctly.
Default: true
default_opts.headless: boolean
Specifies whether the :ConvertSnippets
command should run in headless mode.
If set to false
, a floating window will show the status of the conversion operation.
Default: false