neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

commit 37c0efb21cfbd05554d01b0f05edc7cf28b9d62d
parent 3a4a7a7efb2769d386d780aa79b1f625a0e83ce9
Author: bfredl <bjorn.linse@gmail.com>
Date:   Tue, 24 Feb 2026 11:12:56 +0100

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.

Diffstat:
Msrc/nvim/marktree.c | 19++++++++++---------
Mtest/unit/marktree_spec.lua | 24++++++++++++++++++++++++
2 files changed, 34 insertions(+), 9 deletions(-)

diff --git 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 @@ -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)