LuaSnip
LuaSnip copied to clipboard
Feature Request: Multiple Triggers for the same snippet
Hi, this Feature Request comes from the issue with Lua's Regex: It doesn't have the or | pattern.
If we want to have multiple triggers for the same snippet, we would save the snippet body in a variable, then create new s() snippets with different triggers, then deepcopy the snippet body.
I'm proposing a solution that looks a little something like:
s({ "augroup", "aug" } , { t"This is Augroup Snippet" }) --> If table has only strings in it
s({ { trig = "augroup" }, { trig = "aug", hidden = true } } , { t"This is Augroup Snippet" }) --> If table has multiple tables in it
s({ "augroup", { trig = "aug", hidden = true } } , { t"This is Augroup Snippet" }) --> Hybrid usecase
What do you think of this approach? Do we have a good solution for this problem already? Thank you 🤝
Oh this is interesting, especially allowing different triggers to have different attributes (wordTrig, hidden). A simpler, although less versatile (but also complementary) idea would be to allow vim-regex as triggers. That would come with the added benefit that other more complex triggers can also be expressed. Nevertheless, I'm intrigued by your idea. I'm not yet sure having separate sets of parameters is actually useful (well, besides multiple triggers), but it sure seems like a cool feature.
I'm not yet sure having separate sets of parameters is actually useful
Yeah the only usefulness in having separate sets of parameters is to streamline the snippet creation process and less visual clutter for CMP if you want multiple triggers for the same snippet.
Ahhhh now I get the hidden, I thought it was just an example to show that other parameters should also be accessible, but it was actually necessary for your usecase.
That's nice, then it actually is useful :D
I'll see when I get to implementing it, before I do there are a few breaking changes relating to that table I'd like to get out of the way, so it might be a bit.
One more thing, I don't think you need to deepcopy these snippet-bodies, if two snippets (not snippetNodes!) refer to the same nodes there should be no problem
I don't think you need to deepcopy these snippet-bodies,
Yeah about snippet bodies as variables, I just use deepcopy just to be safe. Sometimes it's fine not having deepcody and other times it just throw a bunch of errors when you try to expand the snippet.
Take your time man 👍
Ouh, do you have an example where deepcopy is necessary? I'm pretty sure not copying should work (and I'd like to make use of that in an eventual implementation), so if copy is necessary in some cases, I might have to take a sharper look at some parts of the code.
Hi, I have some examples for you, I'm sorry for the big tabs idk how to make them smaller in github.
Example 1:
local sample_node = t("hello cool")
s( -- Test
"test",
fmt(
[[
test xxx {} yyy
]],
{
sample_node,
}
)
)
s( -- Test2
"test2",
fmt(
[[
test2 xxx {} yyy
]],
{
sample_node,
}
)
)
Behavior: After saving the snippet file, I can trigger the "test2" snippet, but when I try to trigger the "test" snippet, it errors:
E5108: Error executing lua: ...ite/pack/packer/start/LuaSnip/lua/luasnip/nodes/node.lua:325: attempt to index field 'effective_child_ext_opts' (a nil value)
stack traceback:
...ite/pack/packer/start/LuaSnip/lua/luasnip/nodes/node.lua:325: in function 'resolve_node_ext_opts'
...ite/pack/packer/start/LuaSnip/lua/luasnip/nodes/util.lua:11: in function 'subsnip_init_children'
.../pack/packer/start/LuaSnip/lua/luasnip/nodes/snippet.lua:797: in function 'subsnip_init'
.../pack/packer/start/LuaSnip/lua/luasnip/nodes/snippet.lua:412: in function 'trigger_expand'
...nvim/site/pack/packer/start/LuaSnip/lua/luasnip/init.lua:171: in function 'snip_expand'
...nvim/site/pack/packer/start/LuaSnip/lua/luasnip/init.lua:244: in function 'expand'
/home/ubur/.config/nvim/lua/user/luasnip-config/init.lua:36: in function </home/ubur/.config/nvim/lua/user/luasnip-config/init.lua:34>
Example 2:
local sample_snippet_body = t("snippet body")
s( -- Second Example
"first_trigger",
sample_snippet_body
)
s( -- Second Example
"second_trigger",
sample_snippet_body
)
Also got the error on the "first_trigger":
Error executing vim.schedule lua callback: ...ite/pack/packer/start/LuaSnip/lua/luasnip/nodes/node.lua:325: attempt to index field 'effective_child_ext_opts' (a nil value)
stack traceback:
...ite/pack/packer/start/LuaSnip/lua/luasnip/nodes/node.lua:325: in function 'resolve_node_ext_opts'
...ite/pack/packer/start/LuaSnip/lua/luasnip/nodes/util.lua:11: in function 'subsnip_init_children'
.../pack/packer/start/LuaSnip/lua/luasnip/nodes/snippet.lua:797: in function 'subsnip_init'
.../pack/packer/start/LuaSnip/lua/luasnip/nodes/snippet.lua:679: in function 'fake_expand'
.../pack/packer/start/LuaSnip/lua/luasnip/nodes/snippet.lua:737: in function 'get_docstring'
...e/pack/packer/start/cmp_luasnip/lua/cmp_luasnip/init.lua:35: in function 'get_documentation'
...e/pack/packer/start/cmp_luasnip/lua/cmp_luasnip/init.lua:119: in function 'resolve'
.../nvim/site/pack/packer/start/nvim-cmp/lua/cmp/source.lua:348: in function 'resolve'
...e/nvim/site/pack/packer/start/nvim-cmp/lua/cmp/entry.lua:459: in function 'resolve'
...re/nvim/site/pack/packer/start/nvim-cmp/lua/cmp/view.lua:225: in function 'fn'
.../site/pack/packer/start/nvim-cmp/lua/cmp/utils/async.lua:41: in function <.../site/pack/packer/start/nvim-cmp/lua/cmp/utils/async.lua:39>
With deepcopy wrap around those variables, there is no error.
Right, we do some initialization upon snippet-construction, not just on expansion :facepalm: Okay thank you, that would've made a few problems :D
Although just conceptually, it should be possible to make it work with just one copy of the snippet. I'll have to look into what exactly is causing the error
I think it's easier just to make your own small snippet factory, like
local function sample_snippet_body () return t("snippet body"), i(1, "something") end
s( -- Second Example
"first_trigger",
sample_snippet_body()
)
s( -- Second Example
"second_trigger",
sample_snippet_body()
)
Well if you have to create a new object anyway then why don't just deepcopy it? Wrapping a function around the snippet body adds so much more visual clutter to the code.
A deepcopy may not work well with functions.
so far this solution worked well for me, using a deepcopy:
local function new_snippet(filetype, snip_name, triggers, snippet)
for _, trigger in pairs(triggers) do
ls.add_snippets(filetype, {
s({trig = trigger, name = snip_name}, vim.deepcopy(snippet))
})
end
end
-- example usage
new_snippet("tex", "glqq", {"glqq", "\\glqq"}, {
t({"\\glqq "}), i(1), t({"\\grqq{}"})
})
When you talk about deep copy, do you mean just vim.deepcopy or is the copy functionality luasnip has?
Both should work, but to be safe prefer snippetNode:copy
Anyone working on this right now? If not, I'd do this one (might be a few weeks tough).
before I do there are a few breaking changes relating to that table I'd like to get out of the way
What were those? (for one part I'm curios, for the other part it might be interesting/important for the implementation)
And we need to think about https://github.com/L3MON4D3/LuaSnip/issues/637#issuecomment-1288015494 if we want to do this in the same fashion.
Ouh, I made a bit of progress on that front a few weeks back (mostly re-familiarizing myself with that part of the code, and thinking about how to best do this), now I just have to actually implement it 😅
Regarding the breaking changes, I made a discussion on those a while back, #425, and I think those won't happen.
For that comment, good that you remind me: initially only allowing conditions seems like the right way of going about this, since its implementation will be much cleaner than that accomodating all arguments.
Okay, I actually implemented my ideas, #774
@L3MON4D3 if ms returns a list of real snippets (by acting as a factory with multiple clones) one could do just
ls.add_snippets("all", --- notice here I'm not wrapping this in a table
ms({
{ trig = "a", condition = function(ltc) return ltc ~= "a" end, snippetType = "autosnippet" },
{ trig = "b", condition = function(ltc) return ltc ~= "b" end },
"c"
}, { t "a or b" }, { key = "ab" })
)
With the advantage of not having to touch the internals (as the result are real snippets).
That's true, but I think to really make this work, they should behave like regular snippets. Yes, it is more complicated, but I feel like it is much more elegant too, since we don't really need multiple copies of that snippet.
Also, to really make them comfortable to use, it should be okay to return them from the lua-loader, and for that to work if ms returns a list, we'd always need a vim.tbl_extend, which seems a bit annoying
Okay, #774 is pretty much ready now :D I'd appreciate some feedback on its api, can still iron out any inconveniences :)