LuaSnip icon indicating copy to clipboard operation
LuaSnip copied to clipboard

Expose generic conditions for use in snippets

Open bew opened this issue 2 years ago • 6 comments

Hello,

I'd like to make a snippet with an insert node that defaults to env.LS_SELECT_RAW when luasnip has a selection 'in store', or something else if not.

Grepping the docs shows there is a require"luasnip.extras.conditions.show".has_selected_text 'function' I could use to check if there is a selection available, but it feels weird to use something from a module mentioning show (as if it was specific to show conditions). I would prefer importing something from luasnip.extras.conditions (generic condition objects), or have a handy simple function (not a condition object) for use in snippets. Could also be used in statusline segment to put an indicator when there is a luasnip text selection available

bew avatar Oct 04 '23 23:10 bew

When implementing these condition 'functions'/objects, we started out with luasnip.extras.conditions. The issue is that currently we have two different places where it makes sense to use these conditions, show_condition and condition (relevant for if the snippet can be expanded), which do not share the same API. Therefore we split up the module in conditions.expand and conditions.show to reflect that difference (... .expand contains everything from ... .show as the API is a superset of the show_condition API).

So the naming most probably won't change for this.

Tbh I'm still not sure what exactly your issue is (is it just the naming/organization in modules?). In effect the condition objects from luasnip.extras.conditions.{show,expand} are just simple tables {func = function(...) ... end} (with a special metatable attached). So if you really need to, you could try accessing the underlying function via require"luasnip.extras.conditions.show".has_selected_text.func. But be aware that the contents of that table is kinda an internal representation and might change in the future (although currently I see no reason why it should).

atticus-sullivan avatar Oct 05 '23 08:10 atticus-sullivan

I'd like to make a snippet with an insert node that defaults to env.LS_SELECT_RAW when luasnip has a selection 'in store', or something else if not.

Sorry, completely forgot to answer here, with all the other stuff going on :sweat_smile: You can use a dynamicNode and check if the select-variables contain text. For example:

d(1, function(_, parent)
	if #parent.snippet.env.LS_SELECT_DEDENT ~= 0 then
		return sn(nil, {
			t(parent.snippet.env.LS_SELECT_DEDENT)
		})
	else
		return sn(nil, { i(1) })
	end
end

L3MON4D3 avatar Oct 05 '23 09:10 L3MON4D3

Tbh I'm still not sure what exactly your issue is

I think @bew just thought that the condition-function is the only way to check whether there is a selection

L3MON4D3 avatar Oct 05 '23 09:10 L3MON4D3

note: I actually noticed that using has_selected_text from the extra show conditions module always returns false when inside a snippet for some reason. If I instead check that snip.env.LS_SELECT_RAW isn't empty before using it that works correctly.

I think having a nice function would be better though, so my snippet code don't need to depend on what the default / empty value of LS_SELECT_RAW is when there are no selection.

bew avatar Oct 06 '23 23:10 bew

note: I actually noticed that using has_selected_text from the extra show conditions module always returns false when inside a snippet for some reason.

Oh, I'll check that out

I think having a nice function would be better though, so my snippet code don't need to depend on what the default / empty value of LS_SELECT_RAW is when there are no selection.

True, hmm.. I think we should add that, but I'm not sure where :D We don't yet have a module with functions for use inside of dynamic/functionNode, and I'm not sure where to put it/what to call it. extras/helpers, maybe

L3MON4D3 avatar Oct 07 '23 08:10 L3MON4D3

d(1, function(_, parent)
	if #parent.snippet.env.LS_SELECT_DEDENT ~= 0 then
		return sn(nil, {
			t(parent.snippet.env.LS_SELECT_DEDENT)
		})
	else
		return sn(nil, { i(1) })
	end
end

This workaround has the flaw that the inserted LS_SELECT_DEDENT won't be selected at the end. I am looking for something in the sense of "i(1, LS_SELECT_DEDENT)" but the above solution is basically

  • if LS_SELECT_DEDENT exists then t(LS_SELECT_DEDENT)
  • else i(1)

which is something different.

I'm migrating from UltiSnips and basically half my snippets use ${1:$VISUAL}}, which is a problem for me.

LnLcFlx avatar Mar 10 '24 17:03 LnLcFlx

fyi: With the rework of how selection/restore works from #1013 (last year), the way to check for this is now:

ACTUALLY THIS IS WRONG (see below)
function has_stored_selection()
  local success, _ = pcall(vim.api.nvim_buf_get_var, 0, "LUASNIP_SELECT_RAW")
  return success
end

Reference: https://github.com/L3MON4D3/LuaSnip/blob/33b06d72d220aa56a7ce80a0dd6f06c70cd82b9d/lua/luasnip/util/select.lua#L8

bew avatar Dec 15 '24 07:12 bew

Actually I've been working with this recently as well. With this comment you made me remember/reread this issue again.

Currently I'm using this function

local function visual_selection(_, parent, _, uarg)
	if #parent.snippet.env.LS_SELECT_RAW > 0 then
		return sn(nil, {t(parent.snippet.env.LS_SELECT_RAW)})
	else
		return sn(nil, {i(unpack(uarg.i))})
	end
end

for example as follows

		stop = isn(2, {d(1, visual_selection, nil, {user_args={{i={1}}}})}, "$PARENT_INDENT\t"),

while it might be worth adding some common function for dynamic nodes as some sort of utils library, I'm not sure if we can generalize them enough (the function above for example also is just for a specific usecase even though one can adjust the insertNode arbitrarily) so that it makes sense to ship those functions to every user. Also if we manage to generalize this, it might be not really easier to use compared to writing own functions for dynamicNodes (after all writing a custom function is peak generalization)

atticus-sullivan avatar Dec 15 '24 11:12 atticus-sullivan

Actually the code I posted above is wrong 👀😬. Since the luasnip impl will retrieve the selection from buffer vars and immediately after clear those variable, it's no longer possible to check for that buffer variable during the snippet execution (e.g. in a dynamic node).

I think the only reliable way right now is still to check:

    if snip.env.LS_SELECT_RAW and #snip.env.LS_SELECT_RAW > 0 then
      -- ...
    end

bew avatar Dec 16 '24 00:12 bew