diff --git a/lua/luasnip/nodes/insertNode.lua b/lua/luasnip/nodes/insertNode.lua index bd1cae1f4..fc77e756d 100644 --- a/lua/luasnip/nodes/insertNode.lua +++ b/lua/luasnip/nodes/insertNode.lua @@ -2,9 +2,9 @@ local Node = require("luasnip.nodes.node") local InsertNode = Node.Node:new() local ExitNode = InsertNode:new() local util = require("luasnip.util.util") +local node_util = require("luasnip.nodes.util") local types = require("luasnip.util.types") local events = require("luasnip.util.events") -local session = require("luasnip.session") local extend_decorator = require("luasnip.util.extend_decorator") local function I(pos, static_text, opts) @@ -279,6 +279,48 @@ function InsertNode:is_interactive() return true end +function InsertNode:child_snippets() + local own_child_snippets = {} + for _, child_snippet in ipairs(self.parent.snippet.child_snippets) do + if child_snippet.parent_node == self then + table.insert(own_child_snippets, child_snippet) + end + end + return own_child_snippets +end + +function InsertNode:subtree_set_pos_rgrav(pos, direction, rgrav) + self.mark:set_rgrav(-direction, rgrav) + + local own_child_snippets = self:child_snippets() + + local child_from_indx + if direction == 1 then + child_from_indx = 1 + else + child_from_indx = #own_child_snippets + end + + node_util.nodelist_adjust_rgravs( + own_child_snippets, + child_from_indx, + pos, + direction, + rgrav, + -- don't assume that the child-snippets are all adjacent. + false) +end + +function InsertNode:subtree_set_rgrav(rgrav) + self.mark:set_rgravs(rgrav, rgrav) + + local own_child_snippets = self:child_snippets() + + for _, child_snippet in ipairs(own_child_snippets) do + child_snippet:subtree_set_rgrav(rgrav) + end +end + return { I = I, } diff --git a/lua/luasnip/nodes/node.lua b/lua/luasnip/nodes/node.lua index 85e1f981a..700696ca4 100644 --- a/lua/luasnip/nodes/node.lua +++ b/lua/luasnip/nodes/node.lua @@ -458,6 +458,9 @@ function Node:get_buf_position(opts) end end +-- only does something for insert- and snippetNode. +function Node:set_sibling_rgravs(_, _, _, _) end + -- when an insertNode receives text, its mark/region should contain all the -- text that is inserted. -- This can be achieved by setting the left and right "right-gravity"(rgrav) of @@ -508,15 +511,8 @@ end -- Maybe this whole procedure could be sped up further if we can assume that -- identical endpoints imply identical rgravs. local function focus_node(self, lrgrav, rrgrav) - local abs_pos = vim.deepcopy(self.absolute_position) - -- find nodes on path from self to root. - local nodes_path = node_util.get_nodes_between(self.parent.snippet, self) - -- nodes_on_path_to_self does not include the outer snippet, insert it here - -- (and also insert some dummy-value in abs_pos, such that abs_pos[i] the - -- position of node_path[i] in node_path[i-1] is). - table.insert(nodes_path, 1, self.parent.snippet) - table.insert(abs_pos, 1, 0) + local nodes_path = node_util.root_path(self) -- direction is the direction away from this node, towards the outside of -- the tree-representation of the snippet. @@ -528,7 +524,7 @@ local function focus_node(self, lrgrav, rrgrav) -- adjust left rgrav of all nodes on path upwards to root/snippet: -- (i st. self and the snippet are both handled) - for i = #abs_pos, 1, -1 do + for i = 1, #nodes_path do local node = nodes_path[i] local node_direction_endpoint = node.mark:get_endpoint(direction) @@ -549,17 +545,11 @@ local function focus_node(self, lrgrav, rrgrav) -- dynamicNode, for example, the generated snippets parent is not the -- dynamicNode, but its parent). -- also: don't need to check for nil, because the - local node_above = nodes_path[i - 1] - if - node_above - and ( - node_above.type == types.snippetNode - or node_above.type == types.snippet - ) - then + local node_above = nodes_path[i+1] + if node_above then node_above:set_sibling_rgravs( + node, self_direction_endpoint, - abs_pos[i], direction, direction_rgrav ) diff --git a/lua/luasnip/nodes/snippet.lua b/lua/luasnip/nodes/snippet.lua index 72a3f808f..230716d1f 100644 --- a/lua/luasnip/nodes/snippet.lua +++ b/lua/luasnip/nodes/snippet.lua @@ -1297,53 +1297,24 @@ function Snippet:get_keyed_node(key) return self.dependents_dict:get({ "key", key, "node" }) end --- assumption: direction-endpoint of node at child_from_indx is on child_endpoint. --- (caller responsible) -local function adjust_children_rgravs( - self, - child_endpoint, - child_from_indx, - direction, - rgrav -) - local i = child_from_indx - local node = self.nodes[i] - while node do - local direction_node_endpoint = node.mark:get_endpoint(direction) - if util.pos_equal(direction_node_endpoint, child_endpoint) then - -- both endpoints of node are on top of child_endpoint (we wouldn't - -- be in the loop with `node` if the -direction-endpoint didn't - -- match), so update rgravs of the entire subtree to match rgrav - node:subtree_set_rgrav(rgrav) - else - -- only the -direction-endpoint matches child_endpoint, adjust its - -- position and break the loop (don't need to look at any other - -- siblings). - node:subtree_set_pos_rgrav(child_endpoint, direction, rgrav) - break - end - - i = i + direction - node = self.nodes[i] - end -end - -- adjust rgrav of nodes left (direction=-1) or right (direction=1) of node at -- child_indx. -- (direction is the direction into which is searched, from child_indx outward) +-- assumption: direction-endpoint of node is on child_endpoint. (caller +-- responsible) function Snippet:set_sibling_rgravs( + node, child_endpoint, - child_indx, direction, - rgrav -) - adjust_children_rgravs( - self, + rgrav ) + + node_util.nodelist_adjust_rgravs( + self.nodes, + node.absolute_position[#node.absolute_position] + direction, child_endpoint, - child_indx + direction, direction, - rgrav - ) + rgrav, + true) end -- called only if the "-direction"-endpoint has to be changed, but the @@ -1357,7 +1328,14 @@ function Snippet:subtree_set_pos_rgrav(pos, direction, rgrav) else child_from_indx = #self.nodes end - adjust_children_rgravs(self, pos, child_from_indx, direction, rgrav) + + node_util.nodelist_adjust_rgravs( + self.nodes, + child_from_indx, + pos, + direction, + rgrav, + true) end -- changes rgrav of all nodes and all endpoints in this snippetNode to `rgrav`. function Snippet:subtree_set_rgrav(rgrav) diff --git a/lua/luasnip/nodes/util.lua b/lua/luasnip/nodes/util.lua index 81e752f75..a9a06a580 100644 --- a/lua/luasnip/nodes/util.lua +++ b/lua/luasnip/nodes/util.lua @@ -63,6 +63,7 @@ local function wrap_args(args) end end +-- includes child, does not include parent. local function get_nodes_between(parent, child) local nodes = {} @@ -632,6 +633,54 @@ local function snippettree_find_undamaged_node(pos, opts) return prev_parent, prev_parent_children, child_indx, node end +local function root_path(node) + local path = {} + + while node do + local node_snippet = node.parent.snippet + local snippet_node_path = get_nodes_between(node_snippet, node) + -- get_nodes_between gives parent -> node, but we need + -- node -> parent => insert back to front. + for i = #snippet_node_path, 1, -1 do + table.insert(path, snippet_node_path[i]) + end + -- parent not in get_nodes_between. + table.insert(path, node_snippet) + + node = node_snippet.parent_node + end + + return path +end + +-- adjust rgravs of siblings of the node with indx child_from_indx in nodes. +local function nodelist_adjust_rgravs(nodes, child_from_indx, child_endpoint, direction, rgrav, nodes_adjacent) + -- only handle siblings, not the node with child_from_indx itself. + local i = child_from_indx + local node = nodes[i] + while node do + local direction_node_endpoint = node.mark:get_endpoint(direction) + if util.pos_equal(direction_node_endpoint, child_endpoint) then + -- both endpoints of node are on top of child_endpoint (we wouldn't + -- be in the loop with `node` if the -direction-endpoint didn't + -- match), so update rgravs of the entire subtree to match rgrav + node:subtree_set_rgrav(rgrav) + else + -- either assume that they are adjacent, or check. + if nodes_adjacent or util.pos_equal(node.mark:get_endpoint(-direction), child_endpoint) then + -- only the -direction-endpoint matches child_endpoint, adjust its + -- position and break the loop (don't need to look at any other + -- siblings). + node:subtree_set_pos_rgrav(child_endpoint, direction, rgrav) + end + break + end + + i = i + direction + node = nodes[i] + end +end + return { subsnip_init_children = subsnip_init_children, init_child_positions_func = init_child_positions_func, @@ -651,5 +700,7 @@ return { refocus = refocus, generic_extmarks_valid = generic_extmarks_valid, snippettree_find_undamaged_node = snippettree_find_undamaged_node, - interactive_node = interactive_node + interactive_node = interactive_node, + root_path = root_path, + nodelist_adjust_rgravs = nodelist_adjust_rgravs, } diff --git a/tests/integration/snippet_basics_spec.lua b/tests/integration/snippet_basics_spec.lua index b260be202..d27dacd5b 100644 --- a/tests/integration/snippet_basics_spec.lua +++ b/tests/integration/snippet_basics_spec.lua @@ -1274,4 +1274,47 @@ describe("snippets_basic", function() | {2:-- SELECT --} |]]} end) + + it("focus correctly adjusts gravities of parent-snippets.", function() + exec_lua[[ + ls.setup{ + link_children = true + } + ]] + exec_lua([[ls.lsp_expand("a$1$1a")]]) + exec_lua([[ls.lsp_expand("b$1")]]) + feed("ccc") + exec_lua([[ls.active_update_dependents()]]) + feed("dddd") + -- Here's how this fails if `focus` does not behave correctly (ie. only + -- adjusts extmarks in the snippet the current node is inside): + -- child has a changed $1, triggers update of own snippets, and + -- transitively of the parent-$1. + -- Since the parent has a functionNode that copies the $1's text, it + -- has to first focus the fNode, and update the text. This shifts the + -- gravity of the end of the parent-$1-extmark to the left. + -- Here the first failure may occur: if the child-extmark is not + -- adjusted as well, it will contain the text that belongs to the + -- functionNode. + -- The second issue that may occur is a bit more subtle: + -- After the whole update procedure is done, we have to refocus the + -- current node (since we have to assume that the update changed focus + -- s.t. the current node no longer has correct extmarks). + -- If, in doing this, the parent-$1-extmark end-gravity is not restored + -- to the right, the child-snippet will extend beyond the extmark of + -- its parent-node, the parent-$1. + exec_lua[[ls.jump(-1) ls.jump(-1)]] + -- highlights outer $1. + exec_lua[[ls.jump(1)]] + screen:expect{grid=[[ + a^b{3:cccdddd}bcccdddda | + {0:~ }| + {2:-- SELECT --} |]]} + -- and then inner $1. + exec_lua[[ls.jump(1)]] + screen:expect{grid=[[ + ab^c{3:ccdddd}bcccdddda | + {0:~ }| + {2:-- SELECT --} |]]} + end) end)