mirror of
https://github.com/neovim/neovim.git
synced 2026-08-27 01:21:48 +00:00
refactor: unify context-switching concepts
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
95
src/nvim/context_defs.h
Normal 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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user