diff --git a/DOC.md b/DOC.md index bffc719cb..ba4b449fc 100644 --- a/DOC.md +++ b/DOC.md @@ -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 @@ -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 diff --git a/lua/luasnip/nodes/node.lua b/lua/luasnip/nodes/node.lua index e98654fce..ad41c9232 100644 --- a/lua/luasnip/nodes/node.lua +++ b/lua/luasnip/nodes/node.lua @@ -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") @@ -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 diff --git a/lua/luasnip/nodes/snippet.lua b/lua/luasnip/nodes/snippet.lua index 11bcf91a1..a0be2fb99 100644 --- a/lua/luasnip/nodes/snippet.lua +++ b/lua/luasnip/nodes/snippet.lua @@ -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 @@ -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) diff --git a/lua/luasnip/nodes/util.lua b/lua/luasnip/nodes/util.lua index 6c9f49ad9..53d517b75 100644 --- a/lua/luasnip/nodes/util.lua +++ b/lua/luasnip/nodes/util.lua @@ -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 diff --git a/tests/integration/snippet_basics_spec.lua b/tests/integration/snippet_basics_spec.lua index d37fe68c4..1bb66b8e3 100644 --- a/tests/integration/snippet_basics_spec.lua +++ b/tests/integration/snippet_basics_spec.lua @@ -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)