From 6bb1aa74e6fe26bffbbdb91878f8f07477c01fcc Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 9 Jul 2026 19:50:16 +0200 Subject: [PATCH] refactor: unify context-switching concepts --- src/nvim/api/buffer.c | 7 +- src/nvim/api/vim.c | 2 +- src/nvim/api/win_config.c | 3 +- src/nvim/api/window.c | 9 +- src/nvim/arglist.c | 5 +- src/nvim/autocmd.c | 263 +------------ src/nvim/autocmd.h | 15 - src/nvim/autocmd_defs.h | 15 +- src/nvim/buffer.c | 9 +- src/nvim/cmdexpand.c | 4 +- src/nvim/context.c | 564 +++++++++++++++++++++++----- src/nvim/context.h | 35 +- src/nvim/context_defs.h | 95 +++++ src/nvim/drawscreen.c | 3 +- src/nvim/eval.c | 7 +- src/nvim/eval/funcs.c | 2 +- src/nvim/eval/window.c | 158 +------- src/nvim/eval/window.h | 20 +- src/nvim/ex_docmd.c | 3 +- src/nvim/lua/stdlib.c | 29 +- src/nvim/main.c | 1 + src/nvim/statusline.c | 3 +- src/nvim/terminal.c | 3 +- src/nvim/window.c | 59 +-- test/functional/api/buffer_spec.lua | 10 + 25 files changed, 691 insertions(+), 633 deletions(-) create mode 100644 src/nvim/context_defs.h diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 63efdd9aa1..19e54a1080 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -22,6 +22,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/ex_cmds.h" #include "nvim/extmark.h" @@ -1221,13 +1222,13 @@ Object nvim_buf_call(Buffer buf, LuaRef fn, lua_State *lstate, Error *err) } TRY_WRAP(err, { - aco_save_T aco = { 0 }; - aucmd_prepbuf(&aco, b); + CtxSwitch cs; + ctx_switch(&cs, NULL, NULL, b, 0); Array args = ARRAY_DICT_INIT; nlua_call_ref(fn, NULL, args, kRetMultiStack, NULL, err); - aucmd_restbuf(&aco); + ctx_restore(&cs); }); return NIL; // kRetMultiStack: values are already on the lua stack diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index f898a08a0d..405e2ed5c3 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1567,7 +1567,7 @@ Object nvim_load_context(Dict dict, Error *err) ctx_from_dict(dict, &ctx, err); if (!ERROR_SET(err)) { - ctx_restore(&ctx, kCtxAll); + ctx_load(&ctx, kCtxAll); } ctx_free(&ctx); diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 7873f9b347..b159acf7e6 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -16,6 +16,7 @@ #include "nvim/buffer.h" #include "nvim/buffer_defs.h" #include "nvim/charset.h" +#include "nvim/context.h" #include "nvim/decoration_defs.h" #include "nvim/drawscreen.h" #include "nvim/errors.h" @@ -403,7 +404,7 @@ static bool win_can_move_tp(win_T *wp, tabpage_T *tp, Error *err) api_set_error(err, kErrorTypeException, "%s", e_textlock); return false; } - if (is_aucmd_win(wp)) { + if (is_ctx_win(wp)) { api_set_error(err, kErrorTypeException, "Cannot move autocmd window to another tabpage"); return false; } diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index d0e2a9286c..7d0cbc8ff7 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -11,6 +11,7 @@ #include "nvim/api/window.h" #include "nvim/autocmd.h" #include "nvim/buffer_defs.h" +#include "nvim/context.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" #include "nvim/errors.h" @@ -321,7 +322,7 @@ void nvim_win_hide(Window win, Error *err) tabpage_T *tabpage = win_find_tabpage(w); TRY_WRAP(err, { // Never close the autocommand window. - if (is_aucmd_win(w)) { + if (is_ctx_win(w)) { emsg(_(e_autocmd_close)); } else if (tabpage == curtab) { win_close(w, false, false); @@ -374,12 +375,12 @@ Object nvim_win_call(Window win, LuaRef fn, lua_State *lstate, Error *err) tabpage_T *tabpage = win_find_tabpage(w); TRY_WRAP(err, { - win_execute_T win_execute_args; - if (win_execute_before(&win_execute_args, w, tabpage)) { + CtxSwitch cs; + if (ctx_switch(&cs, w, tabpage, NULL, kCtxNoDisplay | kCtxKeepCwd | kCtxValidate)) { Array args = ARRAY_DICT_INIT; nlua_call_ref(fn, NULL, args, kRetMultiStack, NULL, err); } - win_execute_after(&win_execute_args); + ctx_restore(&cs); }); return NIL; // kRetMultiStack: values are already on the lua stack } diff --git a/src/nvim/arglist.c b/src/nvim/arglist.c index 812f45b8c0..deb783e2c8 100644 --- a/src/nvim/arglist.c +++ b/src/nvim/arglist.c @@ -13,6 +13,7 @@ #include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cmdexpand_defs.h" +#include "nvim/context.h" #include "nvim/errors.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" @@ -879,7 +880,7 @@ static void arg_all_close_unused_windows(arg_all_state_T *aall) if (buf->b_ffname == NULL || (!aall->keep_tabs && (buf->b_nwindows > 1 || wp->w_width != Columns - || (wp->w_floating && !is_aucmd_win(wp))))) { + || (wp->w_floating && !is_ctx_win(wp))))) { i = aall->opened_len; } else { // check if the buffer in this window is in the arglist @@ -1113,7 +1114,7 @@ static void do_arg_all(int count, int forceit, int keep_tabs) autocmd_no_leave++; last_curwin = curwin; last_curtab = curtab; - // lastwin may be aucmd_win + // lastwin may be ctx_win win_enter(lastwin_nofloating(NULL), false); // Open up to "count" windows. diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index e154095001..eda97a24ac 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -13,6 +13,7 @@ #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cmdexpand_defs.h" +#include "nvim/context.h" #include "nvim/cursor.h" #include "nvim/errors.h" #include "nvim/eval.h" @@ -101,7 +102,6 @@ static int current_augroup = AUGROUP_DEFAULT; static bool au_need_clean = false; static int autocmd_blocked = 0; // block all autocmds -static int aucmd_prepbuf_depth = 0; static bool autocmd_nested = false; static bool autocmd_include_groups = false; @@ -598,21 +598,10 @@ void free_all_autocmds(void) }) map_destroy(int, &map_augroup_id_to_name); - // aucmd_win[] is freed in win_free_all() + // ctx_win[] is freed in win_free_all() } #endif -/// Return true if "win" is an active entry in aucmd_win[]. -bool is_aucmd_win(win_T *win) -{ - for (int i = 0; i < AUCMD_WIN_COUNT; i++) { - if (aucmd_win[i].auc_win_used && aucmd_win[i].auc_win == win) { - return true; - } - } - return false; -} - /// Return the event number for event name "start". /// Return NUM_EVENTS if the event name was not found. /// Return a pointer to the next event name in "end". @@ -1212,7 +1201,7 @@ void ex_doautoall(exarg_T *eap) // Execute the modeline settings, but don't set window-local // options if we are using the current window for another // buffer. - do_modelines(is_aucmd_win(curwin) ? OPT_NOWIN : 0); + do_modelines(is_ctx_win(curwin) ? OPT_NOWIN : 0); } // restore the current window @@ -1249,251 +1238,17 @@ bool check_nomodeline(char **argp) return true; } -/// Prepare for executing autocommands for (hidden) buffer `buf`. -/// If the current buffer is not in any visible window, put it in a temporary -/// floating window using an entry in `aucmd_win[]`. -/// Set `curbuf` and `curwin` to match `buf`. -/// -/// @param aco structure to save values in -/// @param buf new curbuf +// TODO(justinmk): legacy shim, use ctx_switch() directly. void aucmd_prepbuf(aco_save_T *aco, buf_T *buf) { - win_T *win; - bool need_append = true; // Append `aucmd_win` to the window list. - const bool same_buffer = buf == curbuf; - - // Find a window that is for the new buffer - if (same_buffer) { // be quick when buf is curbuf - win = curwin; - } else { - win = NULL; - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == buf) { - win = wp; - break; - } - } - } - - // Allocate a window when needed. - win_T *auc_win = NULL; - int auc_idx = AUCMD_WIN_COUNT; - if (win == NULL) { - for (auc_idx = 0; auc_idx < AUCMD_WIN_COUNT; auc_idx++) { - if (!aucmd_win[auc_idx].auc_win_used) { - break; - } - } - - if (auc_idx == AUCMD_WIN_COUNT) { - kv_push(aucmd_win_vec, ((aucmdwin_T){ - .auc_win = NULL, - .auc_win_used = false, - })); - } - - if (aucmd_win[auc_idx].auc_win == NULL) { - win_alloc_aucmd_win(auc_idx); - need_append = false; - } - auc_win = aucmd_win[auc_idx].auc_win; - aucmd_win[auc_idx].auc_win_used = true; - } - - aco->save_curwin_handle = curwin->handle; - aco->save_prevwin_handle = prevwin == NULL ? 0 : prevwin->handle; - if (bt_prompt(curbuf)) { - aco->save_prompt_insert = curbuf->b_prompt_insert; - } - - if (win != NULL) { - // There is a window for "buf" in the current tab page, make it the - // curwin. This is preferred, it has the least side effects (esp. if - // "buf" is curbuf). - aco->use_aucmd_win_idx = -1; - curwin = win; - } else { - // There is no window for "buf", use "auc_win". To minimize the side - // effects, insert it in the current tab page. - // Anything related to a window (e.g., setting folds) may have - // unexpected results. - aco->use_aucmd_win_idx = auc_idx; - auc_win->w_buffer = buf; - auc_win->w_s = &buf->b_s; - buf->b_nwindows++; - win_init_empty(auc_win); // set cursor and topline to safe values - - // Make sure w_localdir, tp_localdir and globaldir are NULL to avoid a - // chdir() in win_enter_ext(). - XFREE_CLEAR(auc_win->w_localdir); - aco->tp_localdir = curtab->tp_localdir; - curtab->tp_localdir = NULL; - aco->globaldir = globaldir; - globaldir = NULL; - - block_autocmds(); // We don't want BufEnter/WinEnter autocommands. - if (need_append) { - win_append(lastwin, auc_win, NULL); - pmap_put(int)(&window_handles, auc_win->handle, auc_win); - win_config_float(auc_win, auc_win->w_config); - } - // Prevent chdir() call in win_enter_ext(), through do_autochdir() - const int save_acd = p_acd; - p_acd = false; - // no redrawing and don't set the window title - RedrawingDisabled++; - win_enter(auc_win, false); - RedrawingDisabled--; - p_acd = save_acd; - unblock_autocmds(); - curwin = auc_win; - } - curbuf = buf; - aco->new_curwin_handle = curwin->handle; - set_bufref(&aco->new_curbuf, curbuf); - - aco->save_VIsual_active = VIsual_active; - if (!same_buffer) { - // disable the Visual area, position may be invalid in another buffer - VIsual_active = false; - } - if (aco->new_curwin_handle != aco->save_curwin_handle) { - autocmd_save_curwin = aucmd_prepbuf_depth == 0 ? aco->save_curwin_handle : autocmd_save_curwin; - aucmd_prepbuf_depth++; - } + ctx_switch(aco, NULL, NULL, buf, 0); } -/// Cleanup after executing autocommands for a (hidden) buffer. -/// Restore the window as it was (if possible). -/// -/// If `aco` was zero-initialized, then `aucmd_restbuf` may be safely called even if `aucmd_prepbuf` -/// was skipped: -/// -/// aco_save_T aco = { 0 }; -/// if (some_condition) { -/// aucmd_prepbuf(&aco, buf); -/// } -/// ... -/// aucmd_restbuf(&aco); // no-op if aucmd_prepbuf was skipped. -/// -/// @param aco structure holding saved values +// TODO(justinmk): legacy shim, use ctx_restore() directly. If `aco` was zero-initialized, this may +// be safely called even if aucmd_prepbuf() was skipped. void aucmd_restbuf(aco_save_T *aco) { - // NULL br_buf means `aucmd_prepbuf` was never called on this `aco`. - if (aco->new_curbuf.br_buf == NULL) { - return; - } - - if (aco->use_aucmd_win_idx >= 0) { - win_T *awp = aucmd_win[aco->use_aucmd_win_idx].auc_win; - - // Find "awp", it can't be closed, but it may be in another tab page. - // Do not trigger autocommands here. - block_autocmds(); - if (curwin != awp) { - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp == awp) { - if (tp != curtab) { - goto_tabpage_tp(tp, true, true); - } - win_goto(awp); - goto win_found; - } - } - } -win_found: - curbuf->b_nwindows--; - // Remove the window. - win_remove(curwin, NULL); - pmap_del(int)(&window_handles, curwin->handle, NULL); - - // The window is marked as not used, but it is not freed, it can be - // used again. - aucmd_win[aco->use_aucmd_win_idx].auc_win_used = false; - - if (!valid_tabpage_win(curtab)) { - // no valid window in current tabpage - close_tabpage(curtab); - } - - unblock_autocmds(); - - win_T *const save_curwin = win_find_by_handle(aco->save_curwin_handle); - if (save_curwin != NULL) { - curwin = save_curwin; - } else { - // Hmm, original window disappeared. Just use the first one. - curwin = firstwin; - } - curbuf = curwin->w_buffer; - // May need to restore insert mode for a prompt buffer. - entering_window(curwin); - if (bt_prompt(curbuf)) { - curbuf->b_prompt_insert = aco->save_prompt_insert; - } - - prevwin = win_find_by_handle(aco->save_prevwin_handle); - vars_clear(&awp->w_vars->dv_hashtab); // free all w: variables - hash_init(&awp->w_vars->dv_hashtab); // re-use the hashtab - - // If :lcd has been used in the autocommand window, correct current - // directory before restoring tp_localdir and globaldir. - if (awp->w_localdir != NULL) { - win_fix_current_dir(); - } - xfree(curtab->tp_localdir); - curtab->tp_localdir = aco->tp_localdir; - xfree(globaldir); - globaldir = aco->globaldir; - - // the buffer contents may have changed - VIsual_active = aco->save_VIsual_active; - check_cursor(curwin); - if (curwin->w_topline > curbuf->b_ml.ml_line_count) { - curwin->w_topline = curbuf->b_ml.ml_line_count; - curwin->w_topfill = 0; - } - } else { - // Restore curwin. Use the window ID, a window may have been closed - // and the memory re-used for another one. - win_T *const save_curwin = win_find_by_handle(aco->save_curwin_handle); - if (save_curwin != NULL) { - // Restore the buffer which was previously edited by curwin, if it was - // changed, we are still the same window and the buffer is valid. - if (curwin->handle == aco->new_curwin_handle - && curbuf != aco->new_curbuf.br_buf - && bufref_valid(&aco->new_curbuf) - && aco->new_curbuf.br_buf->b_ml.ml_mfp != NULL) { - if (curwin->w_s == &curbuf->b_s) { - curwin->w_s = &aco->new_curbuf.br_buf->b_s; - } - curbuf->b_nwindows--; - curbuf = aco->new_curbuf.br_buf; - curwin->w_buffer = curbuf; - curbuf->b_nwindows++; - } - - curwin = save_curwin; - curbuf = curwin->w_buffer; - prevwin = win_find_by_handle(aco->save_prevwin_handle); - - // In case the autocommand moves the cursor to a position that does not - // exist in curbuf - VIsual_active = aco->save_VIsual_active; - check_cursor(curwin); - } - } - - VIsual_active = aco->save_VIsual_active; - check_cursor(curwin); // just in case lines got deleted - if (VIsual_active) { - check_pos(curbuf, &VIsual); - } - if (aco->new_curwin_handle != aco->save_curwin_handle) { - assert(aucmd_prepbuf_depth > 0); - aucmd_prepbuf_depth--; - autocmd_save_curwin = aucmd_prepbuf_depth == 0 ? 0 : autocmd_save_curwin; - } + ctx_restore(aco); } /// Schedules an autocommand event, to be executed at the next event-loop tick. @@ -2373,7 +2128,7 @@ char *expand_get_augroup_name(expand_T *xp, int idx) } /// @param doautocmd true for :doauto*, false for :autocmd -char *set_context_in_autocmd(expand_T *xp, char *arg, bool doautocmd) +char *aucmd_set_expand_context(expand_T *xp, char *arg, bool doautocmd) { // check for a group name, skip it if present autocmd_include_groups = false; diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h index 3fb1a3b79c..aa3a1b2ddb 100644 --- a/src/nvim/autocmd.h +++ b/src/nvim/autocmd.h @@ -41,21 +41,6 @@ EXTERN bool autocmd_fname_full INIT( = false); ///< autocmd_fname is full path EXTERN int autocmd_bufnr INIT( = 0); ///< fnum for on cmdline EXTERN char *autocmd_match INIT( = NULL); ///< name for on cmdline EXTERN bool did_cursorhold INIT( = true); ///< set when CursorHold t'gerd -// Used to restore the actual current buffer/window when redrawing. -EXTERN handle_T autocmd_save_curwin INIT( = 0); - -typedef struct { - win_T *auc_win; ///< Window used in aucmd_prepbuf(). When not NULL the - ///< window has been allocated. - bool auc_win_used; ///< This auc_win is being used. -} aucmdwin_T; - -/// When executing autocommands for a buffer that is not in any window, a -/// special window is created to handle the side effects. When autocommands -/// nest we may need more than one. -EXTERN kvec_t(aucmdwin_T) aucmd_win_vec INIT( = KV_INITIAL_VALUE); -#define aucmd_win (aucmd_win_vec.items) -#define AUCMD_WIN_COUNT ((int)aucmd_win_vec.size) enum { AUGROUP_DEFAULT = -1, ///< default autocmd group diff --git a/src/nvim/autocmd_defs.h b/src/nvim/autocmd_defs.h index f11a40743c..006f3e6f8e 100644 --- a/src/nvim/autocmd_defs.h +++ b/src/nvim/autocmd_defs.h @@ -5,24 +5,11 @@ #include #include "nvim/buffer_defs.h" +#include "nvim/context_defs.h" // IWYU pragma: keep (aco_save_T) #include "nvim/ex_cmds_defs.h" #include "auevents_enum.generated.h" -/// Struct to save values in before executing autocommands for a buffer that is -/// not the current buffer. -typedef struct { - int use_aucmd_win_idx; ///< index in aucmd_win[] if >= 0 - handle_T save_curwin_handle; ///< ID of saved curwin - handle_T new_curwin_handle; ///< ID of new curwin - handle_T save_prevwin_handle; ///< ID of saved prevwin - bufref_T new_curbuf; ///< new curbuf - char *tp_localdir; ///< saved value of tp_localdir - char *globaldir; ///< saved value of globaldir - bool save_VIsual_active; ///< saved VIsual_active - int save_prompt_insert; ///< saved b_prompt_insert -} aco_save_T; - typedef struct { size_t refcount; ///< Reference count (freed when reaches zero) char *pat; ///< Pattern as typed diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index cdd6ef294a..0903d729ce 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -39,6 +39,7 @@ #include "nvim/channel.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" +#include "nvim/context.h" #include "nvim/cursor.h" #include "nvim/diff.h" #include "nvim/digraph.h" @@ -1455,7 +1456,7 @@ static int do_buffer_ext(int action, int start, int dir, int count, int flags) // Repeat this so long as we end up in a window with this buffer. while (buf == curbuf && !(win_locked(curwin) || curwin->w_buffer->b_locked > 0) - && (is_aucmd_win(lastwin) || !last_window(curwin))) { + && (is_ctx_win(lastwin) || !last_window(curwin))) { if (win_close(curwin, false, false) == FAIL) { break; } @@ -3681,7 +3682,7 @@ void ex_buffer_all(exarg_T *eap) || (had_tab > 0 && wp != firstwin)) && !ONE_WINDOW && !(win_locked(wp) || wp->w_buffer->b_locked > 0) - && !is_aucmd_win(wp)) { + && !is_ctx_win(wp)) { if (win_close(wp, false, false) == FAIL) { break; } @@ -3708,7 +3709,7 @@ void ex_buffer_all(exarg_T *eap) // // Don't execute Win/Buf Enter/Leave autocommands here. autocmd_no_enter++; - // lastwin may be aucmd_win + // lastwin may be ctx_win win_enter(lastwin_nofloating(NULL), false); autocmd_no_leave++; for (buf_T *buf = firstbuf; buf != NULL && open_wins < count; buf = buf->b_next) { @@ -3800,7 +3801,7 @@ void ex_buffer_all(exarg_T *eap) // Close superfluous windows. for (win_T *wp = lastwin; open_wins > count;) { bool r = (buf_hide(wp->w_buffer) || !bufIsChanged(wp->w_buffer) - || autowrite(wp->w_buffer, false) == OK) && !is_aucmd_win(wp); + || autowrite(wp->w_buffer, false) == OK) && !is_ctx_win(wp); if (!win_valid(wp)) { // BufWrite Autocommands made the window invalid, start over wp = lastwin; diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index 8af6fbd536..6edd2cec8a 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -2122,11 +2122,11 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa case CMD_dsplit: return find_cmd_after_isearch_cmd(xp, arg); case CMD_autocmd: - return set_context_in_autocmd(xp, (char *)arg, false); + return aucmd_set_expand_context(xp, (char *)arg, false); case CMD_doautocmd: case CMD_doautoall: - return set_context_in_autocmd(xp, (char *)arg, true); + return aucmd_set_expand_context(xp, (char *)arg, true); case CMD_set: set_context_in_set_cmd(xp, (char *)arg, 0); break; diff --git a/src/nvim/context.c b/src/nvim/context.c index 8a878dc1bc..517b3ac0b4 100644 --- a/src/nvim/context.c +++ b/src/nvim/context.c @@ -1,4 +1,11 @@ -// Context: snapshot of the entire editor state as one big object/map +// Context = "full app state" abstraction. (Note: it's named "Context" to disambiguate with state.c +// which is about the event-loop state-machine, not "total program state".) +// +// Unified interface of: +// + shada +// + CtxSwitch/ctx_switch (FKA: aucmd_prepbuf, switch_win, win_execute_T) +// + TODO: sessions +// + TODO: undo save/restore (for cmdpreview, multicursor) #include #include @@ -11,27 +18,43 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/api/vimscript.h" +#include "nvim/autocmd.h" +#include "nvim/buffer.h" #include "nvim/context.h" +#include "nvim/cursor.h" #include "nvim/eval/encode.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" +#include "nvim/eval/vars.h" #include "nvim/ex_docmd.h" +#include "nvim/globals.h" #include "nvim/hashtab.h" #include "nvim/keycodes.h" +#include "nvim/mark.h" #include "nvim/memory.h" #include "nvim/memory_defs.h" #include "nvim/option.h" #include "nvim/option_defs.h" +#include "nvim/option_vars.h" +#include "nvim/os/fs.h" #include "nvim/shada.h" +#include "nvim/vim_defs.h" +#include "nvim/window.h" +#include "nvim/winfloat.h" #include "context.c.generated.h" -int kCtxAll = (kCtxRegs | kCtxJumps | kCtxBufs | kCtxGVars | kCtxSFuncs - | kCtxFuncs); +int kCtxAll = (kCtxRegs | kCtxJumps | kCtxBufs | kCtxGVars | kCtxSFuncs | kCtxFuncs); static ContextVec ctx_stack = KV_INITIAL_VALUE; +/// Nesting depth of ctx_switch() calls that changed curwin. +static int _ctx_switch_depth = 0; + +/// curwin saved by the outermost curwin-changing ctx_switch() (0: none). +static handle_T _ctx_saved_curwin = 0; + /// Clears and frees the context stack void ctx_free_all(void) { @@ -102,23 +125,42 @@ void ctx_save(Context *ctx, const int flags) ctx->gvars = shada_encode_gvars(); } - if (flags & kCtxFuncs) { - ctx_save_funcs(ctx, false); - } else if (flags & kCtxSFuncs) { - ctx_save_funcs(ctx, true); + if (flags & (kCtxFuncs | kCtxSFuncs)) { + const bool scriptonly = !(flags & kCtxFuncs); // kCtxSFuncs: s: functions only + ctx->funcs = (Array)ARRAY_DICT_INIT; + Error err = ERROR_INIT; + + HASHTAB_ITER(func_tbl_get(), hi, { + const char *const name = hi->hi_key; + bool islambda = (strncmp(name, "", 8) == 0); + bool isscript = ((uint8_t)name[0] == K_SPECIAL); + + if (!islambda && (!scriptonly || isscript)) { + size_t cmd_len = sizeof("func! ") + strlen(name); + char *cmd = xmalloc(cmd_len); + snprintf(cmd, cmd_len, "func! %s", name); + Dict(exec_opts) opts = { .output = true }; + String func_body = exec_impl(VIML_INTERNAL_CALL, cstr_as_string(cmd), &opts, &err); + xfree(cmd); + if (!ERROR_SET(&err)) { + ADD(ctx->funcs, STRING_OBJ(func_body)); + } + api_clear_error(&err); + } + }); } } -/// Restores the editor state from a context. +/// Loads (restores) the editor state from a Context snapshot. /// /// If "context" is NULL, pops context from context stack. /// Use "flags" to select particular types of context. /// -/// @param ctx Restore from this context. Pop from context stack if NULL. +/// @param ctx Load from this context. Pop from context stack if NULL. /// @param flags Flags, see ContextTypeFlags enum. /// /// @return true on success, false otherwise (i.e.: empty context stack). -bool ctx_restore(Context *ctx, const int flags) +bool ctx_load(Context *ctx, const int flags) { bool free_ctx = false; if (ctx == NULL) { @@ -133,23 +175,25 @@ bool ctx_restore(Context *ctx, const int flags) set_option_value(kOptShada, STATIC_CSTR_AS_OPTVAL("!,'100,%"), OPT_GLOBAL); if (flags & kCtxRegs) { - ctx_restore_regs(ctx); + shada_read_string(ctx->regs, kShaDaWantInfo | kShaDaForceit); } if (flags & kCtxJumps) { - ctx_restore_jumps(ctx); + shada_read_string(ctx->jumps, kShaDaWantInfo | kShaDaForceit); } if (flags & kCtxBufs) { - ctx_restore_bufs(ctx); + shada_read_string(ctx->bufs, kShaDaWantInfo | kShaDaForceit); } if (flags & kCtxGVars) { - ctx_restore_gvars(ctx); + shada_read_string(ctx->gvars, kShaDaWantInfo | kShaDaForceit); } if (flags & kCtxFuncs) { - ctx_restore_funcs(ctx); + for (size_t i = 0; i < ctx->funcs.size; i++) { + do_cmdline_cmd(ctx->funcs.items[i].data.string.data); + } } if (free_ctx) { @@ -162,84 +206,6 @@ bool ctx_restore(Context *ctx, const int flags) return true; } -/// Restores the global registers from a context. -/// -/// @param ctx Restore from this context. -static inline void ctx_restore_regs(Context *ctx) - FUNC_ATTR_NONNULL_ALL -{ - shada_read_string(ctx->regs, kShaDaWantInfo | kShaDaForceit); -} - -/// Restores the jumplist from a context. -/// -/// @param ctx Restore from this context. -static inline void ctx_restore_jumps(Context *ctx) - FUNC_ATTR_NONNULL_ALL -{ - shada_read_string(ctx->jumps, kShaDaWantInfo | kShaDaForceit); -} - -/// Restores the buffer list from a context. -/// -/// @param ctx Restore from this context. -static inline void ctx_restore_bufs(Context *ctx) - FUNC_ATTR_NONNULL_ALL -{ - shada_read_string(ctx->bufs, kShaDaWantInfo | kShaDaForceit); -} - -/// Restores global variables from a context. -/// -/// @param ctx Restore from this context. -static inline void ctx_restore_gvars(Context *ctx) - FUNC_ATTR_NONNULL_ALL -{ - shada_read_string(ctx->gvars, kShaDaWantInfo | kShaDaForceit); -} - -/// Saves functions to a context. -/// -/// @param ctx Save to this context. -/// @param scriptonly Save script-local (s:) functions only. -static inline void ctx_save_funcs(Context *ctx, bool scriptonly) - FUNC_ATTR_NONNULL_ALL -{ - ctx->funcs = (Array)ARRAY_DICT_INIT; - Error err = ERROR_INIT; - - HASHTAB_ITER(func_tbl_get(), hi, { - const char *const name = hi->hi_key; - bool islambda = (strncmp(name, "", 8) == 0); - bool isscript = ((uint8_t)name[0] == K_SPECIAL); - - if (!islambda && (!scriptonly || isscript)) { - size_t cmd_len = sizeof("func! ") + strlen(name); - char *cmd = xmalloc(cmd_len); - snprintf(cmd, cmd_len, "func! %s", name); - Dict(exec_opts) opts = { .output = true }; - String func_body = exec_impl(VIML_INTERNAL_CALL, cstr_as_string(cmd), - &opts, &err); - xfree(cmd); - if (!ERROR_SET(&err)) { - ADD(ctx->funcs, STRING_OBJ(func_body)); - } - api_clear_error(&err); - } - }); -} - -/// Restores functions from a context. -/// -/// @param ctx Restore from this context. -static inline void ctx_restore_funcs(Context *ctx) - FUNC_ATTR_NONNULL_ALL -{ - for (size_t i = 0; i < ctx->funcs.size; i++) { - do_cmdline_cmd(ctx->funcs.items[i].data.string.data); - } -} - /// Convert readfile()-style array to String /// /// @param[in] array readfile()-style array to convert. @@ -323,3 +289,415 @@ int ctx_from_dict(Dict dict, Context *ctx, Error *err) return types; } + +/// kCtxKeepCwd: remembers the cwd so that ctx_restore() can undo any directory change caused by +/// switching to "wp" ('autochdir', win/tab-local directories). +static void ctx_cwd_save(CtxSwitch *cs, win_T *wp, tabpage_T *tp) +{ + cs->cs_cwd_status = FAIL; + + // Getting and setting directory can be slow on some systems, only do + // this when the current or target window/tab have a local directory or + // 'acd' is set. + char cwd[MAXPATHL]; + if (curwin != wp + && (curwin->w_localdir != NULL || (wp != NULL && wp->w_localdir != NULL) + || (curtab != tp && (curtab->tp_localdir != NULL || tp->tp_localdir != NULL)) + || p_acd)) { + cs->cs_cwd_status = os_dirname(cwd, MAXPATHL); + if (cs->cs_cwd_status == OK) { + cs->cs_cwd = xstrdup(cwd); // allocated on demand: keeps CtxSwitch small + } + } + + // If 'acd' is set, check we are using that directory. If yes, then + // apply 'acd' afterwards, otherwise restore the current directory. + if (cs->cs_cwd_status == OK && p_acd) { + if (curbuf->b_sfname != NULL && curbuf->b_fname == curbuf->b_sfname) { + cs->cs_save_sfname = xstrdup(curbuf->b_sfname); + } + do_autochdir(); + char autocwd[MAXPATHL]; + if (os_dirname(autocwd, MAXPATHL) == OK) { + cs->cs_apply_acd = strcmp(cwd, autocwd) == 0; + } + } +} + +/// kCtxKeepCwd: restores the current directory. +static void ctx_cwd_restore(CtxSwitch *cs) +{ + if (cs->cs_apply_acd) { + xfree(cs->cs_save_sfname); + do_autochdir(); + } else if (cs->cs_cwd_status == OK) { + os_chdir(cs->cs_cwd); + if (cs->cs_save_sfname != NULL) { + xfree(curbuf->b_sfname); + curbuf->b_sfname = cs->cs_save_sfname; + curbuf->b_fname = curbuf->b_sfname; + } + } + XFREE_CLEAR(cs->cs_cwd); +} + +/// Return true if "win" is an active entry in ctx_win[] (the pool of temporary scratch windows). +bool is_ctx_win(win_T *win) +{ + for (int i = 0; i < CTX_WIN_COUNT; i++) { + if (ctx_win[i].cw_used && ctx_win[i].cw_win == win) { + return true; + } + } + return false; +} + +/// Prepares a temporary "autocmd window" showing `buf`: allocated (or reused) from the `ctx_win[]` +/// pool, appended to the window list of the current tabpage, and entered, all without side effects +/// (autocommands, chdir, redraw). Records what ctx_restore() needs to undo it in `cs`. +/// +/// @return the entered autocmd window (the new curwin). +static win_T *ctx_win_prep(CtxSwitch *cs, buf_T *buf) +{ + bool need_append = true; // Append `cw_win` to the window list. + + // Allocate a window when needed. + int idx; + for (idx = 0; idx < CTX_WIN_COUNT; idx++) { + if (!ctx_win[idx].cw_used) { + break; + } + } + if (idx == CTX_WIN_COUNT) { + kv_push(ctx_win_vec, ((CtxWin){ + .cw_win = NULL, + .cw_used = false, + })); + } + if (ctx_win[idx].cw_win == NULL) { + win_alloc_ctx_win(idx); + need_append = false; + } + win_T *cw_win = ctx_win[idx].cw_win; + ctx_win[idx].cw_used = true; + cs->cs_ctxwin_idx = idx; + + cw_win->w_buffer = buf; + cw_win->w_s = &buf->b_s; + buf->b_nwindows++; + win_init_empty(cw_win); // set cursor and topline to safe values + + // Make sure w_localdir, tp_localdir and globaldir are NULL to avoid a + // chdir() in win_enter_ext(). + XFREE_CLEAR(cw_win->w_localdir); + cs->cs_tp_localdir = curtab->tp_localdir; + curtab->tp_localdir = NULL; + cs->cs_globaldir = globaldir; + globaldir = NULL; + + block_autocmds(); // We don't want BufEnter/WinEnter autocommands. + if (need_append) { + win_append(lastwin, cw_win, NULL); + pmap_put(int)(&window_handles, cw_win->handle, cw_win); + win_config_float(cw_win, cw_win->w_config); + } + // Prevent chdir() call in win_enter_ext(), through do_autochdir() + const int save_acd = p_acd; + p_acd = false; + // no redrawing and don't set the window title + RedrawingDisabled++; + win_enter(cw_win, false); + RedrawingDisabled--; + p_acd = save_acd; + unblock_autocmds(); + + return cw_win; +} + +/// Removes the temporary "autocmd window" prepared by ctx_win_prep() from the window list (entering +/// it first if needed), and releases its pool slot. Caller must restore curwin (the removed window +/// is curwin) and the directory state saved in "cs". +/// +/// @return the removed autocmd window. +static win_T *ctx_win_rest(CtxSwitch *cs) +{ + win_T *cwp = ctx_win[cs->cs_ctxwin_idx].cw_win; + + // Find `cwp`, it can't be closed, but it may be in another tab page. + // Do not trigger autocommands here. + block_autocmds(); + if (curwin != cwp) { + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp == cwp) { + if (tp != curtab) { + goto_tabpage_tp(tp, true, true); + } + win_goto(cwp); + goto win_found; + } + } + } +win_found: + curbuf->b_nwindows--; + // Remove the window. + win_remove(curwin, NULL); + pmap_del(int)(&window_handles, curwin->handle, NULL); + + // Mark window as "not used", but don't free, it can be used again. + ctx_win[cs->cs_ctxwin_idx].cw_used = false; + + if (!valid_tabpage_win(curtab)) { + // no valid window in current tabpage + close_tabpage(curtab); + } + + unblock_autocmds(); + return cwp; +} + +/// Window saved by the outermost curwin-changing ctx_switch(), or NULL. Used to restore the +/// actual current window when redrawing. +win_T *ctx_saved_curwin(void) +{ + return _ctx_saved_curwin == 0 ? NULL : win_find_by_handle(_ctx_saved_curwin); +} + +/// Prepares a temporary window or buffer as a temporary execution context. ctx_restore() MUST be +/// called afterwards, also when this returns false. +/// +/// - Passing `wp` makes that window the curwin (in tabpage `tp`, or NULL for current tabpage). +/// - (Legacy: switch_win(), switch_win_noblock(), win_execute_before().) +/// - Passing `buf`, enters a window showing `buf` in the current tabpage, or prepares a temporary +/// "autocmd window" for it (never switches tabpage). +/// - (Legacy: aucmd_prepbuf().) +/// +/// The switch itself never triggers autocommands; whether autocommands can fire _while_ switched +/// (until ctx_restore()) is the caller's choice via kCtxNoEvents. +/// +/// @param wp Target window, or NULL to target a buffer. +/// @param tp Tabpage of `wp`, or NULL to not switch tabpage. +/// @param buf Target buffer, or NULL to target a window. +/// @param flags kCtx flags. +/// +/// @return false if switching failed (only possible for a window target). +bool ctx_switch(CtxSwitch *cs, win_T *wp, tabpage_T *tp, buf_T *buf, CtxSwitchFlags flags) +{ + assert((wp == NULL) != (buf == NULL)); + assert(buf == NULL || tp == NULL); // a buffer target never switches tabpage + CLEAR_POINTER(cs); + cs->cs_flags = flags; + cs->cs_mode = buf != NULL ? kCtxSwitchBuf : kCtxSwitchWin; + cs->cs_ctxwin_idx = -1; + + // Resolve the target window. A buffer target prefers a window already showing "buf" in the + // current tabpage (least side effects, esp. if "buf" is curbuf); when there is none, an autocmd + // window is prepared below, after the save (entering it changes curwin and prevwin). + if (buf != NULL) { + if (buf == curbuf) { // be quick when buf is curbuf + wp = curwin; + } else { + FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) { + if (wp2->w_buffer == buf) { + wp = wp2; + break; + } + } + } + } + + if ((flags & kCtxValidate) && wp != NULL) { + cs->cs_target_win = wp->handle; + cs->cs_target_old_pos = wp->w_cursor; + } + if (flags & kCtxKeepCwd) { + ctx_cwd_save(cs, wp, tp == NULL ? curtab : tp); + } + + // Save the current state. + cs->cs_curwin = curwin->handle; + cs->cs_prevwin = prevwin == NULL ? 0 : prevwin->handle; + cs->cs_same_win = wp == curwin; + if (bt_prompt(curbuf)) { + cs->cs_prompt_insert = curbuf->b_prompt_insert; + } + if (!cs->cs_same_win) { + // Disable Visual selection, because redrawing may fail. + cs->cs_visual_active = VIsual_active; + VIsual_active = false; + } + + if (flags & kCtxNoEvents) { + block_autocmds(); + } + if (tp != NULL) { + cs->cs_curtab = curtab; + if (flags & kCtxNoDisplay) { + unuse_tabpage(curtab); + use_tabpage(tp); + } else { + goto_tabpage_tp(tp, false, false); + } + } + + if (buf != NULL) { + if (wp == NULL) { + // No window shows "buf": prepare an autocmd window. Anything related to a window (e.g., + // setting folds) may have unexpected results. + wp = ctx_win_prep(cs, buf); + } + assert(win_valid(wp)); + } else if (!win_valid(wp)) { + return false; + } + curwin = wp; + curbuf = curwin->w_buffer; + cs->cs_new_curwin = curwin->handle; + set_bufref(&cs->cs_new_curbuf, curbuf); + + if (cs->cs_mode == kCtxSwitchBuf && cs->cs_new_curwin != cs->cs_curwin) { + _ctx_saved_curwin = _ctx_switch_depth == 0 ? cs->cs_curwin : _ctx_saved_curwin; + _ctx_switch_depth++; + } + if (flags & kCtxValidate) { + check_cursor(curwin); + } + return true; +} + +/// Undoes ctx_switch(): restores the previous location (if possible) and the kept state. +/// +/// No-op if `cs` was zero-initialized, even if ctx_switch() was not called on it: +/// +/// CtxSwitch cs = { 0 }; +/// if (some_condition) { +/// ctx_switch(&cs, NULL, NULL, buf, 0); +/// } +/// ... +/// ctx_restore(&cs); // no-op if ctx_switch() was skipped. +/// +/// Legacy: restore_win()/restore_win_noblock(), aucmd_restbuf(), win_execute_after(). +void ctx_restore(CtxSwitch *cs) +{ + if (cs->cs_mode == kCtxSwitchNone) { + return; // zero-initialized: ctx_switch() was never called on `cs`. + } + + if (cs->cs_mode == kCtxSwitchWin) { + // Window target: restore tabpage and curwin. + if (cs->cs_curtab != NULL && valid_tabpage(cs->cs_curtab)) { + if (cs->cs_flags & kCtxNoDisplay) { + win_T *const old_tp_curwin = curtab->tp_curwin; + + unuse_tabpage(curtab); + // Don't change the curwin of the tabpage we temporarily visited. + curtab->tp_curwin = old_tp_curwin; + use_tabpage(cs->cs_curtab); + } else { + goto_tabpage_tp(cs->cs_curtab, false, false); + } + } + + // Look up the window by handle: the user code may have closed it, and + // its memory been reused for another window. + win_T *const save_curwin = win_find_by_handle(cs->cs_curwin); + if (save_curwin != NULL) { + curwin = save_curwin; + curbuf = curwin->w_buffer; + } + } else if (cs->cs_ctxwin_idx >= 0) { + win_T *cwp = ctx_win_rest(cs); + + win_T *const save_curwin = win_find_by_handle(cs->cs_curwin); + if (save_curwin != NULL) { + curwin = save_curwin; + } else { + // Hmm, original window disappeared. Just use the first one. + curwin = firstwin; + } + curbuf = curwin->w_buffer; + // May need to restore insert mode for a prompt buffer. + entering_window(curwin); + if (bt_prompt(curbuf)) { + curbuf->b_prompt_insert = cs->cs_prompt_insert; + } + + prevwin = win_find_by_handle(cs->cs_prevwin); + vars_clear(&cwp->w_vars->dv_hashtab); // free all w: variables + hash_init(&cwp->w_vars->dv_hashtab); // re-use the hashtab + + // If :lcd has been used in the autocommand window, correct current + // directory before restoring tp_localdir and globaldir. + if (cwp->w_localdir != NULL) { + win_fix_current_dir(); + } + xfree(curtab->tp_localdir); + curtab->tp_localdir = cs->cs_tp_localdir; + xfree(globaldir); + globaldir = cs->cs_globaldir; + + // Buffer contents may have changed; cursor is checked below, AFTER restoring Visual state. + if (curwin->w_topline > curbuf->b_ml.ml_line_count) { + curwin->w_topline = curbuf->b_ml.ml_line_count; + curwin->w_topfill = 0; + } + } else { + // Restore curwin. Use the window ID, a window may have been closed + // and the memory re-used for another one. + win_T *const save_curwin = win_find_by_handle(cs->cs_curwin); + if (save_curwin != NULL) { + // Restore the buffer which was previously edited by curwin, if it was + // changed, we are still the same window and the buffer is valid. + if (curwin->handle == cs->cs_new_curwin + && curbuf != cs->cs_new_curbuf.br_buf + && bufref_valid(&cs->cs_new_curbuf) + && cs->cs_new_curbuf.br_buf->b_ml.ml_mfp != NULL) { + if (curwin->w_s == &curbuf->b_s) { + curwin->w_s = &cs->cs_new_curbuf.br_buf->b_s; + } + curbuf->b_nwindows--; + curbuf = cs->cs_new_curbuf.br_buf; + curwin->w_buffer = curbuf; + curbuf->b_nwindows++; + } + + curwin = save_curwin; + curbuf = curwin->w_buffer; + prevwin = win_find_by_handle(cs->cs_prevwin); + } + } + + if (!cs->cs_same_win) { + VIsual_active = cs->cs_visual_active; + } + if (cs->cs_mode == kCtxSwitchBuf) { + check_cursor(curwin); // just in case lines got deleted + if (VIsual_active) { + check_pos(curbuf, &VIsual); + } + } + + // Release what ctx_switch() engaged (any target kind). + if (cs->cs_flags & kCtxNoEvents) { + unblock_autocmds(); + } + if (cs->cs_flags & kCtxKeepCwd) { + ctx_cwd_restore(cs); + } + if (cs->cs_flags & kCtxValidate) { + // Update the status line if the cursor moved in the target window. + win_T *const wp = win_find_by_handle(cs->cs_target_win); + if (wp != NULL && !equalpos(cs->cs_target_old_pos, wp->w_cursor)) { + wp->w_redr_status = true; + } + // In case the code moved the cursor or changed the Visual area, check it is valid. + check_cursor(curwin); + if (VIsual_active) { + check_pos(curbuf, &VIsual); + } + } + if (cs->cs_mode == kCtxSwitchBuf && cs->cs_new_curwin != cs->cs_curwin) { + assert(_ctx_switch_depth > 0); + _ctx_switch_depth--; + _ctx_saved_curwin = _ctx_switch_depth == 0 ? 0 : _ctx_saved_curwin; + } +} diff --git a/src/nvim/context.h b/src/nvim/context.h index 54aa5e50e9..3dd1930f8a 100644 --- a/src/nvim/context.h +++ b/src/nvim/context.h @@ -2,35 +2,14 @@ #include // IWYU pragma: keep -#include "klib/kvec.h" -#include "nvim/api/private/defs.h" - -typedef struct { - String regs; ///< Registers. - String jumps; ///< Jumplist. - String bufs; ///< Buffer list. - String gvars; ///< Global variables. - Array funcs; ///< Functions. -} Context; -typedef kvec_t(Context) ContextVec; - -#define CONTEXT_INIT (Context) { \ - .regs = STRING_INIT, \ - .jumps = STRING_INIT, \ - .bufs = STRING_INIT, \ - .gvars = STRING_INIT, \ - .funcs = ARRAY_DICT_INIT, \ -} - -typedef enum { - kCtxRegs = 1, ///< Registers - kCtxJumps = 2, ///< Jumplist - kCtxBufs = 4, ///< Buffer list - kCtxGVars = 8, ///< Global variables - kCtxSFuncs = 16, ///< Script functions - kCtxFuncs = 32, ///< Functions -} ContextTypeFlags; +#include "nvim/context_defs.h" // IWYU pragma: export +#include "nvim/macros_defs.h" extern int kCtxAll; +/// Pool of temporary scratch windows (fka "autocmd windows"), for ctx_switch(). +EXTERN kvec_t(CtxWin) ctx_win_vec INIT( = KV_INITIAL_VALUE); +#define ctx_win (ctx_win_vec.items) +#define CTX_WIN_COUNT ((int)ctx_win_vec.size) + #include "context.h.generated.h" diff --git a/src/nvim/context_defs.h b/src/nvim/context_defs.h new file mode 100644 index 0000000000..e3add30a85 --- /dev/null +++ b/src/nvim/context_defs.h @@ -0,0 +1,95 @@ +#pragma once + +#include + +#include "klib/kvec.h" +#include "nvim/api/private/defs.h" +#include "nvim/buffer_defs.h" +#include "nvim/os/os_defs.h" +#include "nvim/pos_defs.h" +#include "nvim/types_defs.h" + +typedef struct { + String regs; ///< Registers. + String jumps; ///< Jumplist. + String bufs; ///< Buffer list. + String gvars; ///< Global variables. + Array funcs; ///< Functions. +} Context; +typedef kvec_t(Context) ContextVec; + +#define CONTEXT_INIT (Context) { \ + .regs = STRING_INIT, \ + .jumps = STRING_INIT, \ + .bufs = STRING_INIT, \ + .gvars = STRING_INIT, \ + .funcs = ARRAY_DICT_INIT, \ +} + +typedef enum { + kCtxRegs = 1, ///< Registers + kCtxJumps = 2, ///< Jumplist + kCtxBufs = 4, ///< Buffer list + kCtxGVars = 8, ///< Global variables + kCtxSFuncs = 16, ///< Script functions + kCtxFuncs = 32, ///< Functions +} CtxStateFlags; + +/// Temporary, hidden window (fka "autocmd window"): a pooled window created to temporarily show +/// a buffer that has no window (ctx_switch() on a buffer target), to handle the side effects. When +/// switches nest we may need more than one. +typedef struct { + win_T *cw_win; ///< The window, or NULL if not yet allocated. + bool cw_used; ///< Not currently in use. +} CtxWin; + +/// Flags for ctx_switch(). +typedef enum { + /// Don't affect the display (no redraw; limits access to another tabpage). + kCtxNoDisplay = 1, + /// Block autocommands until ctx_restore(). + kCtxNoEvents = 2, + /// Undo any chdir caused by the switch ('autochdir', win/tab-local CWD) on ctx_restore(). + kCtxKeepCwd = 4, + /// Validate cursor/Visual around the switch; update display (statusline) if the target window's + /// cursor moved. + kCtxValidate = 8, +} CtxSwitchFlags; + +/// What ctx_switch() switched (set internally). +enum { + kCtxSwitchNone = 0, ///< zero-initialized: ctx_restore() is a no-op + kCtxSwitchWin, ///< window target + kCtxSwitchBuf, ///< buffer target +}; + +/// Context before a temporary switch of current window/buffer. Undone by ctx_restore(). +typedef struct { + CtxSwitchFlags cs_flags; ///< kCtx* flags of the switch + int cs_mode; ///< kCtxSwitch* (what was switched) + // Saved location: + handle_T cs_curwin; ///< saved curwin + handle_T cs_prevwin; ///< saved prevwin (ctx_switch()) + tabpage_T *cs_curtab; ///< saved curtab (NULL: tabpage unchanged) + bool cs_same_win; ///< VIsual_active was not reset + bool cs_visual_active; ///< saved VIsual_active + int cs_prompt_insert; ///< saved b_prompt_insert + // Temporary location (ctx_switch()): + handle_T cs_new_curwin; ///< ID of new curwin + bufref_T cs_new_curbuf; ///< new curbuf + int cs_ctxwin_idx; ///< autocmd window in ctx_win[], or -1 + // Target tracking (kCtxValidate): + handle_T cs_target_win; ///< the window switched to + pos_T cs_target_old_pos; ///< its cursor before the switch + // State kept across the switch: + char *cs_tp_localdir; ///< saved tp_localdir (autocmd window) + char *cs_globaldir; ///< saved globaldir (autocmd window) + char *cs_cwd; ///< saved cwd (kCtxKeepCwd; allocated on demand) + int cs_cwd_status; ///< OK if cs_cwd is valid + bool cs_apply_acd; ///< re-apply 'autochdir' on ctx_restore() + char *cs_save_sfname; ///< saved b_sfname (kCtxKeepCwd) +} CtxSwitch; + +// TODO(justinmk): legacy aliases, to avoid churn. They will be unalived soooon. +typedef CtxSwitch aco_save_T; +typedef CtxSwitch switchwin_T; diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index 71949e888c..711691f530 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -68,6 +68,7 @@ #include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" +#include "nvim/context.h" #include "nvim/decoration.h" #include "nvim/decoration_defs.h" #include "nvim/decoration_provider.h" @@ -466,7 +467,7 @@ int update_screen(void) } // Restore actual curwin before redrawing. - win_T *save_curwin = autocmd_save_curwin ? win_find_by_handle(autocmd_save_curwin) : NULL; + win_T *save_curwin = ctx_saved_curwin(); win_T *restore_curwin = save_curwin != NULL ? curwin : NULL; if (save_curwin != NULL) { curwin = save_curwin; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index c0339e9e1d..e815c450d5 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -20,6 +20,7 @@ #include "nvim/channel.h" #include "nvim/charset.h" #include "nvim/cmdexpand_defs.h" +#include "nvim/context.h" #include "nvim/cursor.h" #include "nvim/edit.h" #include "nvim/errors.h" @@ -4070,9 +4071,9 @@ bool garbage_collect(bool testing) ABORTING(set_ref_in_item)(&wp->w_winvar.di_tv, copyID, NULL, NULL); } // window-local variables in autocmd windows - for (int i = 0; i < AUCMD_WIN_COUNT; i++) { - if (aucmd_win[i].auc_win != NULL) { - ABORTING(set_ref_in_item)(&aucmd_win[i].auc_win->w_winvar.di_tv, copyID, NULL, NULL); + for (int i = 0; i < CTX_WIN_COUNT; i++) { + if (ctx_win[i].cw_win != NULL) { + ABORTING(set_ref_in_item)(&ctx_win[i].cw_win->w_winvar.di_tv, copyID, NULL, NULL); } } diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index d8bf95105c..0f6e3aebbf 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -872,7 +872,7 @@ static void f_ctxget(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "ctxpop()" function static void f_ctxpop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - if (!ctx_restore(NULL, kCtxAll)) { + if (!ctx_load(NULL, kCtxAll)) { emsg(_("Context stack is empty")); } } diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c index a43d7285f5..d5494eacb0 100644 --- a/src/nvim/eval/window.c +++ b/src/nvim/eval/window.c @@ -10,6 +10,7 @@ #include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/charset.h" +#include "nvim/context.h" #include "nvim/cursor.h" #include "nvim/errors.h" #include "nvim/eval/funcs.h" @@ -503,78 +504,6 @@ void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_number = nr; } -/// Switch to a window for executing user code. -/// Caller must call win_execute_after() later regardless of return value. -/// -/// @return whether switching the window succeeded. -bool win_execute_before(win_execute_T *args, win_T *wp, tabpage_T *tp) -{ - args->wp = wp; - args->curpos = wp->w_cursor; - args->cwd_status = FAIL; - args->apply_acd = false; - args->save_sfname = NULL; - - // Getting and setting directory can be slow on some systems, only do - // this when the current or target window/tab have a local directory or - // 'acd' is set. - if (curwin != wp - && (curwin->w_localdir != NULL || wp->w_localdir != NULL - || (curtab != tp && (curtab->tp_localdir != NULL || tp->tp_localdir != NULL)) - || p_acd)) { - args->cwd_status = os_dirname(args->cwd, MAXPATHL); - } - - // If 'acd' is set, check we are using that directory. If yes, then - // apply 'acd' afterwards, otherwise restore the current directory. - if (args->cwd_status == OK && p_acd) { - if (curbuf->b_sfname != NULL && curbuf->b_fname == curbuf->b_sfname) { - args->save_sfname = xstrdup(curbuf->b_sfname); - } - do_autochdir(); - char autocwd[MAXPATHL]; - if (os_dirname(autocwd, MAXPATHL) == OK) { - args->apply_acd = strcmp(args->cwd, autocwd) == 0; - } - } - - if (switch_win_noblock(&args->switchwin, wp, tp, true) == OK) { - check_cursor(curwin); - return true; - } - return false; -} - -/// Restore the previous window after executing user code. -void win_execute_after(win_execute_T *args) -{ - restore_win_noblock(&args->switchwin, true); - - if (args->apply_acd) { - xfree(args->save_sfname); - do_autochdir(); - } else if (args->cwd_status == OK) { - os_chdir(args->cwd); - if (args->save_sfname != NULL) { - xfree(curbuf->b_sfname); - curbuf->b_sfname = args->save_sfname; - curbuf->b_fname = curbuf->b_sfname; - } - } - - // Update the status line if the cursor moved. - if (win_valid(args->wp) && !equalpos(args->curpos, args->wp->w_cursor)) { - args->wp->w_redr_status = true; - } - - // In case the command moved the cursor or changed the Visual area, - // check it is valid. - check_cursor(curwin); - if (VIsual_active) { - check_pos(curbuf, &VIsual); - } -} - /// "win_execute(win_id, command)" function void f_win_execute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { @@ -589,11 +518,11 @@ void f_win_execute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } - win_execute_T win_execute_args; - if (win_execute_before(&win_execute_args, wp, tp)) { + CtxSwitch cs; + if (ctx_switch(&cs, wp, tp, NULL, kCtxNoDisplay | kCtxKeepCwd | kCtxValidate)) { execute_common(argvars, rettv, 1); } - win_execute_after(&win_execute_args); + ctx_restore(&cs); } /// "win_findbuf()" function @@ -735,7 +664,7 @@ void f_win_splitmove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } // Check if we're allowed to continue before we bother switching windows. - if (is_aucmd_win(wp) || text_or_buf_locked() || check_split_disallowed(wp) == FAIL) { + if (is_ctx_win(wp) || text_or_buf_locked() || check_split_disallowed(wp) == FAIL) { return; } @@ -771,7 +700,7 @@ void f_win_gettype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) return; } } - if (is_aucmd_win(wp)) { + if (is_ctx_win(wp)) { rettv->vval.v_string = xstrdup("autocmd"); } else if (wp->w_p_pvw) { rettv->vval.v_string = xstrdup("preview"); @@ -962,81 +891,30 @@ void f_winwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } -/// Set "win" to be the curwin and "tp" to be the current tab page. -/// restore_win() MUST be called to undo, also when FAIL is returned. -/// No autocommands will be executed until restore_win() is called. -/// -/// @param no_display if true the display won't be affected, no redraw is -/// triggered, another tabpage access is limited. -/// -/// @return FAIL if switching to "win" failed. +/// TODO(justinmk): legacy shim, use ctx_switch() directly. int switch_win(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_display) { - block_autocmds(); - return switch_win_noblock(switchwin, win, tp, no_display); + return ctx_switch(switchwin, win, tp, NULL, kCtxNoEvents | (no_display ? kCtxNoDisplay : 0)) + ? OK : FAIL; } -// As switch_win() but without blocking autocommands. +// TODO(justinmk): legacy shim, use ctx_switch() directly. int switch_win_noblock(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_display) { - CLEAR_POINTER(switchwin); - switchwin->sw_curwin = curwin; - if (win == curwin) { - switchwin->sw_same_win = true; - } else { - // Disable Visual selection, because redrawing may fail. - switchwin->sw_visual_active = VIsual_active; - VIsual_active = false; - } - - if (tp != NULL) { - switchwin->sw_curtab = curtab; - if (no_display) { - unuse_tabpage(curtab); - use_tabpage(tp); - } else { - goto_tabpage_tp(tp, false, false); - } - } - if (!win_valid(win)) { - return FAIL; - } - curwin = win; - curbuf = curwin->w_buffer; - return OK; + return ctx_switch(switchwin, win, tp, NULL, no_display ? kCtxNoDisplay : 0) + ? OK : FAIL; } -/// Restore current tabpage and window saved by switch_win(), if still valid. -/// When "no_display" is true the display won't be affected, no redraw is -/// triggered. +/// TODO(justinmk): legacy shim, use ctx_restore() directly. void restore_win(switchwin_T *switchwin, bool no_display) { - restore_win_noblock(switchwin, no_display); - unblock_autocmds(); + (void)no_display; // recorded by ctx_switch() + ctx_restore(switchwin); } -/// As restore_win() but without unblocking autocommands. +/// TODO(justinmk): legacy shim, use ctx_restore() directly. void restore_win_noblock(switchwin_T *switchwin, bool no_display) { - if (switchwin->sw_curtab != NULL && valid_tabpage(switchwin->sw_curtab)) { - if (no_display) { - win_T *const old_tp_curwin = curtab->tp_curwin; - - unuse_tabpage(curtab); - // Don't change the curwin of the tabpage we temporarily visited. - curtab->tp_curwin = old_tp_curwin; - use_tabpage(switchwin->sw_curtab); - } else { - goto_tabpage_tp(switchwin->sw_curtab, false, false); - } - } - - if (!switchwin->sw_same_win) { - VIsual_active = switchwin->sw_visual_active; - } - - if (win_valid(switchwin->sw_curwin)) { - curwin = switchwin->sw_curwin; - curbuf = curwin->w_buffer; - } + (void)no_display; // recorded by ctx_switch() + ctx_restore(switchwin); } diff --git a/src/nvim/eval/window.h b/src/nvim/eval/window.h index 888c7a596a..e5e58f25f7 100644 --- a/src/nvim/eval/window.h +++ b/src/nvim/eval/window.h @@ -3,28 +3,10 @@ #include #include "nvim/buffer_defs.h" +#include "nvim/context_defs.h" // IWYU pragma: keep (switchwin_T) #include "nvim/eval/typval_defs.h" // IWYU pragma: keep #include "nvim/os/os_defs.h" #include "nvim/pos_defs.h" #include "nvim/types_defs.h" -/// Structure used by switch_win() to pass values to restore_win() -typedef struct { - win_T *sw_curwin; - tabpage_T *sw_curtab; - bool sw_same_win; ///< VIsual_active was not reset - bool sw_visual_active; -} switchwin_T; - -/// Structure used by win_execute_before() to pass values to win_execute_after() -typedef struct { - win_T *wp; - pos_T curpos; - char cwd[MAXPATHL]; - int cwd_status; - bool apply_acd; - char *save_sfname; - switchwin_T switchwin; -} win_execute_T; - #include "eval/window.h.generated.h" diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 605db95ba2..c4c1fcc62f 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -31,6 +31,7 @@ #include "nvim/clipboard.h" #include "nvim/cmdexpand.h" #include "nvim/cmdexpand_defs.h" +#include "nvim/context.h" #include "nvim/cursor.h" #include "nvim/debugger.h" #include "nvim/digraph.h" @@ -5268,7 +5269,7 @@ static void ex_pclose(exarg_T *eap) void ex_win_close(int forceit, win_T *win, tabpage_T *tp) { // Never close the autocommand window. - if (is_aucmd_win(win)) { + if (is_ctx_win(win)) { emsg(_(e_autocmd_close)); return; } diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c index c4d98057ab..4efcb3228d 100644 --- a/src/nvim/lua/stdlib.c +++ b/src/nvim/lua/stdlib.c @@ -8,6 +8,7 @@ #include #include +#include "nvim/context.h" #include "nvim/log.h" #ifdef NVIM_VENDOR_BIT # include "bit.h" @@ -639,29 +640,25 @@ static int nlua_with(lua_State *L) Error err = ERROR_INIT; TRY_WRAP(&err, { - aco_save_T aco = { 0 }; - win_execute_T win_execute_args; + CtxSwitch cs = { 0 }; + bool switched = true; if (win) { tabpage_T *tabpage = win_find_tabpage(win); - if (!win_execute_before(&win_execute_args, win, tabpage)) { - goto end; - } + switched = ctx_switch(&cs, win, tabpage, NULL, kCtxNoDisplay | kCtxKeepCwd | kCtxValidate); } else if (buf) { - aucmd_prepbuf(&aco, buf); + ctx_switch(&cs, NULL, NULL, buf, 0); } - int s = lua_gettop(L); - lua_pushvalue(L, 2); - status = lua_pcall(L, 0, LUA_MULTRET, 0); - rets = lua_gettop(L) - s; - - if (win) { - win_execute_after(&win_execute_args); - } else if (buf) { - aucmd_restbuf(&aco); + if (switched) { + int s = lua_gettop(L); + lua_pushvalue(L, 2); + status = lua_pcall(L, 0, LUA_MULTRET, 0); + rets = lua_gettop(L) - s; } - end:; + + // No-op if nothing was switched. Restores even when ctx_switch() failed. + ctx_restore(&cs); }); undo_cmdmod(&cmdmod); diff --git a/src/nvim/main.c b/src/nvim/main.c index ae67da64d6..9aed5d0f99 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -33,6 +33,7 @@ #include "nvim/buffer_defs.h" #include "nvim/channel.h" #include "nvim/channel_defs.h" +#include "nvim/context.h" #include "nvim/decoration.h" #include "nvim/decoration_provider.h" #include "nvim/diff.h" diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index 6a4266ddfe..4913dc9a02 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -13,6 +13,7 @@ #include "nvim/buffer.h" #include "nvim/buffer_defs.h" #include "nvim/charset.h" +#include "nvim/context.h" #include "nvim/digraph.h" #include "nvim/drawline.h" #include "nvim/drawscreen.h" @@ -256,7 +257,7 @@ static void win_redr_stl_expr(win_T *wp, bool draw_winbar, bool draw_ruler, bool entered = true; // Restore actual curwin before redrawing. - win_T *save_curwin = autocmd_save_curwin ? win_find_by_handle(autocmd_save_curwin) : NULL; + win_T *save_curwin = ctx_saved_curwin(); win_T *restore_curwin = save_curwin != NULL ? curwin : NULL; if (save_curwin != NULL) { curwin = save_curwin; diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 76decc0900..2e6f226db5 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -51,6 +51,7 @@ #include "nvim/change.h" #include "nvim/channel.h" #include "nvim/channel_defs.h" +#include "nvim/context.h" #include "nvim/cursor.h" #include "nvim/cursor_shape.h" #include "nvim/drawline.h" @@ -783,7 +784,7 @@ void terminal_check_size(Terminal *term) // Check if there is a window that displays the terminal and find the maximum width and height. // Skip the autocommand window which isn't actually displayed. FOR_ALL_TAB_WINDOWS(tp, wp) { - if (is_aucmd_win(wp)) { + if (is_ctx_win(wp)) { continue; } if (wp->w_buffer && wp->w_buffer->terminal == term) { diff --git a/src/nvim/window.c b/src/nvim/window.c index f97308c5fa..7eb883dcd7 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -15,6 +15,7 @@ #include "nvim/buffer.h" #include "nvim/buffer_defs.h" #include "nvim/charset.h" +#include "nvim/context.h" #include "nvim/cursor.h" #include "nvim/decoration.h" #include "nvim/diff.h" @@ -805,14 +806,14 @@ void win_set_buf(win_T *win, buf_T *buf, Error *err) win_result = switch_win_noblock(&switchwin, win, tab, true); if (win_result != FAIL) { const int save_acd = p_acd; - if (!switchwin.sw_same_win) { + if (!switchwin.cs_same_win) { // Temporarily disable 'autochdir' when setting buffer in another window. p_acd = false; } do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0); - if (!switchwin.sw_same_win) { + if (!switchwin.cs_same_win) { p_acd = save_acd; } } @@ -1136,8 +1137,8 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir, frame_T *to_fl { win_T *wp = new_wp; - // aucmd_win[] should always remain floating - if (new_wp != NULL && is_aucmd_win(new_wp)) { + // ctx_win[] should always remain floating + if (new_wp != NULL && is_ctx_win(new_wp)) { return NULL; } @@ -1616,7 +1617,7 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir, frame_T *to_fl // equalize the window sizes. if (do_equal || dir != 0) { win_equal(wp, true, vertical ? (dir == 'v' ? 'b' : 'h') : (dir == 'h' ? 'b' : 'v')); - } else if (!is_aucmd_win(wp)) { + } else if (!is_ctx_win(wp)) { win_fix_scroll(false); } @@ -2049,7 +2050,7 @@ int win_splitmove(win_T *wp, int size, int flags) if (one_window(wp, NULL)) { return OK; // nothing to do } - if (is_aucmd_win(wp) || check_split_disallowed(wp) == FAIL) { + if (is_ctx_win(wp) || check_split_disallowed(wp) == FAIL) { return FAIL; } @@ -2204,7 +2205,7 @@ void win_equal(win_T *next_curwin, bool current, int dir) win_equal_rec(next_curwin == NULL ? curwin : next_curwin, current, topframe, dir, 0, tabline_height(), Columns, topframe->fr_height); - if (!is_aucmd_win(next_curwin)) { + if (!is_ctx_win(next_curwin)) { win_fix_scroll(true); } } @@ -2499,7 +2500,7 @@ void leaving_window(win_T *const win) // Only matters for a prompt window. // Don't do mode changes for a prompt buffer in an autocommand window, as // it's only used temporarily during an autocommand. - if (!bt_prompt(win->w_buffer) || is_aucmd_win(win)) { + if (!bt_prompt(win->w_buffer) || is_ctx_win(win)) { return; } @@ -2528,7 +2529,7 @@ void entering_window(win_T *const win) // Only matters for a prompt window. // Don't do mode changes for a prompt buffer in an autocommand window, as // it's only used temporarily during an autocommand. - if (!bt_prompt(win->w_buffer) || is_aucmd_win(win)) { + if (!bt_prompt(win->w_buffer) || is_ctx_win(win)) { return; } @@ -2579,7 +2580,7 @@ void close_windows(buf_T *buf, bool keep_curwin) // Start from lastwin to close floating windows with the same buffer first. // When the autocommand window is involved win_close() may need to print an error message. - for (win_T *wp = lastwin; wp != NULL && (is_aucmd_win(lastwin) || !one_window(wp, NULL));) { + for (win_T *wp = lastwin; wp != NULL && (is_ctx_win(lastwin) || !one_window(wp, NULL));) { if (wp->w_buffer == buf && (!keep_curwin || wp != curwin) && !(win_locked(wp) || wp->w_buffer->b_locked > 0)) { if (window_layout_locked(CMD_SIZE)) { @@ -2653,7 +2654,7 @@ bool one_window(win_T *win, tabpage_T *tp) /// @return true if all floating windows can be closed static bool can_close_floating_windows(tabpage_T *tp) { - assert(tp != curtab && (tp || !is_aucmd_win(lastwin))); + assert(tp != curtab && (tp || !is_ctx_win(lastwin))); for (win_T *wp = tp ? tp->tp_lastwin : lastwin; wp->w_floating; wp = wp->w_prev) { buf_T *buf = wp->w_buffer; int need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1); @@ -2798,12 +2799,12 @@ int win_close(win_T *win, bool free_buf, bool force) || (win->w_buffer != NULL && win->w_buffer->b_locked > 0)) { return FAIL; // window is already being closed } - if (is_aucmd_win(win)) { + if (is_ctx_win(win)) { emsg(_(e_autocmd_close)); return FAIL; } if (lastwin->w_floating && one_window(win, NULL)) { - if (is_aucmd_win(lastwin)) { + if (is_ctx_win(lastwin)) { emsg(_("E814: Cannot close window, only autocmd window would remain")); return FAIL; } @@ -3166,7 +3167,7 @@ bool win_close_othertab(win_T *win, int free_buf, tabpage_T *tp, bool force) || (win->w_buffer != NULL && win->w_buffer->b_locked > 0)) { return false; // window is already being closed } - if (is_aucmd_win(win)) { + if (is_ctx_win(win)) { emsg(_(e_autocmd_close)); return false; } @@ -3348,22 +3349,22 @@ void win_free_all(void) win_remove(lastwin, NULL); int dummy; win_free_mem(wp, &dummy, NULL); - for (int i = 0; i < AUCMD_WIN_COUNT; i++) { - if (aucmd_win[i].auc_win == wp) { - aucmd_win[i].auc_win = NULL; + for (int i = 0; i < CTX_WIN_COUNT; i++) { + if (ctx_win[i].cw_win == wp) { + ctx_win[i].cw_win = NULL; } } } - for (int i = 0; i < AUCMD_WIN_COUNT; i++) { - if (aucmd_win[i].auc_win != NULL) { + for (int i = 0; i < CTX_WIN_COUNT; i++) { + if (ctx_win[i].cw_win != NULL) { int dummy; - win_free_mem(aucmd_win[i].auc_win, &dummy, NULL); - aucmd_win[i].auc_win = NULL; + win_free_mem(ctx_win[i].cw_win, &dummy, NULL); + ctx_win[i].cw_win = NULL; } } - kv_destroy(aucmd_win_vec); + kv_destroy(ctx_win_vec); while (firstwin != NULL) { int dummy; @@ -4336,9 +4337,9 @@ void win_alloc_first(void) unuse_tabpage(first_tabpage); } -// Init `aucmd_win[idx]`. This can only be done after the first window +// Init `ctx_win[idx]`. This can only be done after the first window // is fully initialized, thus it can't be in win_alloc_first(). -void win_alloc_aucmd_win(int idx) +void win_alloc_ctx_win(int idx) { Error err = ERROR_INIT; WinConfig fconfig = WIN_CONFIG_INIT; @@ -4347,9 +4348,9 @@ void win_alloc_aucmd_win(int idx) fconfig.focusable = false; fconfig.mouse = false; fconfig.hide = true; - aucmd_win[idx].auc_win = win_new_float(NULL, true, fconfig, &err); - aucmd_win[idx].auc_win->w_buffer->b_nwindows--; - RESET_BINDING(aucmd_win[idx].auc_win); + ctx_win[idx].cw_win = win_new_float(NULL, true, fconfig, &err); + ctx_win[idx].cw_win->w_buffer->b_nwindows--; + RESET_BINDING(ctx_win[idx].cw_win); } // Allocate the first window or the first window in a new tab page. @@ -7413,7 +7414,7 @@ int min_rows_for_all_tabpages(void) /// Check that there is only one window (and only one tab page), not counting a /// help or preview window, unless it is the current window. Does not count -/// "aucmd_win". Does not count floats unless it is current. +/// "ctx_win". Does not count floats unless it is current. bool only_one_window(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { // If there is another tab page there always is another window. @@ -7425,7 +7426,7 @@ bool only_one_window(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_buffer != NULL && (!((bt_help(wp->w_buffer) && !bt_help(curbuf)) || wp->w_floating - || wp->w_p_pvw) || wp == curwin) && !is_aucmd_win(wp)) { + || wp->w_p_pvw) || wp == curwin) && !is_ctx_win(wp)) { count++; } } diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua index a81c1ed1c7..1ed77c41ff 100644 --- a/test/functional/api/buffer_spec.lua +++ b/test/functional/api/buffer_spec.lua @@ -2553,6 +2553,16 @@ describe('api/buf', function() end) describe('nvim_buf_call', function() + it('preserves visual-mode, unless the callback ended it', function() + -- Same-buffer: Visual survives untouched. + command('normal! v') + exec_lua('vim.api.nvim_buf_call(0, function() end)') + eq('v', fn.mode()) + -- The callback ends Visual mode: it must STAY ended. + exec_lua([[vim.api.nvim_buf_call(0, function() vim.cmd('normal! \27') end)]]) + eq('n', fn.mode()) + end) + it('supports multiple returns', function() local curbuf = api.nvim_get_current_buf() local other = api.nvim_create_buf(false, true)