refactor: unify context-switching concepts

This commit is contained in:
Justin M. Keyes
2026-07-09 19:50:16 +02:00
parent 982d2f2531
commit 6bb1aa74e6
25 changed files with 691 additions and 633 deletions

View File

@@ -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

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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
}

View File

@@ -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.

View File

@@ -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;

View File

@@ -41,21 +41,6 @@ EXTERN bool autocmd_fname_full INIT( = false); ///< autocmd_fname is full path
EXTERN int autocmd_bufnr INIT( = 0); ///< fnum for <abuf> on cmdline
EXTERN char *autocmd_match INIT( = NULL); ///< name for <amatch> 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

View File

@@ -5,24 +5,11 @@
#include <stdint.h>
#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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 <assert.h>
#include <stdbool.h>
@@ -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, "<lambda>", 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, "<lambda>", 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;
}
}

View File

@@ -2,35 +2,14 @@
#include <stddef.h> // 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"

95
src/nvim/context_defs.h Normal file
View File

@@ -0,0 +1,95 @@
#pragma once
#include <stdbool.h>
#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;

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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"));
}
}

View File

@@ -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);
}

View File

@@ -3,28 +3,10 @@
#include <stdbool.h>
#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"

View File

@@ -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;
}

View File

@@ -8,6 +8,7 @@
#include <string.h>
#include <uv.h>
#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);

View File

@@ -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"

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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++;
}
}

View File

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