From d3ffc9411099890c117fc833fd3c4b09a667db6e Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 10 Jul 2026 03:17:57 +0200 Subject: [PATCH] refactor(cmdpreview): lift undo-checkpoint code This is a mechanical lift of undo save-and-restore logic into... the module that owns undo. --- src/nvim/api/buffer.c | 2 +- src/nvim/api/window.c | 2 +- src/nvim/eval/window.c | 2 +- src/nvim/ex_getln.c | 95 ++---------------------------------------- src/nvim/undo.c | 76 +++++++++++++++++++++++++++++++++ src/nvim/undo_defs.h | 23 ++++++++++ 6 files changed, 105 insertions(+), 95 deletions(-) diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 19e54a1080..2995bd64d8 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1222,7 +1222,7 @@ Object nvim_buf_call(Buffer buf, LuaRef fn, lua_State *lstate, Error *err) } TRY_WRAP(err, { - CtxSwitch cs; + CtxSwitch cs = { 0 }; ctx_switch(&cs, NULL, NULL, b, 0); Array args = ARRAY_DICT_INIT; diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 7d0cbc8ff7..7e7bcd03e0 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -375,7 +375,7 @@ Object nvim_win_call(Window win, LuaRef fn, lua_State *lstate, Error *err) tabpage_T *tabpage = win_find_tabpage(w); TRY_WRAP(err, { - CtxSwitch cs; + CtxSwitch cs = { 0 }; if (ctx_switch(&cs, w, tabpage, NULL, kCtxNoDisplay | kCtxKeepCwd | kCtxValidate)) { Array args = ARRAY_DICT_INIT; nlua_call_ref(fn, NULL, args, kRetMultiStack, NULL, err); diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c index d5494eacb0..0b779427e9 100644 --- a/src/nvim/eval/window.c +++ b/src/nvim/eval/window.c @@ -518,7 +518,7 @@ void f_win_execute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } - CtxSwitch cs; + CtxSwitch cs = { 0 }; if (ctx_switch(&cs, wp, tp, NULL, kCtxNoDisplay | kCtxKeepCwd | kCtxValidate)) { execute_common(argvars, rettv, 1); } diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 51c355b4f3..bda5b7e20c 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -157,31 +157,13 @@ typedef struct { bool did_hist_navigate; } CommandLineState; -typedef struct { - u_header_T *save_b_u_oldhead; - u_header_T *save_b_u_newhead; - u_header_T *save_b_u_curhead; - int save_b_u_numhead; - bool save_b_u_synced; - int save_b_u_seq_last; - int save_b_u_save_nr_last; - int save_b_u_seq_cur; - time_t save_b_u_time_cur; - int save_b_u_save_nr_cur; - char *save_b_u_line_ptr; - linenr_T save_b_u_line_lnum; - colnr_T save_b_u_line_colnr; -} CpUndoInfo; - typedef struct { buf_T *buf; - OptInt save_b_p_ul; int save_b_p_ma; int save_b_changed; pos_T save_b_op_start; pos_T save_b_op_end; - varnumber_T save_changedtick; - CpUndoInfo undo_info; + UndoCheckpoint undo_checkpoint; } CpBufInfo; typedef struct { @@ -2574,45 +2556,6 @@ static void cmdpreview_close_win(void) } } -/// Save the undo state of a buffer for command preview. -static void cmdpreview_save_undo(CpUndoInfo *cp_undoinfo, buf_T *buf) - FUNC_ATTR_NONNULL_ALL -{ - cp_undoinfo->save_b_u_synced = buf->b_u_synced; - cp_undoinfo->save_b_u_oldhead = buf->b_u_oldhead; - cp_undoinfo->save_b_u_newhead = buf->b_u_newhead; - cp_undoinfo->save_b_u_curhead = buf->b_u_curhead; - cp_undoinfo->save_b_u_numhead = buf->b_u_numhead; - cp_undoinfo->save_b_u_seq_last = buf->b_u_seq_last; - cp_undoinfo->save_b_u_save_nr_last = buf->b_u_save_nr_last; - cp_undoinfo->save_b_u_seq_cur = buf->b_u_seq_cur; - cp_undoinfo->save_b_u_time_cur = buf->b_u_time_cur; - cp_undoinfo->save_b_u_save_nr_cur = buf->b_u_save_nr_cur; - cp_undoinfo->save_b_u_line_ptr = buf->b_u_line_ptr; - cp_undoinfo->save_b_u_line_lnum = buf->b_u_line_lnum; - cp_undoinfo->save_b_u_line_colnr = buf->b_u_line_colnr; -} - -/// Restore the undo state of a buffer for command preview. -static void cmdpreview_restore_undo(const CpUndoInfo *cp_undoinfo, buf_T *buf) -{ - buf->b_u_oldhead = cp_undoinfo->save_b_u_oldhead; - buf->b_u_newhead = cp_undoinfo->save_b_u_newhead; - buf->b_u_curhead = cp_undoinfo->save_b_u_curhead; - buf->b_u_numhead = cp_undoinfo->save_b_u_numhead; - buf->b_u_seq_last = cp_undoinfo->save_b_u_seq_last; - buf->b_u_save_nr_last = cp_undoinfo->save_b_u_save_nr_last; - buf->b_u_seq_cur = cp_undoinfo->save_b_u_seq_cur; - buf->b_u_time_cur = cp_undoinfo->save_b_u_time_cur; - buf->b_u_save_nr_cur = cp_undoinfo->save_b_u_save_nr_cur; - buf->b_u_line_ptr = cp_undoinfo->save_b_u_line_ptr; - buf->b_u_line_lnum = cp_undoinfo->save_b_u_line_lnum; - buf->b_u_line_colnr = cp_undoinfo->save_b_u_line_colnr; - if (buf->b_u_curhead == NULL) { - buf->b_u_synced = cp_undoinfo->save_b_u_synced; - } -} - /// Save current state and prepare windows and buffers for command preview. static void cmdpreview_prepare(CpInfo *cpinfo) FUNC_ATTR_NONNULL_ALL @@ -2634,17 +2577,12 @@ static void cmdpreview_prepare(CpInfo *cpinfo) CpBufInfo cp_bufinfo; cp_bufinfo.buf = buf; cp_bufinfo.save_b_p_ma = buf->b_p_ma; - cp_bufinfo.save_b_p_ul = buf->b_p_ul; cp_bufinfo.save_b_changed = buf->b_changed; cp_bufinfo.save_b_op_start = buf->b_op_start; cp_bufinfo.save_b_op_end = buf->b_op_end; - cp_bufinfo.save_changedtick = buf_get_changedtick(buf); - cmdpreview_save_undo(&cp_bufinfo.undo_info, buf); + u_checkpoint(&cp_bufinfo.undo_checkpoint, buf); kv_push(cpinfo->buf_info, cp_bufinfo); set_put(ptr_t, &saved_bufs, buf); - - u_clearall(buf); - buf->b_p_ul = INT_MAX; // Make sure we can undo all changes } CpWinInfo cp_wininfo; @@ -2692,38 +2630,11 @@ static void cmdpreview_restore_state(CpInfo *cpinfo) // Clear preview highlights. extmark_clear(buf, (uint32_t)cmdpreview_ns, 0, 0, MAXLNUM, MAXCOL); - if (buf->b_u_seq_cur != cp_bufinfo.undo_info.save_b_u_seq_cur) { - int count = 0; - - // Calculate how many undo steps are necessary to restore earlier state. - for (u_header_T *uhp = buf->b_u_curhead ? buf->b_u_curhead : buf->b_u_newhead; - uhp != NULL; - uhp = uhp->uh_next.ptr, ++count) {} - - aco_save_T aco = { 0 }; - aucmd_prepbuf(&aco, buf); - // Ensure all the entries will be undone - if (curbuf->b_u_synced == false) { - u_sync(true); - } - // Undo invisibly. This also moves the cursor! - if (!u_undo_and_forget(count, false)) { - abort(); - } - aucmd_restbuf(&aco); - } - - u_blockfree(buf); - cmdpreview_restore_undo(&cp_bufinfo.undo_info, buf); + u_rollback(&cp_bufinfo.undo_checkpoint, buf); buf->b_op_start = cp_bufinfo.save_b_op_start; buf->b_op_end = cp_bufinfo.save_b_op_end; - if (cp_bufinfo.save_changedtick != buf_get_changedtick(buf)) { - buf_set_changedtick(buf, cp_bufinfo.save_changedtick); - } - - buf->b_p_ul = cp_bufinfo.save_b_p_ul; // Restore 'undolevels' buf->b_p_ma = cp_bufinfo.save_b_p_ma; // Restore 'modifiable' } diff --git a/src/nvim/undo.c b/src/nvim/undo.c index f3d81897a7..d42b8fd349 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -88,6 +88,7 @@ #include "nvim/buffer_defs.h" #include "nvim/buffer_updates.h" #include "nvim/change.h" +#include "nvim/context.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" @@ -3248,3 +3249,78 @@ u_header_T *u_force_get_undo_header(buf_T *buf) } return uhp; } + +/// Checkpoints the undo state of `buf` and detaches its undotree: subsequent edits build +/// a disposable tree. Then u_rollback() can revert them without a trace ("undo-invisible" +/// speculative edits, e.g. 'inccommand' preview). Sets 'undolevels' so every edit stays undoable. +void u_checkpoint(UndoCheckpoint *uc, buf_T *buf) + FUNC_ATTR_NONNULL_ALL +{ + uc->uc_synced = buf->b_u_synced; + uc->uc_oldhead = buf->b_u_oldhead; + uc->uc_newhead = buf->b_u_newhead; + uc->uc_curhead = buf->b_u_curhead; + uc->uc_numhead = buf->b_u_numhead; + uc->uc_seq_last = buf->b_u_seq_last; + uc->uc_save_nr_last = buf->b_u_save_nr_last; + uc->uc_seq_cur = buf->b_u_seq_cur; + uc->uc_time_cur = buf->b_u_time_cur; + uc->uc_save_nr_cur = buf->b_u_save_nr_cur; + uc->uc_line_ptr = buf->b_u_line_ptr; + uc->uc_line_lnum = buf->b_u_line_lnum; + uc->uc_line_colnr = buf->b_u_line_colnr; + uc->uc_undolevels = buf->b_p_ul; + uc->uc_changedtick = buf_get_changedtick(buf); + + u_clearall(buf); + buf->b_p_ul = INT_MAX; // Make sure we can undo all changes +} + +/// Reverts `buf` from a checkpoint: drops all edits made since u_checkpoint(), and reattaches the +/// checkpointed undotree. Also restores b:changedtick and 'undolevels'. +void u_rollback(UndoCheckpoint *uc, buf_T *buf) + FUNC_ATTR_NONNULL_ALL +{ + if (buf->b_u_seq_cur != uc->uc_seq_cur) { + int count = 0; + + // Calculate how many undo steps are necessary to restore earlier state. + for (u_header_T *uhp = buf->b_u_curhead ? buf->b_u_curhead : buf->b_u_newhead; + uhp != NULL; + uhp = uhp->uh_next.ptr, ++count) {} + + CtxSwitch cs = { 0 }; + ctx_switch(&cs, NULL, NULL, buf, 0); + // Ensure all the entries will be undone + if (curbuf->b_u_synced == false) { + u_sync(true); + } + // Undo invisibly. This also moves the cursor! + if (!u_undo_and_forget(count, false)) { + abort(); + } + ctx_restore(&cs); + } + + u_blockfree(buf); + buf->b_u_oldhead = uc->uc_oldhead; + buf->b_u_newhead = uc->uc_newhead; + buf->b_u_curhead = uc->uc_curhead; + buf->b_u_numhead = uc->uc_numhead; + buf->b_u_seq_last = uc->uc_seq_last; + buf->b_u_save_nr_last = uc->uc_save_nr_last; + buf->b_u_seq_cur = uc->uc_seq_cur; + buf->b_u_time_cur = uc->uc_time_cur; + buf->b_u_save_nr_cur = uc->uc_save_nr_cur; + buf->b_u_line_ptr = uc->uc_line_ptr; + buf->b_u_line_lnum = uc->uc_line_lnum; + buf->b_u_line_colnr = uc->uc_line_colnr; + if (buf->b_u_curhead == NULL) { + buf->b_u_synced = uc->uc_synced; + } + + if (uc->uc_changedtick != buf_get_changedtick(buf)) { + buf_set_changedtick(buf, uc->uc_changedtick); + } + buf->b_p_ul = uc->uc_undolevels; // Restore 'undolevels' +} diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h index 39786fa928..0206f15b6c 100644 --- a/src/nvim/undo_defs.h +++ b/src/nvim/undo_defs.h @@ -2,8 +2,10 @@ #include +#include "nvim/eval/typval_defs.h" #include "nvim/extmark_defs.h" #include "nvim/mark_defs.h" +#include "nvim/option_defs.h" enum { UNDO_HASH_SIZE = 32, }; ///< Size in bytes of the hash used in the undo file. @@ -73,3 +75,24 @@ enum { UH_EMPTYBUF = 0x02, ///< buffer was empty UH_RELOAD = 0x04, ///< buffer was reloaded }; + +/// Checkpoint of buffer undo state, for "undo-invisible" speculative edits: u_checkpoint() detaches +/// the undo tree so subsequent edits build a disposable one; u_rollback() reverts those edits and +/// reattaches the checkpointed tree. Used by 'inccommand' preview. +typedef struct { + u_header_T *uc_oldhead; + u_header_T *uc_newhead; + u_header_T *uc_curhead; + int uc_numhead; + bool uc_synced; + int uc_seq_last; + int uc_save_nr_last; + int uc_seq_cur; + time_t uc_time_cur; + int uc_save_nr_cur; + char *uc_line_ptr; + linenr_T uc_line_lnum; + colnr_T uc_line_colnr; + OptInt uc_undolevels; ///< saved 'undolevels' + varnumber_T uc_changedtick; ///< saved b:changedtick +} UndoCheckpoint;