From 2accf2480530c7df2120d8522103f5f1516268cf Mon Sep 17 00:00:00 2001 From: bfredl Date: Thu, 15 Feb 2024 19:05:01 +0100 Subject: [PATCH] fix(decorations): crash with revised mark with changed decoration flags fixes #27211 --- src/nvim/extmark.c | 8 +++++-- src/nvim/marktree.c | 29 ++++++++++++++++++++++++ test/functional/ui/decorations_spec.lua | 30 +++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index e753ad199a..e4c4960b9e 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -74,11 +74,15 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col if (old_mark.pos.row == row && old_mark.pos.col == col) { // not paired: we can revise in place if (!invalid && mt_decor_any(old_mark)) { + // TODO(bfredl): conflict of concerns: buf_decor_remove() must process + // the buffer as if MT_FLAG_DECOR_SIGNTEXT is already removed, however + // marktree must precisely adjust the set of flags from the old set to the new + uint16_t save_flags = mt_itr_rawkey(itr).flags; mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_DECOR_SIGNTEXT; buf_decor_remove(buf, row, row, col, mt_decor(old_mark), true); + mt_itr_rawkey(itr).flags = save_flags; } - mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_EXTERNAL_MASK; - mt_itr_rawkey(itr).flags |= flags; + marktree_revise_flags(buf->b_marktree, itr, flags); mt_itr_rawkey(itr).decor_data = decor.data; goto revised; } diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c index 9da7503524..1da75eb2af 100644 --- a/src/nvim/marktree.c +++ b/src/nvim/marktree.c @@ -774,6 +774,35 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev) return other; } +void marktree_revise_flags(MarkTree *b, MarkTreeIter *itr, uint16_t new_flags) +{ + uint32_t meta_old[4]; + meta_describe_key(meta_old, rawkey(itr)); + rawkey(itr).flags &= (uint16_t) ~MT_FLAG_EXTERNAL_MASK; + rawkey(itr).flags |= new_flags; + + uint32_t meta_new[4]; + meta_describe_key(meta_new, rawkey(itr)); + + if (!memcmp(meta_old, meta_new, sizeof(meta_old))) { + return; + } + + MTNode *lnode = itr->x; + while (lnode->parent) { + uint32_t *meta_p = lnode->parent->meta[lnode->p_idx]; + for (int m = 0; m < kMTMetaCount; m++) { + meta_p[m] += meta_new[m] - meta_old[m]; + } + + lnode = lnode->parent; + } + + for (int m = 0; m < kMTMetaCount; m++) { + b->meta_root[m] += meta_new[m] - meta_old[m]; + } +} + /// similar to intersect_common but modify x and y in place to retain /// only the items which are NOT in common static void intersect_merge(Intersection *restrict m, Intersection *restrict x, diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 930e4c3f10..9bbae0dae0 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -2371,6 +2371,36 @@ describe('extmark decorations', function() | ]]} end) + + it('can replace marks in place with different decorations #27211', function() + local mark = api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_lines = {{{"foo", "ErrorMsg"}}}, }) + screen:expect{grid=[[ + ^ | + {4:foo} | + {1:~ }|*12 + | + ]]} + + api.nvim_buf_set_extmark(0, ns, 0, 0, { + id = mark, + virt_text = { { "testing", "NonText" } }, + virt_text_pos = "inline", + }) + screen:expect{grid=[[ + {1:^testing} | + {1:~ }|*13 + | + ]]} + + api.nvim_buf_del_extmark(0, ns, mark) + screen:expect{grid=[[ + ^ | + {1:~ }|*13 + | + ]]} + + helpers.assert_alive() + end) end) describe('decorations: inline virtual text', function()