neovim/test/unit/marktree_spec.lua

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

649 lines
19 KiB
Lua
Raw Normal View History

local t = require('test.unit.testutil')
local itp = t.gen_itp(it)
local ffi = t.ffi
local eq = t.eq
local ok = t.ok
local lib = t.cimport('./src/nvim/marktree.h')
local function tablelength(tbl)
local count = 0
for _ in pairs(tbl) do
count = count + 1
end
return count
end
local function pos_leq(a, b)
return a[1] < b[1] or (a[1] == b[1] and a[2] <= b[2])
end
-- Checks that shadow and tree is consistent, and optionally
-- return the order
local function shadoworder(tree, shadow, iter, giveorder)
ok(iter ~= nil)
local status = lib.marktree_itr_first(tree, iter)
local count = 0
local pos2id, id2pos = {}, {}
local last
if not status and next(shadow) == nil then
return pos2id, id2pos
end
repeat
local mark = lib.marktree_itr_current(iter)
local id = tonumber(mark.id)
local spos = shadow[id]
eq(mark.pos.row, spos[1], mark.id)
eq(mark.pos.col, spos[2], mark.id)
if lib.mt_right_test(mark) ~= spos[3] then
error('invalid gravity for ' .. id .. ':(' .. mark.pos.row .. ', ' .. mark.pos.col .. ')')
end
if count > 0 then
if not pos_leq(last, spos) then
error('DISORDER')
end
end
count = count + 1
last = spos
if giveorder then
pos2id[count] = id
id2pos[id] = count
end
until not lib.marktree_itr_next(tree, iter)
local shadowlen = tablelength(shadow)
if shadowlen ~= count then
error('missed some keys? (shadow ' .. shadowlen .. ', tree ' .. count .. ')')
end
return id2pos, pos2id
end
local function shadowsplice(shadow, start, old_extent, new_extent)
local old_end = {
start[1] + old_extent[1],
(old_extent[1] == 0 and start[2] or 0) + old_extent[2],
}
local new_end = {
start[1] + new_extent[1],
(new_extent[1] == 0 and start[2] or 0) + new_extent[2],
}
local delta = { new_end[1] - old_end[1], new_end[2] - old_end[2] }
for _, pos in pairs(shadow) do
if pos_leq(start, pos) then
if pos_leq(pos, old_end) then
-- delete region
if pos[3] then -- right gravity
pos[1], pos[2] = new_end[1], new_end[2]
else
pos[1], pos[2] = start[1], start[2]
end
else
if pos[1] == old_end[1] then
pos[2] = pos[2] + delta[2]
end
pos[1] = pos[1] + delta[1]
end
end
end
end
local function dosplice(tree, shadow, start, old, new)
lib.marktree_splice(tree, start[1], start[2], old[1], old[2], new[1], new[2])
shadowsplice(shadow, start, old, new)
end
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
local ns = 10
local last_id = nil
local function put(tree, row, col, gravity, end_row, end_col, end_gravity)
last_id = last_id + 1
local my_id = last_id
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
end_row = end_row or -1
end_col = end_col or -1
end_gravity = end_gravity or false
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
lib.marktree_put_test(tree, ns, my_id, row, col, gravity, end_row, end_col, end_gravity, false)
return my_id
end
local function put_meta(tree, row, col, gravitate, meta)
last_id = last_id + 1
local my_id = last_id
lib.marktree_put_test(tree, ns, my_id, row, col, gravitate, -1, -1, false, meta)
return my_id
end
describe('marktree', function()
before_each(function()
last_id = 0
end)
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
itp('works', function()
local tree = ffi.new('MarkTree[1]') -- zero initialized by luajit
local shadow = {}
local iter = ffi.new('MarkTreeIter[1]')
local iter2 = ffi.new('MarkTreeIter[1]')
for i = 1, 100 do
for j = 1, 100 do
local gravitate = (i % 2) > 0
local id = put(tree, j, i, gravitate)
ok(id > 0)
eq(nil, shadow[id])
shadow[id] = { j, i, gravitate }
end
-- checking every insert is too slow, but this is ok
lib.marktree_check(tree)
end
-- ss = lib.mt_inspect_rec(tree)
-- io.stdout:write(ffi.string(ss))
-- io.stdout:flush()
local id2pos, pos2id = shadoworder(tree, shadow, iter)
eq({}, pos2id) -- not set if not requested
eq({}, id2pos)
for i, ipos in pairs(shadow) do
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
local p = lib.marktree_lookup_ns(tree, ns, i, false, iter)
eq(ipos[1], p.pos.row)
eq(ipos[2], p.pos.col)
local k = lib.marktree_itr_current(iter)
eq(ipos[1], k.pos.row)
eq(ipos[2], k.pos.col, ipos[1])
lib.marktree_itr_next(tree, iter)
-- TODO(bfredl): use id2pos to check neighbour?
-- local k2 = lib.marktree_itr_current(iter)
end
for i, ipos in pairs(shadow) do
lib.marktree_itr_get(tree, ipos[1], ipos[2], iter)
local k = lib.marktree_itr_current(iter)
eq(i, tonumber(k.id))
eq(ipos[1], k.pos.row)
eq(ipos[2], k.pos.col)
end
ok(lib.marktree_itr_first(tree, iter))
local del = lib.marktree_itr_current(iter)
lib.marktree_del_itr(tree, iter, false)
shadow[tonumber(del.id)] = nil
shadoworder(tree, shadow, iter)
for _, ci in ipairs({ 0, -1, 1, -2, 2, -10, 10 }) do
for i = 1, 100 do
lib.marktree_itr_get(tree, i, 50 + ci, iter)
local k = lib.marktree_itr_current(iter)
local id = tonumber(k.id)
eq(shadow[id][1], k.pos.row)
eq(shadow[id][2], k.pos.col)
lib.marktree_del_itr(tree, iter, false)
shadow[id] = nil
end
lib.marktree_check(tree)
shadoworder(tree, shadow, iter)
end
-- NB: this is quite rudimentary. We rely on
-- functional tests exercising splicing quite a bit
lib.marktree_check(tree)
dosplice(tree, shadow, { 2, 2 }, { 0, 5 }, { 1, 2 })
lib.marktree_check(tree)
shadoworder(tree, shadow, iter)
dosplice(tree, shadow, { 30, 2 }, { 30, 5 }, { 1, 2 })
lib.marktree_check(tree)
shadoworder(tree, shadow, iter)
dosplice(tree, shadow, { 5, 3 }, { 0, 2 }, { 0, 5 })
shadoworder(tree, shadow, iter)
lib.marktree_check(tree)
-- build then burn (HOORAY! HOORAY!)
while next(shadow) do
lib.marktree_itr_first(tree, iter)
-- delete every other key for fun and profit
while true do
local k = lib.marktree_itr_current(iter)
lib.marktree_del_itr(tree, iter, false)
ok(shadow[tonumber(k.id)] ~= nil)
shadow[tonumber(k.id)] = nil
local stat = lib.marktree_itr_next(tree, iter)
if not stat then
break
end
end
lib.marktree_check(tree)
shadoworder(tree, shadow, iter2)
end
extmark: fix deletable nodes in MarkTree sometimes getting skipped As per #14236, performing extmark cleanup in a certain namespace does not guarantee removing all the extmarks inside given namespace. The issue resides within the tree node removal method and results in a couple of rare edge cases. To demonstrate what causes this bug, I'll give an example covering one of the edge cases. === AN EXAMPLE === (A) (B) (C) (D) (E) --------- --------- --------- --------- --------- <0, 1> <0, 1> <0, 1> <0, 1> <0, 1> <0, 2> <0, 2> <0, 2> <0, 2> <0, 2> <0, 3> <0, 3> <0, 3> <0, 3> <0, 3> <0, 4> <0, 4> <0, 4> <0, 4> <0, 4> <0, 5> <0, 5> <0, 5> <0, 5> <0, 5> <0, 6> <0, 6> <0, 6> <0, 6> <0, 6> <0, 7> <0, 7> <0, 7> <0, 7> <0, 7> <0, 8> <0, 8> <0, 8> <0, 8> <0, 8> <0, 9> <0, 9> * * <0, 9> * <0, 9> [0, 10] * [0, 10] <0, 9> [0, 11] [0, 11] [0, 11] [0, 11] [0, 11] [0, 12] [0, 12] * [0, 12] [0, 12] [0, 12] [0, 13] [0, 13] [0, 13] [0, 13] [0, 13] [0, 14] [0, 14] [0, 14] [0, 14] [0, 14] [0, 15] [0, 15] [0, 15] [0, 15] [0, 15] [0, 16] [0, 16] [0, 16] [0, 16] [0, 16] [0, 17] [0, 17] [0, 17] [0, 17] [0, 17] [0, 18] [0, 18] [0, 18] [0, 18] [0, 18] [0, 19] [0, 19] [0, 19] [0, 19] [0, 19] [0, 20] [0, 20] [0, 20] [0, 20] [0, 20] DIAGRAM EXPLANATION * Every column is a state of the marktree at a certain stage. * To make it simple, I don't draw the whole tree. What you see are 2 leftmost parent nodes ([0, 10], [0, 20]) and their children placed in order `MarkTreeIter` would iterate through. From top to bottom. * Numbers on this diagram represent extmark coordinates. Relative positioning and actual mark IDs used by the marktree are avoided for simplicity. * 2 types of brackets around coordinates represent 2 different extmark namespaces (`ns_id`s). * '*' shows iterator position. ACTUAL EXPLANATION Let's assume, we have two sets of extmarks from 2 different plugins: * Plugin1: <0, 1-9> * Plugin2: [0, 10-20] 1. Plugin2 calls `vim.api.nvim_buf_clear_namespace(buf_handle, ns_id, 0, -1)` to clear all its extmarks which results in `extmark_clear` call. 2. The iteration process goes on ignoring extmarks with irrelevant `ns_id` from Plugin1, until it reaches [0, 10], entering state (A). 3. At the end of cleaning up process, `marktree_del_itr` gets called. This function is supposed to remove given node and, if necessary, restructure the tree. Also, move the iterator to the next node. The bug occurs in this function. 4. The iterator goes backwards to the node's last child, to put it in the place of its deleted parent later. (B) 5. The parent node is deleted and replaced with its child node. (C) 6. Since now this node has 8 children, which is less than `MT_BRANCH_FACTOR - 1`, it get's merged with the next node. (D) 7. Finally, since at (B) the iterator went backward, it goes forward twice, skipping [0, 11] node, causing this extmark to persist, causing the bug. (E) ANALYSIS AND SOLUTION The algorithm works perfectly when the parent node gets replaced by its child, but no merging occurs. I.e. the exact same diagram, but without the (D) stage. If not for (D), it would iterate to <0, 9> and then to [0, 11]. So, iterating twice makes sense. The actual problem is in (C) stage, because the iterator index isn't adjusted and still pointing to no longer existent node. So my solution is to adjust iterator index after removing the child node. More info: https://github.com/neovim/neovim/pull/14719
2021-06-04 03:38:13 -07:00
-- Check iterator validity for 2 specific edge cases:
-- https://github.com/neovim/neovim/pull/14719
lib.marktree_clear(tree)
for i = 1, 20 do
put(tree, i, i, false)
extmark: fix deletable nodes in MarkTree sometimes getting skipped As per #14236, performing extmark cleanup in a certain namespace does not guarantee removing all the extmarks inside given namespace. The issue resides within the tree node removal method and results in a couple of rare edge cases. To demonstrate what causes this bug, I'll give an example covering one of the edge cases. === AN EXAMPLE === (A) (B) (C) (D) (E) --------- --------- --------- --------- --------- <0, 1> <0, 1> <0, 1> <0, 1> <0, 1> <0, 2> <0, 2> <0, 2> <0, 2> <0, 2> <0, 3> <0, 3> <0, 3> <0, 3> <0, 3> <0, 4> <0, 4> <0, 4> <0, 4> <0, 4> <0, 5> <0, 5> <0, 5> <0, 5> <0, 5> <0, 6> <0, 6> <0, 6> <0, 6> <0, 6> <0, 7> <0, 7> <0, 7> <0, 7> <0, 7> <0, 8> <0, 8> <0, 8> <0, 8> <0, 8> <0, 9> <0, 9> * * <0, 9> * <0, 9> [0, 10] * [0, 10] <0, 9> [0, 11] [0, 11] [0, 11] [0, 11] [0, 11] [0, 12] [0, 12] * [0, 12] [0, 12] [0, 12] [0, 13] [0, 13] [0, 13] [0, 13] [0, 13] [0, 14] [0, 14] [0, 14] [0, 14] [0, 14] [0, 15] [0, 15] [0, 15] [0, 15] [0, 15] [0, 16] [0, 16] [0, 16] [0, 16] [0, 16] [0, 17] [0, 17] [0, 17] [0, 17] [0, 17] [0, 18] [0, 18] [0, 18] [0, 18] [0, 18] [0, 19] [0, 19] [0, 19] [0, 19] [0, 19] [0, 20] [0, 20] [0, 20] [0, 20] [0, 20] DIAGRAM EXPLANATION * Every column is a state of the marktree at a certain stage. * To make it simple, I don't draw the whole tree. What you see are 2 leftmost parent nodes ([0, 10], [0, 20]) and their children placed in order `MarkTreeIter` would iterate through. From top to bottom. * Numbers on this diagram represent extmark coordinates. Relative positioning and actual mark IDs used by the marktree are avoided for simplicity. * 2 types of brackets around coordinates represent 2 different extmark namespaces (`ns_id`s). * '*' shows iterator position. ACTUAL EXPLANATION Let's assume, we have two sets of extmarks from 2 different plugins: * Plugin1: <0, 1-9> * Plugin2: [0, 10-20] 1. Plugin2 calls `vim.api.nvim_buf_clear_namespace(buf_handle, ns_id, 0, -1)` to clear all its extmarks which results in `extmark_clear` call. 2. The iteration process goes on ignoring extmarks with irrelevant `ns_id` from Plugin1, until it reaches [0, 10], entering state (A). 3. At the end of cleaning up process, `marktree_del_itr` gets called. This function is supposed to remove given node and, if necessary, restructure the tree. Also, move the iterator to the next node. The bug occurs in this function. 4. The iterator goes backwards to the node's last child, to put it in the place of its deleted parent later. (B) 5. The parent node is deleted and replaced with its child node. (C) 6. Since now this node has 8 children, which is less than `MT_BRANCH_FACTOR - 1`, it get's merged with the next node. (D) 7. Finally, since at (B) the iterator went backward, it goes forward twice, skipping [0, 11] node, causing this extmark to persist, causing the bug. (E) ANALYSIS AND SOLUTION The algorithm works perfectly when the parent node gets replaced by its child, but no merging occurs. I.e. the exact same diagram, but without the (D) stage. If not for (D), it would iterate to <0, 9> and then to [0, 11]. So, iterating twice makes sense. The actual problem is in (C) stage, because the iterator index isn't adjusted and still pointing to no longer existent node. So my solution is to adjust iterator index after removing the child node. More info: https://github.com/neovim/neovim/pull/14719
2021-06-04 03:38:13 -07:00
end
lib.marktree_itr_get(tree, 10, 10, iter)
lib.marktree_del_itr(tree, iter, false)
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
eq(11, iter[0].x.key[iter[0].i].pos.col)
extmark: fix deletable nodes in MarkTree sometimes getting skipped As per #14236, performing extmark cleanup in a certain namespace does not guarantee removing all the extmarks inside given namespace. The issue resides within the tree node removal method and results in a couple of rare edge cases. To demonstrate what causes this bug, I'll give an example covering one of the edge cases. === AN EXAMPLE === (A) (B) (C) (D) (E) --------- --------- --------- --------- --------- <0, 1> <0, 1> <0, 1> <0, 1> <0, 1> <0, 2> <0, 2> <0, 2> <0, 2> <0, 2> <0, 3> <0, 3> <0, 3> <0, 3> <0, 3> <0, 4> <0, 4> <0, 4> <0, 4> <0, 4> <0, 5> <0, 5> <0, 5> <0, 5> <0, 5> <0, 6> <0, 6> <0, 6> <0, 6> <0, 6> <0, 7> <0, 7> <0, 7> <0, 7> <0, 7> <0, 8> <0, 8> <0, 8> <0, 8> <0, 8> <0, 9> <0, 9> * * <0, 9> * <0, 9> [0, 10] * [0, 10] <0, 9> [0, 11] [0, 11] [0, 11] [0, 11] [0, 11] [0, 12] [0, 12] * [0, 12] [0, 12] [0, 12] [0, 13] [0, 13] [0, 13] [0, 13] [0, 13] [0, 14] [0, 14] [0, 14] [0, 14] [0, 14] [0, 15] [0, 15] [0, 15] [0, 15] [0, 15] [0, 16] [0, 16] [0, 16] [0, 16] [0, 16] [0, 17] [0, 17] [0, 17] [0, 17] [0, 17] [0, 18] [0, 18] [0, 18] [0, 18] [0, 18] [0, 19] [0, 19] [0, 19] [0, 19] [0, 19] [0, 20] [0, 20] [0, 20] [0, 20] [0, 20] DIAGRAM EXPLANATION * Every column is a state of the marktree at a certain stage. * To make it simple, I don't draw the whole tree. What you see are 2 leftmost parent nodes ([0, 10], [0, 20]) and their children placed in order `MarkTreeIter` would iterate through. From top to bottom. * Numbers on this diagram represent extmark coordinates. Relative positioning and actual mark IDs used by the marktree are avoided for simplicity. * 2 types of brackets around coordinates represent 2 different extmark namespaces (`ns_id`s). * '*' shows iterator position. ACTUAL EXPLANATION Let's assume, we have two sets of extmarks from 2 different plugins: * Plugin1: <0, 1-9> * Plugin2: [0, 10-20] 1. Plugin2 calls `vim.api.nvim_buf_clear_namespace(buf_handle, ns_id, 0, -1)` to clear all its extmarks which results in `extmark_clear` call. 2. The iteration process goes on ignoring extmarks with irrelevant `ns_id` from Plugin1, until it reaches [0, 10], entering state (A). 3. At the end of cleaning up process, `marktree_del_itr` gets called. This function is supposed to remove given node and, if necessary, restructure the tree. Also, move the iterator to the next node. The bug occurs in this function. 4. The iterator goes backwards to the node's last child, to put it in the place of its deleted parent later. (B) 5. The parent node is deleted and replaced with its child node. (C) 6. Since now this node has 8 children, which is less than `MT_BRANCH_FACTOR - 1`, it get's merged with the next node. (D) 7. Finally, since at (B) the iterator went backward, it goes forward twice, skipping [0, 11] node, causing this extmark to persist, causing the bug. (E) ANALYSIS AND SOLUTION The algorithm works perfectly when the parent node gets replaced by its child, but no merging occurs. I.e. the exact same diagram, but without the (D) stage. If not for (D), it would iterate to <0, 9> and then to [0, 11]. So, iterating twice makes sense. The actual problem is in (C) stage, because the iterator index isn't adjusted and still pointing to no longer existent node. So my solution is to adjust iterator index after removing the child node. More info: https://github.com/neovim/neovim/pull/14719
2021-06-04 03:38:13 -07:00
lib.marktree_itr_get(tree, 11, 11, iter)
lib.marktree_del_itr(tree, iter, false)
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
eq(12, iter[0].x.key[iter[0].i].pos.col)
end)
itp("'intersect_mov' function works correctly", function()
local function mov(x, y, w)
local xa = ffi.new('uint64_t[?]', #x)
for i, xi in ipairs(x) do
xa[i - 1] = xi
end
local ya = ffi.new('uint64_t[?]', #y)
for i, yi in ipairs(y) do
ya[i - 1] = yi
end
local wa = ffi.new('uint64_t[?]', #w)
for i, wi in ipairs(w) do
wa[i - 1] = wi
end
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
local dummy_size = #x + #y + #w
local wouta = ffi.new('uint64_t[?]', dummy_size)
local douta = ffi.new('uint64_t[?]', dummy_size)
local wsize = ffi.new('size_t[1]')
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
wsize[0] = dummy_size
local dsize = ffi.new('size_t[1]')
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
dsize[0] = dummy_size
local status = lib.intersect_mov_test(xa, #x, ya, #y, wa, #w, wouta, wsize, douta, dsize)
if status == 0 then
error 'wowza'
end
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
local wout, dout = {}, {}
for i = 0, tonumber(wsize[0]) - 1 do
table.insert(wout, tonumber(wouta[i]))
end
for i = 0, tonumber(dsize[0]) - 1 do
table.insert(dout, tonumber(douta[i]))
end
return { wout, dout }
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
end
eq({ {}, {} }, mov({}, { 2, 3 }, { 2, 3 }))
eq({ { 2, 3 }, {} }, mov({}, {}, { 2, 3 }))
eq({ { 2, 3 }, {} }, mov({ 2, 3 }, {}, {}))
eq({ {}, { 2, 3 } }, mov({}, { 2, 3 }, {}))
eq({ { 1, 5 }, {} }, mov({ 1, 2, 5 }, { 2, 3 }, { 3 }))
eq({ { 1, 2 }, {} }, mov({ 1, 2, 5 }, { 5, 10 }, { 10 }))
eq({ { 1, 2 }, { 5 } }, mov({ 1, 2 }, { 5, 10 }, { 10 }))
eq({ { 1, 3, 5, 7, 9 }, { 2, 4, 6, 8, 10 } }, mov({ 1, 3, 5, 7, 9 }, { 2, 4, 6, 8, 10 }, {}))
eq({ { 1, 3, 5, 7, 9 }, { 2, 6, 10 } }, mov({ 1, 3, 5, 7, 9 }, { 2, 4, 6, 8, 10 }, { 4, 8 }))
eq({ { 1, 4, 7 }, { 2, 5, 8 } }, mov({ 1, 3, 4, 6, 7, 9 }, { 2, 3, 5, 6, 8, 9 }, {}))
eq({ { 1, 4, 7 }, {} }, mov({ 1, 3, 4, 6, 7, 9 }, { 2, 3, 5, 6, 8, 9 }, { 2, 5, 8 }))
eq(
{ { 0, 1, 4, 7, 10 }, {} },
mov({ 1, 3, 4, 6, 7, 9 }, { 2, 3, 5, 6, 8, 9 }, { 0, 2, 5, 8, 10 })
)
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
end)
local function check_intersections(tree)
lib.marktree_check(tree)
-- to debug stuff disable this branch
if true == true then
ok(lib.marktree_check_intersections(tree))
return
end
local str1 = lib.mt_inspect(tree, true, true)
local dot1 = ffi.string(str1.data, str1.size)
local val = lib.marktree_check_intersections(tree)
if not val then
local str2 = lib.mt_inspect(tree, true, true)
local dot2 = ffi.string(str2.data, str2.size)
print('actual:\n\n' .. 'Xafile.dot' .. '\n\nexpected:\n\n' .. 'Xefile.dot' .. '\n')
print('nivå', tree[0].root.level)
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
io.stdout:flush()
local afil = io.open('Xafile.dot', 'wb')
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
afil:write(dot1)
afil:close()
local efil = io.open('Xefile.dot', 'wb')
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
efil:write(dot2)
efil:close()
ok(false)
else
ffi.C.xfree(str1.data)
end
end
itp('works with intersections', function()
local tree = ffi.new('MarkTree[1]') -- zero initialized by luajit
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
local ids = {}
for i = 1, 80 do
table.insert(ids, put(tree, 1, i, false, 2, 100 - i, false))
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
check_intersections(tree)
end
for i = 1, 80 do
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
lib.marktree_del_pair_test(tree, ns, ids[i])
check_intersections(tree)
end
ids = {}
for i = 1, 80 do
table.insert(ids, put(tree, 1, i, false, 2, 100 - i, false))
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
check_intersections(tree)
end
for i = 1, 10 do
for j = 1, 8 do
local ival = (j - 1) * 10 + i
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
lib.marktree_del_pair_test(tree, ns, ids[ival])
check_intersections(tree)
end
end
end)
itp('works with intersections with a big tree', function()
local tree = ffi.new('MarkTree[1]') -- zero initialized by luajit
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
local ids = {}
for i = 1, 1000 do
table.insert(ids, put(tree, 1, i, false, 2, 1000 - i, false))
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
if i % 10 == 1 then
check_intersections(tree)
end
end
check_intersections(tree)
eq(2000, tree[0].n_keys)
ok(tree[0].root.level >= 2)
local iter = ffi.new('MarkTreeIter[1]')
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
local k = 0
for i = 1, 20 do
for j = 1, 50 do
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
k = k + 1
local ival = (j - 1) * 20 + i
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
if false == true then -- if there actually is a failure, this branch will fail out at the actual spot of the error
lib.marktree_lookup_ns(tree, ns, ids[ival], false, iter)
lib.marktree_del_itr(tree, iter, false)
check_intersections(tree)
lib.marktree_lookup_ns(tree, ns, ids[ival], true, iter)
lib.marktree_del_itr(tree, iter, false)
check_intersections(tree)
else
lib.marktree_del_pair_test(tree, ns, ids[ival])
if k % 5 == 1 then
check_intersections(tree)
end
end
end
end
eq(0, tree[0].n_keys)
end)
itp('works with intersections and marktree_splice', function()
local tree = ffi.new('MarkTree[1]') -- zero initialized by luajit
for i = 1, 1000 do
put(tree, 1, i, false, 2, 1000 - i, false)
if i % 10 == 1 then
check_intersections(tree)
end
end
check_intersections(tree)
eq(2000, tree[0].n_keys)
ok(tree[0].root.level >= 2)
for _ = 1, 10 do
lib.marktree_splice(tree, 0, 0, 0, 100, 0, 0)
check_intersections(tree)
end
end)
itp('marktree_move should preserve key order', function()
local tree = ffi.new('MarkTree[1]') -- zero initialized by luajit
local iter = ffi.new('MarkTreeIter[1]')
local ids = {}
-- new index and old index look the same, but still have to move because
-- pos will get updated
table.insert(ids, put(tree, 1, 1, false, 1, 3, false))
table.insert(ids, put(tree, 1, 3, false, 1, 3, false))
table.insert(ids, put(tree, 1, 3, false, 1, 3, false))
table.insert(ids, put(tree, 1, 3, false, 1, 3, false))
lib.marktree_lookup_ns(tree, ns, ids[3], false, iter)
lib.marktree_move(tree, iter, 1, 2)
check_intersections(tree)
end)
itp('works with intersections and marktree_move', function()
local tree = ffi.new('MarkTree[1]') -- zero initialized by luajit
local ids = {}
for i = 1, 1000 do
table.insert(ids, put(tree, 1, i, false, 2, 1000 - i, false))
if i % 10 == 1 then
check_intersections(tree)
end
end
local iter = ffi.new('MarkTreeIter[1]')
for i = 1, 1000 do
local which = i % 2
lib.marktree_lookup_ns(tree, ns, ids[i], which, iter)
lib.marktree_move(tree, iter, 1 + which, 500 + i)
if i % 10 == 1 then
check_intersections(tree)
end
end
end)
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
itp('works with intersections with a even bigger tree', function()
local tree = ffi.new('MarkTree[1]') -- zero initialized by luajit
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
local ids = {}
-- too much overhead on ASAN
local size_factor = t.is_asan() and 3 or 10
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
local at_row = {}
for i = 1, 10 do
at_row[i] = {}
end
local size = 1000 * size_factor
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
local k = 1
while k <= size do
for row1 = 1, 9 do
for row2 = row1, 10 do -- note row2 can be == row1, leads to empty ranges being tested when k > size/2
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
if k > size then
break
end
local id = put(tree, row1, k, false, row2, size - k, false)
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
table.insert(ids, id)
for i = row1 + 1, row2 do
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
table.insert(at_row[i], id)
end
--if tree[0].root.level == 4 then error("kk"..k) end
if k % 100 * size_factor == 1 or (k < 2000 and k % 100 == 1) then
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
check_intersections(tree)
end
k = k + 1
end
end
end
eq(2 * size, tree[0].n_keys)
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
ok(tree[0].root.level >= 3)
check_intersections(tree)
local iter = ffi.new('MarkTreeIter[1]')
local pair = ffi.new('MTPair[1]')
for i = 1, 10 do
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
-- use array as set and not {[id]=true} map, to detect duplicates
local set = {}
eq(true, ffi.C.marktree_itr_get_overlap(tree, i, 0, iter))
while ffi.C.marktree_itr_step_overlap(tree, iter, pair) do
local id = tonumber(pair[0].start.id)
table.insert(set, id)
end
table.sort(set)
eq(at_row[i], set)
end
k = 0
for i = 1, 100 do
for j = 1, (10 * size_factor) do
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
k = k + 1
local ival = (j - 1) * 100 + i
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
lib.marktree_del_pair_test(tree, ns, ids[ival])
-- just a few stickprov, if there is trouble we need to check
-- everyone using the code in the "big tree" case above
if k % 100 * size_factor == 0 or (k > 3000 and k % 200 == 0) then
feat(extmark): support proper multiline ranges The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
2020-11-22 02:10:37 -07:00
check_intersections(tree)
end
end
end
eq(0, tree[0].n_keys)
end)
itp('works with intersections with a even bigger tree and splice', function()
local tree = ffi.new('MarkTree[1]') -- zero initialized by luajit
-- too much overhead on ASAN
local size_factor = t.is_asan() and 3 or 10
local at_row = {}
for i = 1, 10 do
at_row[i] = {}
end
local size = 1000 * size_factor
local k = 1
while k <= size do
for row1 = 1, 9 do
for row2 = row1, 10 do -- note row2 can be == row1, leads to empty ranges being tested when k > size/2
if k > size then
break
end
local id = put(tree, row1, k, false, row2, size - k, false)
for i = row1 + 1, row2 do
table.insert(at_row[i], id)
end
--if tree[0].root.level == 4 then error("kk"..k) end
if k % 100 * size_factor == 1 or (k < 2000 and k % 100 == 1) then
check_intersections(tree)
end
k = k + 1
end
end
end
eq(2 * size, tree[0].n_keys)
ok(tree[0].root.level >= 3)
check_intersections(tree)
for _ = 1, 10 do
for j = 3, 8 do
lib.marktree_splice(tree, j, 0, 0, 200, 0, 0)
check_intersections(tree)
end
end
end)
itp('works with meta counts', function()
local tree = ffi.new('MarkTree[1]') -- zero initialized by luajit
-- add
local shadow = {}
for i = 1, 100 do
for j = 1, 100 do
local gravitate = (i % 2) > 0
local inline = (j == 3 or j == 50 or j == 51 or j == 55) and i % 11 == 1
inline = inline or ((j >= 80 and j < 85) and i % 3 == 1)
local id = put_meta(tree, j, i, gravitate, inline)
if inline then
shadow[id] = { j, i, gravitate }
end
end
-- checking every insert is too slow, but this is ok
lib.marktree_check(tree)
end
lib.marktree_check(tree)
local iter = ffi.new('MarkTreeIter[1]')
local filter = ffi.new('uint32_t[4]')
filter[0] = -1
ok(lib.marktree_itr_get_filter(tree, 0, 0, 101, 0, filter, iter))
local seen = {}
repeat
local mark = lib.marktree_itr_current(iter)
eq(nil, seen[mark.id])
seen[mark.id] = true
eq(mark.pos.row, shadow[mark.id][1])
eq(mark.pos.col, shadow[mark.id][2])
until not lib.marktree_itr_next_filter(tree, iter, 101, 0, filter)
eq(tablelength(seen), tablelength(shadow))
-- test skipping subtrees to find the filtered mark at line 50
for i = 4, 50 do
ok(lib.marktree_itr_get_filter(tree, i, 0, 60, 0, filter, iter))
local mark = lib.marktree_itr_current(iter)
eq({ 50, 50, 1 }, { mark.id, mark.pos.row, mark.pos.col })
end
-- delete
for id = 1, 10000, 2 do
lib.marktree_lookup_ns(tree, ns, id, false, iter)
if shadow[id] then
local mark = lib.marktree_itr_current(iter)
eq(mark.pos.row, shadow[id][1])
eq(mark.pos.col, shadow[id][2])
shadow[id] = nil
end
lib.marktree_del_itr(tree, iter, false)
if id % 100 == 1 then
lib.marktree_check(tree)
end
end
-- Splice!
dosplice(tree, shadow, { 82, 0 }, { 0, 50 }, { 0, 0 })
lib.marktree_check(tree)
dosplice(tree, shadow, { 81, 50 }, { 2, 50 }, { 1, 0 })
lib.marktree_check(tree)
dosplice(tree, shadow, { 2, 50 }, { 1, 50 }, { 0, 10 })
lib.marktree_check(tree)
ok(lib.marktree_itr_get_filter(tree, 0, 0, 101, 0, filter, iter))
seen = {}
repeat
local mark = lib.marktree_itr_current(iter)
eq(nil, seen[mark.id])
seen[mark.id] = true
eq(mark.pos.row, shadow[mark.id][1])
eq(mark.pos.col, shadow[mark.id][2])
until not lib.marktree_itr_next_filter(tree, iter, 101, 0, filter)
eq(tablelength(seen), tablelength(shadow))
end)
end)