From c4f76299f0398f8a9b42bb7c1a5fad3205680298 Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Fri, 6 Dec 2024 11:13:58 +0100 Subject: [PATCH] fix(marks): skip right_gravity marks when deleting text Problem: Marks that are properly restored by the splice associated with an undo edit, are unnecessarily pushed to the undo header. This results in incorrect mark tracking in the "copy_only" save/restore completion path. Solution: Avoid pushing left gravity marks at the beginning of the range, and right gravity marks at the end of the range to the undo header. --- src/nvim/extmark.c | 26 ++++++++++++++++++-------- test/functional/api/extmark_spec.lua | 2 +- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index c20c7dea23..6119d838f9 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -367,12 +367,23 @@ void extmark_splice_delete(buf_T *buf, int l_row, colnr_T l_col, int u_row, coln marktree_itr_get(buf->b_marktree, (int32_t)l_row, l_col, itr); while (true) { MTKey mark = marktree_itr_current(itr); - if (mark.pos.row < 0 - || mark.pos.row > u_row - || (mark.pos.row == u_row && mark.pos.col > u_col)) { + if (mark.pos.row < 0 || mark.pos.row > u_row) { break; } + bool copy = true; + // No need to copy left gravity marks at the beginning of the range, + // and right gravity marks at the end of the range, unless invalidated. + if (mark.pos.row == l_row && mark.pos.col - !mt_right(mark) < l_col) { + copy = false; + } else if (mark.pos.row == u_row) { + if (mark.pos.col > u_col + 1) { + break; + } else if (mark.pos.col + mt_right(mark) > u_col) { + copy = false; + } + } + bool invalidated = false; // Invalidate/delete mark if (!only_copy && !mt_invalid(mark) && mt_invalidate(mark) && !mt_end(mark)) { @@ -388,6 +399,7 @@ void extmark_splice_delete(buf_T *buf, int l_row, colnr_T l_col, int u_row, coln extmark_del(buf, itr, mark, true); continue; } else { + copy = true; invalidated = true; mt_itr_rawkey(itr).flags |= MT_FLAG_INVALID; marktree_revise_meta(buf->b_marktree, itr, mark); @@ -397,7 +409,7 @@ void extmark_splice_delete(buf_T *buf, int l_row, colnr_T l_col, int u_row, coln } // Push mark to undo header - if (only_copy || (uvp != NULL && op == kExtmarkUndo && !mt_no_undo(mark))) { + if (copy && (only_copy || (uvp != NULL && op == kExtmarkUndo && !mt_no_undo(mark)))) { ExtmarkSavePos pos = { .mark = mt_lookup_key(mark), .invalidated = invalidated, @@ -541,10 +553,8 @@ void extmark_splice_impl(buf_T *buf, int start_row, colnr_T start_col, bcount_t if (old_row > 0 || old_col > 0) { // Copy and invalidate marks that would be effected by delete - // TODO(bfredl): Be "smart" about gravity here, left-gravity at the - // beginning and right-gravity at the end need not be preserved. - // Also be smart about marks that already have been saved (important for - // merge!) + // TODO(bfredl): Be smart about marks that already have been + // saved (important for merge!) int end_row = start_row + old_row; int end_col = (old_row ? 0 : start_col) + old_col; u_header_T *uhp = u_force_get_undo_header(buf); diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index 43be0c0e43..49c55288e8 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -249,7 +249,7 @@ describe('API/extmarks', function() set_extmark(ns, 2, 1, 0, { right_gravity = false }) eq({ { 1, 0, 0 }, { 2, 1, 0 } }, get_extmarks(ns, { 0, 0 }, { -1, -1 })) feed('u') - eq({ { 1, 0, 0 }, { 2, 1, 0 } }, get_extmarks(ns, { 0, 0 }, { -1, -1 })) + eq({ { 1, 0, 0 }, { 2, 0, 0 } }, get_extmarks(ns, { 0, 0 }, { -1, -1 })) api.nvim_buf_clear_namespace(0, ns, 0, -1) end)