-
-
Notifications
You must be signed in to change notification settings - Fork 246
Nice Configs
Using ext_opts
it's possible to add virtual text to nodes:
local types = require("luasnip.util.types")
require'luasnip'.config.setup({
ext_opts = {
[types.choiceNode] = {
active = {
virt_text = {{"●", "GruvboxOrange"}}
}
},
[types.insertNode] = {
active = {
virt_text = {{"●", "GruvboxBlue"}}
}
}
},
})
This adds an orange dot to the end of the line if inside a choiceNode
, and a blue one if inside an insertNode
:
local util = require("luasnip.util.util")
local node_util = require("luasnip.nodes.util")
ls.setup({
parser_nested_assembler = function(_, snippetNode)
local select = function(snip, no_move, dry_run)
if dry_run then
return
end
snip:focus()
-- make sure the inner nodes will all shift to one side when the
-- entire text is replaced.
snip:subtree_set_rgrav(true)
-- fix own extmark-gravities, subtree_set_rgrav affects them as well.
snip.mark:set_rgravs(false, true)
-- SELECT all text inside the snippet.
if not no_move then
require("luasnip.util.feedkeys").feedkeys_insert("<Esc>")
node_util.select_node(snip)
end
end
local original_extmarks_valid = snippetNode.extmarks_valid
function snippetNode:extmarks_valid()
-- the contents of this snippetNode are supposed to be deleted, and
-- we don't want the snippet to be considered invalid because of
-- that -> always return true.
return true
end
function snippetNode:init_dry_run_active(dry_run)
if dry_run and dry_run.active[self] == nil then
dry_run.active[self] = self.active
end
end
function snippetNode:is_active(dry_run)
return (not dry_run and self.active) or (dry_run and dry_run.active[self])
end
function snippetNode:jump_into(dir, no_move, dry_run)
self:init_dry_run_active(dry_run)
if self:is_active(dry_run) then
-- inside snippet, but not selected.
if dir == 1 then
self:input_leave(no_move, dry_run)
return self.next:jump_into(dir, no_move, dry_run)
else
select(self, no_move, dry_run)
return self
end
else
-- jumping in from outside snippet.
self:input_enter(no_move, dry_run)
if dir == 1 then
select(self, no_move, dry_run)
return self
else
return self.inner_last:jump_into(dir, no_move, dry_run)
end
end
end
-- this is called only if the snippet is currently selected.
function snippetNode:jump_from(dir, no_move, dry_run)
if dir == 1 then
if original_extmarks_valid(snippetNode) then
return self.inner_first:jump_into(dir, no_move, dry_run)
else
return self.next:jump_into(dir, no_move, dry_run)
end
else
self:input_leave(no_move, dry_run)
return self.prev:jump_into(dir, no_move, dry_run)
end
end
return snippetNode
end,
})
The main reason for this not being the default is that it
- requires a lot of modification to the resulting snippets
- Doesn't work perfectly(yet): if the entire placeholder is replaced, the nested tabstops won't be skipped.
Example with "test: ${1: this is ${2:nested} ${3:text}} ${4:yay!}"
:
A slight change to the recommended nvim-cmp setup where we replace luasnip.expand_or_jumpable()
with luasnip.expand_or_locally_jumpable()
to only jump to a snippet field if we are currently in a snippet.
example setup:
cmp.setup({
-- ... Your other configuration ...
mapping = {
-- ... Your other mappings ...
["<Tab>"] = cmp.mapping(function(fallback)
if cmp.visible() then
cmp.select_next_item()
elseif luasnip.expand_or_locally_jumpable() then
luasnip.expand_or_jump()
elseif has_words_before() then
cmp.complete()
else
fallback()
end
end, { "i", "s" }),
}
}
Similar to the above, but this is for setting the same keymap for changing choice if there's an active choicenode or using external update dynamic node. Since there are a lot of keymaps already, it might be nice to overload them and use the same keymap for mutually exclusive events. Set it up as follows:
-- feel free to change the keys to new ones, those are just my current mappings
vim.keymap.set("i", "<C-f>", function ()
if ls.choice_active() then
return ls.change_choice(1)
else
return _G.dynamic_node_external_update(1) -- feel free to update to any index i
end
end, { noremap = true })
vim.keymap.set("s", "<C-f>", function ()
if ls.choice_active() then
return ls.change_choice(1)
else
return _G.dynamic_node_external_update(1)
end
end, { noremap = true })
vim.keymap.set("i", "<C-d>", function ()
if ls.choice_active() then
return ls.change_choice(-1)
else
return _G.dynamic_node_external_update(2)
end
end, { noremap = true })
vim.keymap.set("s", "<C-d>", function ()
if ls.choice_active() then
return ls.change_choice(-1)
else
return _G.dynamic_node_external_update(2)
end
end, { noremap = true })
Also see:
- this discussion for more context
- this wiki page for more information on external update dynamic node
With ls.activate_node()
being added recently, there is now a way to jump to any snippet in the buffer.
This can be utilized with a mapping like the following:
local select_next = false
vim.keymap.set({"i"}, "<C-;>", function()
-- the meat of this mapping: call ls.activate_node.
-- strict makes it so there is no fallback to activating any node in the
-- snippet, and select controls whether the text associated with the node is
-- selected.
local ok, _ = pcall(ls.activate_node, {
strict = true,
-- select_next is initially unset, but set within the first second after
-- activating the mapping, so activating it again in that timeframe will
-- select the text of the found node.
select = select_next
})
-- ls.activate_node throws on failure.
if not ok then
print("No node.")
return
end
-- once the node is activated, we are either done (if text is SELECTed), or
-- we briefly highlight the text associated with the node so one can know
-- which node was activated.
-- TODO: this highlighting does not show up if the node has no text
-- associated (ie ${1:asdf} vs $1), a cool extension would be to also show
-- something if there was no text.
if select_next then
return
end
local curbuf = vim.api.nvim_get_current_buf()
local hl_duration_ms = 100
local node = ls.session.current_nodes[curbuf]
-- get node-position, raw means we want byte-columns, since those are what
-- nvim_buf_set_extmark expects.
local from, to = node:get_buf_position({raw = true})
-- highlight snippet for 1000ms
local id = vim.api.nvim_buf_set_extmark(
curbuf,
ls.session.ns_id,
from[1],
from[2],
{
-- one line below, at col 0 => entire last line is highlighted.
end_row = to[1],
end_col = to[2],
hl_group = "Visual",
}
)
-- disable highlight by removing the extmark after a short wait.
vim.defer_fn(function()
vim.api.nvim_buf_del_extmark(curbuf, ls.session.ns_id, id)
end, hl_duration_ms)
-- set select_next for the next second.
select_next = true
vim.uv.new_timer():start(1000, 0, function()
select_next = false
end)
end)
And, of course, an example of the result :D