LuaSnip icon indicating copy to clipboard operation
LuaSnip copied to clipboard

Make it easy to create functions that override the defaults of functions.

Open L3MON4D3 opened this issue 3 years ago • 14 comments

Sounds a bit cryptic, the general idea is to allow stuff like

-- this somewhere above: local override_fn = require("luasnip.extras.override_fn")
local fmts = override_fn.fmt({delimiters = "[]"})

This makes it easy to create functions with different defaults, like above for fmt, or maybe a ls.snippet-function that always sets regTrig or some combination of parameters.

This needs some more work:

  • [ ] snippet does not really work yet, if the first arg is a string, we're in trouble.
  • [ ] add more functions beside fmt
  • [ ] think about exposing and documenting the generic function (override_any, currently) so it can be used to craft yet more of these override-functions (although that is really quick)
  • [ ] think of a better name, override_fn isn't very self-explanatory

L3MON4D3 avatar Aug 17 '22 12:08 L3MON4D3

WTYT if instead of the override_fn.func api you just expose override_with(func, opt_overrides) in luasnip.utils or in luasnip.extras? The same function could be used for creating auto-expand snippets by wrapping add_snippets with the auto_expand (is that the name?).

leiserfg avatar Aug 17 '22 12:08 leiserfg

The issue with that is that we don't know which position the opts are in, or how many there are (think ls.s, one might like to override both the first or third parameter). So we'd best provide presets for common functions (like fmt, s, add_snippets), that's why I put that mechanism into its own module for now.

The same function could be used for creating auto-expand snippets by wrapping add_snippets with the auto_expand (is that the name?).

Yeah right, via type = "autosnippets"

L3MON4D3 avatar Aug 17 '22 13:08 L3MON4D3

Did a small rewrite, now it's possible to override the snippet-parameters. Also, extend instead of override would make more sense I think. Example:

ls.setup_snip_env()

local override_fn = require("luasnip.extras.override_fn")
local expand_conditions = require("luasnip.extras.expand_conditions")

local fmts = override_fn.fmt({delimiters = "[]"})
-- equivalent to ultisnips br (well, bp for pattern :D)
local br_snip = override_fn.snippet(
	{regTrig = true},
	{condition = expand_conditions.line_begin} )

ls.add_snippets("all", {
	br_snip("%d", {
		i(1, "asdf")
	}),
	s("trig", fmts("[]", {i(1, "asdf")} ) )
}, {
	key = "asd"
})

I think this is really sick, great idea @leiserfg!!

L3MON4D3 avatar Aug 17 '22 17:08 L3MON4D3

I was riding my :bike: and an idea came to my mind. What if we add a fun._opt_pos to every function that can be overridden, so you don't have to import everything inside of luasnip.extras.override_fn? That way you only expose override_fn and it uses ._opt_pos to know where to override. The decorated function should get the same `_opt_pos as the original one, that way a function can be wrapped more than once. The default value should be 2, maybe it can be also a 3rd parameter of the function, in case someone wanna override a function that is not shipped by luasnip.

leiserfg avatar Aug 18 '22 07:08 leiserfg

Mhmmm interesting idea :+1: I definitely like defining properties of the function (position of the opts-arg, how can it be overridden) close to the function, as opposed to in a completely different file.

To implement this, we'd have to turn all outward-facing functions into tables with __call, right? (Don't like this very much, although we could hide this behind a nice constructor, so at least the code doesn't look completely weird) (Can you think of a better approach for the implementation?)

