Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow callbacks via node-options. #1092

Merged
merged 3 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
21 changes: 14 additions & 7 deletions doc/luasnip.txt
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,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
|luasnip-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 *luasnip-node-api*
Expand Down Expand Up @@ -3206,13 +3211,15 @@ The cache is located at `stdpath("cache")/luasnip/docstrings.json` (probably
25. Events *luasnip-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).

`callbacks`: `fn(node[, event_args]) -> event_res` All callbacks get 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.
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 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. 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)