Skip to content

Commit

Permalink
feat: define callbacks in node-opts.
Browse files Browse the repository at this point in the history
This circumvents the much more complicated current mechanism of setting
the callback by identifying the node via jump-index in the parent.
  • Loading branch information
L3MON4D3 committed Feb 14, 2024
1 parent 81d83ec commit e65f3e0
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 16 deletions.
14 changes: 10 additions & 4 deletions DOC.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,11 @@ Common opts:
point in time, the snippet may contain each key only once. This means it is
fine to return a keyed node from a `dynamicNode`, because even if it will be
generated multiple times, those will not be valid at the same time.
* `node_callbacks`: Define event-callbacks for this node (see
[events](#events)).
Accepts a table that maps an event, e.g. `events.enter` to the callback
(essentially the same as `callbacks` passed to `s`, only that there is no
first mapping from jump-index to the table of callbacks).

## Api

Expand Down Expand Up @@ -3350,14 +3355,15 @@ The cache is located at `stdpath("cache")/luasnip/docstrings.json` (probably
# Events

Events can be used to react to some action inside snippets. These callbacks can
be defined per snippet (`callbacks`-key in snippet constructor) or globally
(autocommand).
be defined per snippet (`callbacks`-key in snippet constructor), per-node by
passing them as `node_callbacks` in `node_opts`, or globally (autocommand).

`callbacks`: `fn(node[, event_args]) -> event_res`
All callbacks get the `node` associated with the event and event-specific
All callbacks receive the `node` associated with the event and event-specific
optional arguments, `event_args`.
`event_res` is only used in one event, `pre_expand`, where some properties of
the snippet can be changed.
the snippet can be changed. If multiple callbacks return `event_res`, we only
guarantee that one of them will be effective, not all of them.

`autocommand`:
Luasnip uses `User`-events. Autocommands for these can be registered using
Expand Down
13 changes: 9 additions & 4 deletions lua/luasnip/nodes/node.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
local session = require("luasnip.session")
local util = require("luasnip.util.util")
local node_util = require("luasnip.util.util")
local node_util = require("luasnip.nodes.util")
local ext_util = require("luasnip.util.ext_opts")
local events = require("luasnip.util.events")
Expand Down Expand Up @@ -276,15 +275,21 @@ function Node:init_insert_positions(position_so_far)
end

function Node:event(event)
local node_callback = self.node_callbacks[event]
if node_callback then
node_callback(self)
end

-- try to get the callback from the parent.
if self.pos then
-- node needs position to get callback (nodes may not have position if
-- defined in a choiceNode, ie. c(1, {
-- i(nil, {"works!"})
-- }))
-- works just fine.
local callback = self.parent.callbacks[self.pos][event]
if callback then
callback(self)
local parent_callback = self.parent.callbacks[self.pos][event]
if parent_callback then
parent_callback(self)
end
end

Expand Down
29 changes: 21 additions & 8 deletions lua/luasnip/nodes/snippet.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1176,16 +1176,29 @@ function Snippet:text_only()
end

function Snippet:event(event, event_args)
local callback = self.callbacks[-1][event]
local cb_res
if callback then
cb_res = callback(self, event_args)
-- there are 3 sources of a callback, for a snippetNode:
-- self.callbacks[-1], self.node_callbacks, and parent.callbacks[self.pos].
local m1_cb, cb, parent_cb
-- since we handle pre-expand callbacks here, we need to handle the
-- event_res, which may be returned by more than one callback.
-- In order to keep it simple, we just return any non-nil result.
local m1_cb_res, cb_res, parent_cb_res

m1_cb = self.callbacks[-1][event]
if m1_cb then
m1_cb_res = m1_cb(self, event_args)
end

cb = self.node_callbacks[event]
if cb then
cb_res = cb(self, event_args)
end

if self.type == types.snippetNode and self.pos then
-- if snippetNode, also do callback for position in parent.
callback = self.parent.callbacks[self.pos][event]
if callback then
callback(self)
parent_cb = self.parent.callbacks[self.pos][event]
if parent_cb then
parent_cb_res = parent_cb(self)
end
end

Expand All @@ -1196,7 +1209,7 @@ function Snippet:event(event, event_args)
modeline = false,
})

return cb_res
return vim.F.if_nil(cb_res, m1_cb_res, parent_cb_res)
end

local function nodes_from_pattern(pattern)
Expand Down
2 changes: 2 additions & 0 deletions lua/luasnip/nodes/util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ local function init_node_opts(opts)

in_node.key = opts.key

in_node.node_callbacks = opts.node_callbacks or {}

return in_node
end

Expand Down
46 changes: 46 additions & 0 deletions tests/integration/snippet_basics_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1446,4 +1446,50 @@ describe("snippets_basic", function()
exec_lua([[ls.snip_expand(snip)]])
assert.are.same(2, exec_lua("return counter"))
end)

it("node-callbacks are executed correctly.", function()
exec_lua[[
enter_qwer = false
enter_qwer_via_parent = false
snip = s("foo", {
t"asdf", i(1, "qwer", {node_callbacks = {[events.enter] = function()
enter_qwer = true
end}})
}, {callbacks = {[1] = { [events.enter] = function()
enter_qwer_via_parent = true
end}} } )
ls.snip_expand(snip)
]]

assert.are.same(true, exec_lua("return enter_qwer"))
assert.are.same(true, exec_lua("return enter_qwer_via_parent"))

exec_lua[[
enter_snode = false
enter_snode_m1 = false
enter_snode_via_parent = false
snip = s("foo", {
sn(1, {t"qwer"}, {
node_callbacks = {[events.enter] = function()
enter_snode = true
end},
callbacks = { [-1] = {
[events.enter] = function()
enter_snode_m1 = true
end }}
} )
}, {callbacks = {[1] = { [events.enter] = function()
enter_snode_via_parent = true
end}}, } )
ls.snip_expand(snip)
]]

assert.are.same(true, exec_lua("return enter_snode"))
assert.are.same(true, exec_lua("return enter_snode_m1"))
assert.are.same(true, exec_lua("return enter_snode_via_parent"))
end)
end)

0 comments on commit e65f3e0

Please sign in to comment.