Communicating which functions are overridable would be a bit harder this way, by putting all of the overridable functions into one module it's very clearly defined. (We don't have this problem if we make all functions overridable though xD) (OTOH, we can just return a readable error in the function that's responsible for overriding, yeeah that works)

Yeah, I think this is even better :+1::+1:

L3MON4D3 avatar Aug 18 '22 08:08 L3MON4D3

To implement this, we'd have to turn all outward-facing functions into tables with __call, right?

Crap, I forgot functions are not indexable in lua :face_exhaling:

leiserfg avatar Aug 18 '22 08:08 leiserfg

But also, it does not need to make every function a table, you can just expose a function to annotate where the opt goes, like so:

local opt_pos = setmetatable({}, {__mode = "kv"})
function M.overridable(fn, pos)  
   opt_pos[fn] = pos
   return fn
end

local function M.override(fn, opt, maybe_pos)
-- if maybe_pos use it other way check the table, if none is there, consider it's 2 or fail, the one you prefer
-- HERE THE MAGIC happens

-- call  `overridable` on the decorated function to set the same position as the used 
end

leiserfg avatar Aug 18 '22 08:08 leiserfg

To implement this, we'd have to turn all outward-facing functions into tables with __call, right?

Crap, I forgot functions are not indexable in lua :face_exhaling:

xD What language is that possible in?

But also, it does not need to make every function a table, you can just expose a function to annotate where the opt goes, like so:

Oh, yeah I like that approach :+1:

Sidenote: Making the keys weak is a nice touch, but I don't get why the values are weak too, aren't they only stored in this table?

L3MON4D3 avatar Aug 18 '22 10:08 L3MON4D3

xD What language is that possible in?

I use python 90% of the time :smile:, there you can.

leiserfg avatar Aug 18 '22 12:08 leiserfg

Oho, I use Python sometimes but have never seen that, cool

L3MON4D3 avatar Aug 18 '22 12:08 L3MON4D3

Making the keys weak is a nice touch, but I don't get why the values are weak too, aren't they only stored in this table?

Yes, kv is not needed, k is good enough.

leiserfg avatar Aug 18 '22 12:08 leiserfg

Okay, works pretty well, I've renamed everything to reflect that we're extending, not overriding the passed args (not sure what's easier to understand, overriding defaults or extending passed arguments...) Here's what usage looks like now:

ls.setup_snip_env()

local ed = require("luasnip.util.extend_decorator")
local expand_conditions = require("luasnip.extras.expand_conditions")

local fmts = ed.apply(fmt, {delimiters = "[]"})
-- can also apply to already-decorated functions.
local fmtd = ed.apply(require("luasnip.extras.fmt").fmta, {dedent = false})

-- equivalent to ultisnips br (well, bp for pattern :D)
local br_snip = ed.apply(s,
	{regTrig = true},
	{condition = expand_conditions.line_begin} )

ls.add_snippets("all", {
	br_snip("%d", {
		i(1, "asdf")
	}),
	s("trig", fmts("\t\t\t[]", {i(1, "asdf")} ) ),
	s("triga", fmtd("\t\t\t<>", {i(1, "asdf")} ) )
}, {
	key = "asd"
})

Also moved it to util, since we now rely on it internally. It's also pretty straightforward to register personal functions, I think we should properly expose this. I've decided not to set default-values for the opts-parameter, too easy to introduce subtle errors imo. Maybe I'll also remove the defaulting for the extend-function and just export the default-extend from the module, I think that's okay. Maybe export it from util, under a fitting name.

L3MON4D3 avatar Aug 18 '22 19:08 L3MON4D3

Yes, it makes sense to re-export it (as we did for the function to add environments) and only document that exposed part.

leiserfg avatar Aug 18 '22 20:08 leiserfg

Ahh, yeah, that file is so small it's fine to re-export :+1: Can you think of useful functions to extend? I guess

  • node-constructors (not thaaaaaaat useful, but one can more easily create highlighted nodes that way, like red_insertNode xD)
  • parse: once the parser-update is merged, it'll also have dedent and trim_empty like fmt, but default false to not mess up snippets from lsp. So overriding that is an easy target.
  • ???

will come in handy, but those are the only ones that come to mind directly.

L3MON4D3 avatar Aug 20 '22 12:08 L3MON4D3

@leiserfg Would you take a look?

L3MON4D3 avatar Aug 25 '22 14:08 L3MON4D3

@leiserfg Would you take a look?

Sure, tonight I will review it.

leiserfg avatar Aug 25 '22 16:08 leiserfg

Looks awesome :tada:

leiserfg avatar Aug 25 '22 16:08 leiserfg

Hell yeah, that's what I wanna hear :D Thank you!

L3MON4D3 avatar Aug 25 '22 19:08 L3MON4D3