Skip to content

Commit

Permalink
fix extmark-adjustments for nested snippets.
Browse files Browse the repository at this point in the history
now parents update children, and children parents.
  • Loading branch information
L3MON4D3 committed Oct 4, 2023
1 parent d0d43d9 commit 870eb1a
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 60 deletions.
44 changes: 43 additions & 1 deletion lua/luasnip/nodes/insertNode.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
}
26 changes: 8 additions & 18 deletions lua/luasnip/nodes/node.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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)

Expand All @@ -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
)
Expand Down
58 changes: 18 additions & 40 deletions lua/luasnip/nodes/snippet.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
53 changes: 52 additions & 1 deletion lua/luasnip/nodes/util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}

Expand Down Expand Up @@ -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,
Expand All @@ -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,
}
43 changes: 43 additions & 0 deletions tests/integration/snippet_basics_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)

0 comments on commit 870eb1a

Please sign in to comment.