LuaSnip icon indicating copy to clipboard operation
LuaSnip copied to clipboard

Feature Request: Multiple Triggers for the same snippet

Open ziontee113 opened this issue 3 years ago • 11 comments

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 🤝

ziontee113 avatar May 06 '22 01:05 ziontee113

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.

L3MON4D3 avatar May 06 '22 06:05 L3MON4D3

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.

ziontee113 avatar May 06 '22 14:05 ziontee113

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

L3MON4D3 avatar May 06 '22 17:05 L3MON4D3

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 👍

ziontee113 avatar May 06 '22 22:05 ziontee113

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.

L3MON4D3 avatar May 07 '22 14:05 L3MON4D3

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.

ziontee113 avatar May 07 '22 14:05 ziontee113

Right, we do some initialization upon snippet-construction, not just on expansion :facepalm: Okay thank you, that would've made a few problems :D

L3MON4D3 avatar May 07 '22 14:05 L3MON4D3

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

L3MON4D3 avatar May 07 '22 14:05 L3MON4D3

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()
)

leiserfg avatar May 07 '22 16:05 leiserfg

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.

ziontee113 avatar May 07 '22 18:05 ziontee113

A deepcopy may not work well with functions.

leiserfg avatar May 07 '22 20:05 leiserfg

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{}"})
})

wintermute-cell avatar Dec 18 '22 18:12 wintermute-cell

When you talk about deep copy, do you mean just vim.deepcopy or is the copy functionality luasnip has?

danielo515 avatar Jan 02 '23 08:01 danielo515

Both should work, but to be safe prefer snippetNode:copy

L3MON4D3 avatar Jan 02 '23 10:01 L3MON4D3

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.

atticus-sullivan avatar Feb 14 '23 16:02 atticus-sullivan

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.

L3MON4D3 avatar Feb 15 '23 10:02 L3MON4D3

Okay, I actually implemented my ideas, #774

L3MON4D3 avatar Feb 15 '23 21:02 L3MON4D3

@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).

leiserfg avatar Feb 15 '23 22:02 leiserfg

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

L3MON4D3 avatar Feb 16 '23 06:02 L3MON4D3

Okay, #774 is pretty much ready now :D I'd appreciate some feedback on its api, can still iron out any inconveniences :)

L3MON4D3 avatar Mar 06 '23 17:03 L3MON4D3