Home

Awesome

<!-- LTeX: enabled=false -->

nvim-scissors ✂️

<!-- LTeX: enabled=true --> <a href="https://dotfyle.com/plugins/chrisgrieser/nvim-scissors"> <img alt="badge" src="https://dotfyle.com/plugins/chrisgrieser/nvim-scissors/shield"/></a>

Automagical editing and creation of snippets.

https://github.com/chrisgrieser/nvim-scissors/assets/73286100/c620958a-eef6-46c2-957a-8504733e0312

https://github.com/chrisgrieser/nvim-scissors/assets/73286100/de544b7e-20c3-4bec-b7aa-cbaaacca09ca

Table of Contents

<!-- toc --> <!-- tocstop -->

Features

[!TIP] You can use snippet-converter.nvim to convert your snippets to the VSCode format.

Rationale

Requirements

Installation

-- lazy.nvim
{
	"chrisgrieser/nvim-scissors",
	dependencies = { "nvim-telescope/telescope.nvim", "garymjr/nvim-snippets" }, 
	opts = {
		snippetDir = "path/to/your/snippetFolder",
	} 
},

-- packer
use {
	"chrisgrieser/nvim-scissors",
	dependencies = { "nvim-telescope/telescope.nvim", "garymjr/nvim-snippets" }, 
	config = function()
		require("scissors").setup ({
			snippetDir = "path/to/your/snippetFolder",
		})
	end,
}

In addition, you need your snippet engine to point to the same snippet folder as nvim-scissors:

LuaSnip

require("luasnip.loaders.from_vscode").lazy_load {
	paths = { "path/to/your/snippetFolder" },
}

nvim-snippets

require("nvim-snippets").setup {
	search_paths = { "path/to/your/snippetFolder" },
}

vim-vsnip

vim.g.vsnip_snippet_dir = "path/to/your/snippetFolder"
-- OR
vim.g.vsnip_snippet_dirs = { "path/to/your/snippetFolder" }

Usage

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)

Useful keymaps in the scissors popup window

Prefixes

"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.

<img alt="Showcase prefix change" width=70% src="https://github.com/chrisgrieser/nvim-scissors/assets/73286100/d54f96c2-6751-46e9-9185-77b63eb2664a">

Configuration

The .setup() call is optional.

-- default settings
require("scissors").setup {
	snippetDir = vim.fn.stdpath("config") .. "/snippets",
	editSnippetPopup = {
		height = 0.4, -- relative to the editor, number between 0 and 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
		},
	},
	backdrop = {
		enabled = true,
		blend = 50, -- between 0-100
	},
	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.
	jsonFormatter = "none", -- "yq"|"jq"|"none"
}

[!TIP] vim.fn.stdpath("config") returns the path to your nvim config.

Cookbook & FAQ

Introduction to the VSCode-style snippet format

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 at the root of the snippet folder, specifying which files are 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:

Variables & Tabstops

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 nvim-scissors's popup window. For example, typing filen then creates a suggestion for $TM_FILENAME.

Version Controlling Snippets: JSON-formatting

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.

Snippets on visual selections

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.

friendly-snippets

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.

Auto-triggered Snippets

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.

<!-- vale Google.FirstPerson = NO -->

Credits

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

<a href='https://ko-fi.com/Y8Y86SQ91' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://cdn.ko-fi.com/cdn/kofi1.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>