From 37c0efb21cfbd05554d01b0f05edc7cf28b9d62d Mon Sep 17 00:00:00 2001 From: bfredl Date: Tue, 24 Feb 2026 11:12:56 +0100 Subject: [PATCH] fix(marktree): fix edge case bug regarding changing intersections fix #37867 This bug happens when only one end is moved between different nodes, but the other end is also moved but within the same node. When this happens we need the correct previous position even for the internal move. This code shall be refactored to make the intent clearer, (and avoid some unnecessary processing) but this is a fix for the observable bug. Thanks to KevinGoodsell for providing a deterministic reproduce using fuzzing techniques. --- src/nvim/marktree.c | 19 ++++++++++--------- test/unit/marktree_spec.lua | 24 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c index f76fcbaf4c..bd972862a6 100644 --- a/src/nvim/marktree.c +++ b/src/nvim/marktree.c @@ -1860,16 +1860,17 @@ bool marktree_itr_step_overlap(MarkTree *b, MarkTreeIter *itr, MTPair *pair) static void swap_keys(MarkTree *b, MarkTreeIter *itr1, MarkTreeIter *itr2, DamageList *damage) { - if (itr1->x != itr2->x) { - if (mt_paired(rawkey(itr1))) { - kvi_push(*damage, ((Damage){ mt_lookup_key(rawkey(itr1)), itr1->x, itr2->x, - itr1->i, itr2->i })); - } - if (mt_paired(rawkey(itr2))) { - kvi_push(*damage, ((Damage){ mt_lookup_key(rawkey(itr2)), itr2->x, itr1->x, - itr2->i, itr1->i })); - } + // TODO(bfredl): redactor is planned, see TODO comment next to qsort in marktree_splice + if (mt_paired(rawkey(itr1))) { + kvi_push(*damage, ((Damage){ mt_lookup_key(rawkey(itr1)), itr1->x, itr2->x, + itr1->i, itr2->i })); + } + if (mt_paired(rawkey(itr2))) { + kvi_push(*damage, ((Damage){ mt_lookup_key(rawkey(itr2)), itr2->x, itr1->x, + itr2->i, itr1->i })); + } + if (itr1->x != itr2->x) { uint32_t meta_inc_1[kMTMetaCount]; meta_describe_key(meta_inc_1, rawkey(itr1)); uint32_t meta_inc_2[kMTMetaCount]; diff --git a/test/unit/marktree_spec.lua b/test/unit/marktree_spec.lua index cac99cd4ab..37ac501b89 100644 --- a/test/unit/marktree_spec.lua +++ b/test/unit/marktree_spec.lua @@ -645,4 +645,28 @@ describe('marktree', function() until not lib.marktree_itr_next_filter(tree, iter, 101, 0, filter) eq(tablelength(seen), tablelength(shadow)) end) + + itp('works with edge case splicing overlapping ranges #37867', function() + local tree = ffi.new('MarkTree[1]') -- zero initialized by luajit + put(tree, 190, 48, false) + put(tree, 48, 48, true) + put(tree, 190, 48, false) + put(tree, 166, 48, false, 166, 48, false) + put(tree, 48, 48, true, 48, 48, false) + put(tree, 48, 48, true, 48, 48, false) + put(tree, 48, 48, true, 255, 48, false) + put(tree, 131, 48, false, 48, 48, false) + put(tree, 131, 48, false, 48, 48, false) + put(tree, 48, 48, true, 131, 48, false) + put(tree, 48, 48, false, 216, 48, false) + put(tree, 172, 48, false, 51, 48, false) + put(tree, 131, 48, false, 131, 48, false) + put(tree, 156, 48, false, 131, 48, false) + put(tree, 135, 48, false, 166, 48, false) + put(tree, 172, 48, false, 250, 48, false) + put(tree, 48, 48, false, 143, 48, false) + ok(lib.marktree_check_intersections(tree)) + lib.marktree_splice(tree, 48, 0, 139, 0, 0, 0) + ok(lib.marktree_check_intersections(tree)) + end) end)