LuaSnip
javadoc.mp4
Features
- Tabstops
- Text-Transformations using Lua functions
- Conditional Expansion
- Defining nested Snippets
- Filetype-specific Snippets
- Choices
- Dynamic Snippet creation
- Regex-Trigger
- Autotriggered Snippets
- Fast
- Parse LSP-Style Snippets (Does not, however, support Regex-Transformations)
- Expand LSP-Snippets with nvim-compe (or its' successor, nvim-cmp (requires cmp_luasnip))
- Snippet history (jump back into older snippets)
Drawbacks
- Snippets that make use of the entire functionality of this plugin have to be defined in Lua (but 95% of snippets can be written in lsp-syntax).
Requirements
Neovim >= 0.5 (extmarks)
Setup
Install
Ie. With vim-plug
Keymaps
in vimscript
" press <Tab> to expand or jump in a snippet. These can also be mapped separately
" via <Plug>luasnip-expand-snippet and <Plug>luasnip-jump-next.
imap <silent><expr> <Tab> luasnip#expand_or_jumpable() ? '<Plug>luasnip-expand-or-jump' : '<Tab>'
" -1 for jumping backwards.
inoremap <silent> <S-Tab> <cmd>lua require'luasnip'.jump(-1)<Cr>
snoremap <silent> <Tab> <cmd>lua require('luasnip').jump(1)<Cr>
snoremap <silent> <S-Tab> <cmd>lua require('luasnip').jump(-1)<Cr>
" For changing choices in choiceNodes (not strictly necessary for a basic setup).
imap <silent><expr> <C-E> luasnip#choice_active() ? '<Plug>luasnip-next-choice' : '<C-E>'
smap <silent><expr> <C-E> luasnip#choice_active() ? '<Plug>luasnip-next-choice' : '<C-E>'
or in lua (includes supertab-like functionality with nvim-cmp)
local function prequire(...)
local status, lib = pcall(require, ...)
if (status) then return lib end
return nil
end
local luasnip = prequire('luasnip')
local cmp = prequire("cmp")
local t = function(str)
return vim.api.nvim_replace_termcodes(str, true, true, true)
end
local check_back_space = function()
local col = vim.fn.col('.') - 1
if col == 0 or vim.fn.getline('.'):sub(col, col):match('%s') then
return true
else
return false
end
end
_G.tab_complete = function()
if cmp and cmp.visible() then
cmp.select_next_item()
elseif luasnip and luasnip.expand_or_jumpable() then
return t("<Plug>luasnip-expand-or-jump")
elseif check_back_space() then
return t "<Tab>"
else
cmp.complete()
end
return ""
end
_G.s_tab_complete = function()
if cmp and cmp.visible() then
cmp.select_prev_item()
elseif luasnip and luasnip.jumpable(-1) then
return t("<Plug>luasnip-jump-prev")
else
return t "<S-Tab>"
end
return ""
end
vim.api.nvim_set_keymap("i", "<Tab>", "v:lua.tab_complete()", {expr = true})
vim.api.nvim_set_keymap("s", "<Tab>", "v:lua.tab_complete()", {expr = true})
vim.api.nvim_set_keymap("i", "<S-Tab>", "v:lua.s_tab_complete()", {expr = true})
vim.api.nvim_set_keymap("s", "<S-Tab>", "v:lua.s_tab_complete()", {expr = true})
vim.api.nvim_set_keymap("i", "<C-E>", "<Plug>luasnip-next-choice", {})
vim.api.nvim_set_keymap("s", "<C-E>", "<Plug>luasnip-next-choice", {})
or in lua with nvim-compe
local function prequire(...)
local status, lib = pcall(require, ...)
if (status) then return lib end
return nil
end
local luasnip = prequire('luasnip')
local t = function(str)
return vim.api.nvim_replace_termcodes(str, true, true, true)
end
local check_back_space = function()
local col = vim.fn.col('.') - 1
if col == 0 or vim.fn.getline('.'):sub(col, col):match('%s') then
return true
else
return false
end
end
_G.tab_complete = function()
if vim.fn.pumvisible() == 1 then
return t "<C-n>"
elseif luasnip and luasnip.expand_or_jumpable() then
return t("<Plug>luasnip-expand-or-jump")
elseif check_back_space() then
return t "<Tab>"
else
return vim.fn['compe#complete']()
end
return ""
end
_G.s_tab_complete = function()
if vim.fn.pumvisible() == 1 then
return t "<C-p>"
elseif luasnip and luasnip.jumpable(-1) then
return t("<Plug>luasnip-jump-prev")
else
return t "<S-Tab>"
end
return ""
end
vim.api.nvim_set_keymap("i", "<Tab>", "v:lua.tab_complete()", {expr = true})
vim.api.nvim_set_keymap("s", "<Tab>", "v:lua.tab_complete()", {expr = true})
vim.api.nvim_set_keymap("i", "<S-Tab>", "v:lua.s_tab_complete()", {expr = true})
vim.api.nvim_set_keymap("s", "<S-Tab>", "v:lua.s_tab_complete()", {expr = true})
vim.api.nvim_set_keymap("i", "<C-E>", "<Plug>luasnip-next-choice", {})
vim.api.nvim_set_keymap("s", "<C-E>", "<Plug>luasnip-next-choice", {})
For nvim-cmp, it is also possible to follow the example recommendation from the nvim-cmp wiki.
Add Snippets
-
Vscode-like: For using snippets from a plugin (eg. rafamadriz/friendly-snippets) install it and add
require("luasnip.loaders.from_vscode").load()
somewhere in your config.
For more info on the vscode-loader, check the examples or documentation. -
Snipmate-like: Very similar to Vscode-packages: install the a plugin that provides snippets and call the
load
-function:require("luasnip.loaders.from_snipmate").load()
The snipmate format is very simple, so adding custom snippets only requires a few steps:
- add a directory beside your
init.vim
(or any other place that is in yourruntimepath
) namedsnippets
. - inside that directory, create files named
<filetype>.snippet
and add snippets for the given filetype in it (for inspiration, check honza/vim-snippets).# comment snippet <trigger> <description> <snippet-body> snippet if C-style if if ($1) $0
- add a directory beside your
-
Lua: Add the snippets directly to
require("luasnip").snippets.<filetype>
. An example for this can be found here.
This can also be done much better (one snippet-file per filetype+command for editing the current filetype) than in the example, see this entry in the wiki
Docs and Exaples
I highly recommend looking into (or better yet, :luafile
ing) Examples/snippets.lua
before writing snippets in lua.
Check DOC.md
(or :help luasnip
) for in-depth explanations of the different nodes.
Config
history
: If true, Snippets that were exited can still be jumped back into. As Snippets are not removed when their text is deleted, they have to be removed manually viaLuasnipUnlinkCurrent
.updateevents
: Choose which events trigger an update of the active nodes' dependents. Default is just'InsertLeave'
,'TextChanged,TextChangedI'
would update on every change.region_check_events
: Events on which to leave the current snippet if the cursor is outside its' 'region'. Disabled by default,'CursorMoved'
,'CursorHold'
or'InsertEnter'
seem reasonable.delete_check_events
: When to check if the current snippet was deleted, and if so, remove it from the history. Off by default,'TextChanged'
(perhaps'InsertLeave'
, to react to changes done in Insert mode) should work just fine (alternatively, this can also be mapped using<Plug>luasnip-delete-check
).store_selection_keys
: Mapping for populatingTM_SELECTED_TEXT
and related variables (not set by default).enable_autosnippets
: Autosnippets are disabled by default to minimize performance penalty if unused. Set totrue
to enable.ext_opts
: Additional options passed to extmarks. Can be used to add passive/active highlight on a per-node-basis (more info in DOC.md)parser_nested_assembler
: Override the default behaviour of inserting achoiceNode
containing the nested snippet and an emptyinsertNode
for nested placeholders ("${1: ${2: this is nested}}"
). For an example (behaviour more similar to vscode), check hereft_func
: Source of possible filetypes for snippets. Defaults to a function, which returnsvim.split(vim.bo.filetype, ".", true)
, but check filetype_functions for other options
Inspired by vsnip.vim