Automagical editing and creation of snippets.
add-new-snippet.mp4
edit-existing-snippet.mp4
- Add new snippets, edit snippets, or delete snippets on the fly.
- Syntax highlighting while you edit the snippet. Includes highlighting of
tabstops and placeholders such as
$0
,${2:foobar}
, or$CLIPBOARD
- Automagical conversion from buffer text to JSON string.
- Intuitive UI for editing the snippet, dynamically adapting the number of prefixes.
- Automatic hot-reloading of any changes.
- Optional JSON-formatting and sorting of the snippet file after updating, using
yq
orjq
. (Useful when version-controlling your snippet collection.) - Snippet/file selection via
telescope
orvim.ui.select
. - Automatic bootstrapping of the snippet folder or new snippet files if needed.
- Supports only VSCode-style snippets.
Tip
You can use snippet-converter.nvim to convert your snippets to the VSCode format.
- The VSCode snippet format is the closest thing to a standard regarding snippets. It is used by friendly-snippets and supported by most snippet engine plugins for nvim.
- However, the snippets are stored as JSON files, which are a pain to modify manually. This plugin aims to alleviate that pain by automagically writing the JSON for you.
- nvim 0.10+
- Snippets saved in the VSCode-style snippet format.
- telescope OR
(dressing.nvim AND
fzf-lua).
- Note that snippet previews
only work when using
telescope
.
- Note that snippet previews
only work when using
- A snippet engine that can load VSCode-style snippets, such as:
- Optional: Treesitter parsers for the languages you want syntax highlighting for.
-- lazy.nvim
{
"chrisgrieser/nvim-scissors",
dependencies = "nvim-telescope/telescope.nvim",
opts = {
snippetDir = "path/to/your/snippetFolder",
}
},
-- packer
use {
"chrisgrieser/nvim-scissors",
dependencies = "nvim-telescope/telescope.nvim",
config = function()
require("scissors").setup ({
snippetDir = "path/to/your/snippetFolder",
})
end,
}
In addition, your snippet engine needs to point to the same snippet folder as
nvim-scissors
:
Tip
vim.fn.stdpath("config")
returns the path to your nvim config.
require("luasnip.loaders.from_vscode").lazy_load {
paths = { "path/to/your/snippetFolder" },
}
require("blink.cmp").setup {
sources = {
providers = {
snippets = {
opts = {
search_paths = { "path/to/your/snippetFolder" },
},
}
}
}
}
It is recommended to use the latest release of blink.cmp
for hot-reloading to
work.
-- NOTE: this requires the `nvim-lspconfig` as additional dependency
require('lspconfig').basics_ls.setup({
settings = {
snippet = {
enable = true,
sources = { "path/to/your/snippetFolder" }
},
}
})
Hot-reloading of the new/edited snippet for basics_ls
requires nvim-lspconfig
.
require("nvim-snippets").setup {
search_paths = { "path/to/your/snippetFolder" },
}
vim.g.vsnip_snippet_dir = "path/to/your/snippetFolder"
-- OR
vim.g.vsnip_snippet_dirs = { "path/to/your/snippetFolder" }
The plugin provides two ex commands, :ScissorsAddNewSnippet
and
:ScissorsEditSnippet
. You can pass a range to :ScissorsAddSnippet
command to
prefill snippet body (for example :'<,'> ScissorsAddSnippet
or :3 ScissorsAddSnippet
).
The plugin also provides two lua functions addNewSnippet
and editSnippet
,
which you can use to directly create keymaps:
vim.keymap.set("n", "<leader>se", function() require("scissors").editSnippet() end)
-- when used in visual mode, prefills the selection as snippet body
vim.keymap.set({ "n", "x" }, "<leader>sa", function() require("scissors").addNewSnippet() end)
"Prefix" is how trigger words are referred to in the VSCode format.
The popup intelligently adapts to changes in the prefix area: Each line represents one prefix, and creating or removing lines thus changes the number of prefixes.
The .setup()
call is optional.
-- default settings
require("scissors").setup {
snippetDir = vim.fn.stdpath("config") .. "/snippets",
editSnippetPopup = {
height = 0.4, -- relative to the window, between 0-1
width = 0.6,
border = "rounded",
keymaps = {
cancel = "q",
saveChanges = "<CR>", -- alternatively, can also use `:w`
goBackToSearch = "<BS>",
deleteSnippet = "<C-BS>",
duplicateSnippet = "<C-d>",
openInFile = "<C-o>",
insertNextPlaceholder = "<C-p>", -- insert & normal mode
},
},
telescope = {
-- By default, the query only searches snippet prefixes. Set this to
-- `true` to also search the body of the snippets.
alsoSearchSnippetBody = false,
},
-- `none` writes as a minified json file using `vim.encode.json`.
-- `yq`/`jq` ensure formatted & sorted json files, which is relevant when
-- you version control your snippets. To use a custom formatter, set to a
-- list of strings, which will then be passed to `vim.system()`.
---@type "yq"|"jq"|"none"|string[]
jsonFormatter = "none",
backdrop = {
enabled = true,
blend = 50, -- between 0-100
},
icons = {
scissors = "",
},
}
This plugin requires that you have a valid VSCode snippet folder. In addition to
saving the snippets in the required JSON format, there must also be a
package.json
file at the root of the snippet folder, specifying which files
should be used for which languages.
Example file structure inside the snippetDir
:
.
├── package.json
├── python.json
├── project-specific
│ └── nvim-lua.json
├── javascript.json
└── allFiletypes.json
Example package.json
:
{
"contributes": {
"snippets": [
{
"language": "python",
"path": "./python.json"
},
{
"language": "lua",
"path": "./project-specific/nvim-lua.json"
},
{
"language": ["javascript", "typescript"],
"path": "./javascript.json"
},
{
"language": "all",
"path": "./allFiletypes.json"
}
]
},
"name": "my-snippets"
}
Note
The special filetype all
enables the snippets globally, regardless of
filetype.
Example snippet file (here: nvim-lua.json
):
{
"autocmd (Filetype)": {
"body": [
"vim.api.nvim_create_autocmd(\"FileType\", {",
"\tpattern = \"${1:ft}\",",
"\tcallback = function()",
"\t\t$0",
"\tend,",
"})"
],
"prefix": "autocmd (Filetype)"
},
"file exists": {
"body": "local fileExists = vim.uv.fs_stat(\"${1:filepath}\") ~= nil",
"prefix": "file exists"
},
}
For details, read the official VSCode snippet documentation:
Tabstops
are denoted by $1
, $2
, $3
, with $0
being the last tabstop. They
support placeholders such ${1:foobar}
.
Note
Due to the use of $
in the snippet syntax, any literal $
needs to be
escaped as \$
.
Furthermore, there are various variables you can use, such as $TM_FILENAME
or
$LINE_COMMENT
. See here for a full list of
variables.
Tip
If you frequently create new snippets, you may also use the command
:ScissorsCreateSnippetsForSnippetVars
to create snippets for the VSCode
snippet variables in the nvim-scissors
popup window. For example, typing
filen
then triggers a suggestion for $TM_FILENAME
.
Even though the snippets from the friendly-snippets
repository are written in the VSCode-style format, editing them directly is not
supported. The reason being that any changes made would be overwritten as soon
as the friendly-snippets
repository is updated (which happens fairly
regularly). Unfortunately, there is little nvim-scissors
can do about that.
What you can do, however, is to copy individual snippets files from the
friendly-snippets
repository into your own snippet folder, and edit them there.
nvim-scissors
only allows to edit the snippet prefix and snippet body, to keep
the UI as simple as possible. For the few cases where you need to edit a
snippet's title or description, you can use the openInFile
keymap and edit
them directly in the snippet file.
This plugin writes JSON files via vim.encode.json()
. That method saves
the file in minified form, and does not have a
deterministic order of dictionary keys.
Both, minification and unstable key order, are a problem if you
version-control your snippet collection. To solve this issue, nvim-scissors
can optionally unminify and sort the JSON files via yq
or jq
after updating
a snippet. (Both are also available via
mason.nvim.)
It is recommended to run yq
/jq
once on all files in your snippet
collection, since the first time you edit a file, you would still get a large diff
from the initial sorting. You can do so with yq
using this command:
cd "/your/snippet/dir"
fd ".*\.json" | xargs -I {} yq --inplace --output-format=json "sort_keys(..)" {}
How to do the same with jq
is left as an exercise to the reader.
With Luasnip
, this is an opt-in feature, enabled via:
require("luasnip").setup {
store_selection_keys = "<Tab>",
}
In your VSCode-style snippet, use the token $TM_SELECTED_TEXT
at the location
where you want the selection to be inserted. (It's roughly the equivalent of
LS_SELECT_RAW
in the Luasnip
syntax.)
Then, in visual mode, press the key from store_selection_keys
. The selection
disappears, and you are put in insert mode. The next snippet you now trigger
is going to have $TM_SELECTED_TEXT
replaced with your selection.
While the VSCode snippet format does not support auto-triggered snippets,
LuaSnip
allows you to specify auto-triggering in the VSCode-style JSON
files by adding the luasnip
key.
nvim-scissors
does not touch any keys other than prefix
and body
in the
JSON files, so any additions via the luasnip
key are preserved.
Tip
You can use the openInFile
keymap to directory open JSON file at the
snippet's location to make edits there easier.
In my day job, I am a sociologist studying the social mechanisms underlying the digital economy. For my PhD project, I investigate the governance of the app economy and how software ecosystems manage the tension between innovation and compatibility. If you are interested in this subject, feel free to get in touch.
I also occasionally blog about vim: Nano Tips for Vim