mirror of
https://github.com/neovim/neovim.git
synced 2026-08-25 00:21:55 +00:00
feat(vim._with): keepcwd
This commit is contained in:
@@ -1480,6 +1480,7 @@ end
|
||||
--- @field go? table<string, any>
|
||||
--- @field hide? boolean
|
||||
--- @field keepalt? boolean
|
||||
--- @field keepcwd? boolean
|
||||
--- @field keepjumps? boolean
|
||||
--- @field keepmarks? boolean
|
||||
--- @field keeppatterns? boolean
|
||||
@@ -1537,9 +1538,6 @@ end
|
||||
--- indicated by the spec is restored.
|
||||
---
|
||||
--- Notes:
|
||||
--- - If `buf`/`win` is specified, CWD state (win/buf/tab-local dirs) is restored after execution.
|
||||
--- Any :cd/:tcd/:lcd/:bcd during execution is undone.
|
||||
--- - TODO: allow opt-out? Workaround: use nvim_buf_call()/nvim_win_call().
|
||||
--- - Context `{ buf = buf }` has no guarantees about current window when
|
||||
--- inside context.
|
||||
--- - Context `{ buf = buf, win = win }` is yet not allowed, but this seems
|
||||
@@ -1571,6 +1569,7 @@ function vim._with(context, f)
|
||||
vim.validate('context.go', context.go, 'table', true)
|
||||
vim.validate('context.hide', context.hide, 'boolean', true)
|
||||
vim.validate('context.keepalt', context.keepalt, 'boolean', true)
|
||||
vim.validate('context.keepcwd', context.keepcwd, 'boolean', true)
|
||||
vim.validate('context.keepjumps', context.keepjumps, 'boolean', true)
|
||||
vim.validate('context.keepmarks', context.keepmarks, 'boolean', true)
|
||||
vim.validate('context.keeppatterns', context.keeppatterns, 'boolean', true)
|
||||
|
||||
@@ -497,38 +497,42 @@ 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.
|
||||
/// Prepares 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().)
|
||||
/// - Passing neither: only CWD state is saved; flags must include `kCtxKeepDirs`.
|
||||
///
|
||||
/// 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 wp Target window, or NULL.
|
||||
/// @param tp Tabpage of `wp`, or NULL to not switch tabpage.
|
||||
/// @param buf Target buffer, or NULL to target a window.
|
||||
/// @param buf Target buffer, or NULL.
|
||||
/// @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));
|
||||
// Exactly one target, or none with kCtxKeepDirs (which only saves the CWD state).
|
||||
assert(((wp == NULL) != (buf == NULL)) || (wp == NULL && (flags & kCtxKeepDirs)));
|
||||
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_mode = buf != NULL ? kCtxSwitchBuf : wp != NULL ? kCtxSwitchWin : kCtxSwitchDirs;
|
||||
cs->cs_ctxwin_idx = -1;
|
||||
cs->cs_did_chdir = _ctx_did_chdir;
|
||||
_ctx_did_chdir = false;
|
||||
if (cs->cs_mode == kCtxSwitchDirs) {
|
||||
wp = curwin; // No target: "switch" to curwin, i.e. stay put.
|
||||
}
|
||||
|
||||
// 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).
|
||||
// Resolve the target window. A buffer target prefers a window already showing it, in the current
|
||||
// tabpage (minimizes side effects); else a ctx_win is prepared below (ctx_win_prep).
|
||||
if (buf != NULL) {
|
||||
if (buf == curbuf) { // be quick when buf is curbuf
|
||||
wp = curwin;
|
||||
@@ -546,8 +550,8 @@ bool ctx_switch(CtxSwitch *cs, win_T *wp, tabpage_T *tp, buf_T *buf, CtxSwitchFl
|
||||
cs->cs_target_win = wp->handle;
|
||||
cs->cs_target_old_pos = wp->w_cursor;
|
||||
}
|
||||
// The CWD-state snapshot is only for targets with a real window: hidden-buffer targets are
|
||||
// handled by the ctx_win machinery instead (see ctx_win_prep()).
|
||||
// The CWD-state snapshot is only for a real window target; hidden-buffer target is handled by the
|
||||
// ctx_win machinery (ctx_win_prep).
|
||||
if (buf == NULL || wp != NULL) {
|
||||
ctx_dirs_save(cs, wp, tp == NULL ? curtab : tp, buf);
|
||||
}
|
||||
@@ -580,8 +584,8 @@ bool ctx_switch(CtxSwitch *cs, win_T *wp, tabpage_T *tp, buf_T *buf, CtxSwitchFl
|
||||
|
||||
if (buf != NULL) {
|
||||
if (wp == NULL) {
|
||||
// No window shows `buf`: prepare a temp window. Anything related to a window (e.g., setting
|
||||
// folds) may have unexpected results.
|
||||
// Hidden buffer (`buf` not visible in any window): prepare a temp window.
|
||||
// Window behavior (e.g., setting folds) may have unexpected results.
|
||||
wp = ctx_win_prep(cs, buf);
|
||||
// Leave the window we entered "from".
|
||||
leaving_window(curwin);
|
||||
@@ -677,7 +681,7 @@ void ctx_restore(CtxSwitch *cs)
|
||||
curwin->w_topline = curbuf->b_ml.ml_line_count;
|
||||
curwin->w_topfill = 0;
|
||||
}
|
||||
} else {
|
||||
} else if (cs->cs_mode == kCtxSwitchBuf) {
|
||||
// Restore the buffer previously edited by curwin.
|
||||
if (curwin->handle == cs->cs_new_curwin
|
||||
&& curbuf != cs->cs_new_curbuf.br_buf
|
||||
@@ -693,7 +697,7 @@ void ctx_restore(CtxSwitch *cs)
|
||||
}
|
||||
|
||||
ctx_restore_curwin(cs, NULL);
|
||||
}
|
||||
} // Else: only save CWD state.
|
||||
|
||||
if (!cs->cs_same_win) {
|
||||
Visual.active = cs->cs_visual_active;
|
||||
|
||||
@@ -65,9 +65,10 @@ typedef enum {
|
||||
|
||||
/// What ctx_switch() switched (set internally).
|
||||
enum {
|
||||
kCtxSwitchNone = 0, ///< zero-initialized: ctx_restore() is a no-op
|
||||
kCtxSwitchWin, ///< window target
|
||||
kCtxSwitchBuf, ///< buffer target
|
||||
kCtxSwitchNone = 0, ///< Zero-initialized: ctx_restore() is a no-op.
|
||||
kCtxSwitchWin, ///< Window target.
|
||||
kCtxSwitchBuf, ///< Buffer target.
|
||||
kCtxSwitchDirs, ///< No target: only CWD state is saved.
|
||||
};
|
||||
|
||||
/// Context before a temporary switch of current window/buffer. Undone by ctx_restore().
|
||||
@@ -84,7 +85,7 @@ typedef struct {
|
||||
// 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.
|
||||
int cs_ctxwin_idx; ///< "autocmd" window in the ctx_win pool, 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
|
||||
|
||||
@@ -586,6 +586,7 @@ static int nlua_with(lua_State *L)
|
||||
int flags = 0;
|
||||
buf_T *buf = NULL;
|
||||
win_T *win = NULL;
|
||||
bool keepcwd = false;
|
||||
int log_level = -1;
|
||||
|
||||
#define APPLY_FLAG(key, flag) \
|
||||
@@ -604,6 +605,8 @@ static int nlua_with(lua_State *L)
|
||||
buf = handle_get_buffer((int)luaL_checkinteger(L, -1));
|
||||
} else if (strequal("win", k)) {
|
||||
win = handle_get_window((int)luaL_checkinteger(L, -1));
|
||||
} else if (strequal("keepcwd", k)) {
|
||||
keepcwd = v;
|
||||
} else if (strequal("log_level", k)) {
|
||||
log_level = (int)luaL_checkinteger(L, -1);
|
||||
} else {
|
||||
@@ -645,13 +648,15 @@ static int nlua_with(lua_State *L)
|
||||
TRY_WRAP(&err, {
|
||||
CtxSwitch cs = { 0 };
|
||||
bool switched = true;
|
||||
CtxSwitchFlags dirs = keepcwd ? kCtxKeepDirs : kCtxKeepCwd;
|
||||
|
||||
if (win) {
|
||||
tabpage_T *tabpage = win_find_tabpage(win);
|
||||
switched = ctx_switch(&cs, win, tabpage, NULL,
|
||||
kCtxNoDisplay | kCtxValidate | kCtxKeepDirs);
|
||||
switched = ctx_switch(&cs, win, tabpage, NULL, kCtxNoDisplay | kCtxValidate | dirs);
|
||||
} else if (buf) {
|
||||
ctx_switch(&cs, NULL, NULL, buf, kCtxKeepDirs);
|
||||
ctx_switch(&cs, NULL, NULL, buf, dirs);
|
||||
} else if (keepcwd) {
|
||||
ctx_switch(&cs, NULL, NULL, NULL, kCtxKeepDirs);
|
||||
}
|
||||
|
||||
if (switched) {
|
||||
|
||||
@@ -345,30 +345,25 @@ describe('vim._with', function()
|
||||
eq(true, out)
|
||||
end)
|
||||
|
||||
it('restores CWD state', function()
|
||||
it('keeps ":bcd" on the target buffer', function()
|
||||
local out = exec_lua [[
|
||||
local other_buf, cur_buf = setup_buffers()
|
||||
local err_buf = api.nvim_create_buf(false, true)
|
||||
local cwd = fn.getcwd()
|
||||
local dir = vim.fs.joinpath(cwd, 'test')
|
||||
-- ":bcd" on the target buffer is discarded: hidden target, (nested) visible target, and
|
||||
-- when the callback errors.
|
||||
vim._with({ buf = other_buf }, function()
|
||||
vim.cmd.bcd(dir)
|
||||
vim._with({ buf = cur_buf }, function()
|
||||
vim.cmd.bcd(dir)
|
||||
end)
|
||||
end)
|
||||
pcall(vim._with, { buf = other_buf }, function()
|
||||
vim._with({ buf = other_buf }, function() vim.cmd.bcd(dir) end)
|
||||
pcall(vim._with, { buf = err_buf }, function()
|
||||
vim.cmd.bcd(dir)
|
||||
error('oops')
|
||||
end)
|
||||
return {
|
||||
fn.haslocaldir(-1, -1, other_buf),
|
||||
fn.haslocaldir(-1, -1, err_buf),
|
||||
fn.haslocaldir(-1, -1, cur_buf),
|
||||
fn.getcwd() == cwd,
|
||||
fn.getcwd() == cwd, -- Caller's CWD is unaffected: the target is not current.
|
||||
}
|
||||
]]
|
||||
eq({ 0, 0, true }, out)
|
||||
eq({ 1, 1, 0, true }, out)
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -422,6 +417,55 @@ describe('vim._with', function()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('`keepcwd` context', function()
|
||||
it('undoes chdir at every scope', function()
|
||||
local out = exec_lua [[
|
||||
local cwd = fn.getcwd()
|
||||
local dir = vim.fs.joinpath(cwd, 'test')
|
||||
vim._with({ keepcwd = true }, function()
|
||||
vim.cmd.cd(dir)
|
||||
vim.cmd.tcd(dir)
|
||||
vim.cmd.bcd(dir)
|
||||
vim.cmd.lcd(dir)
|
||||
end)
|
||||
return {
|
||||
fn.haslocaldir(), -- window
|
||||
fn.haslocaldir(-1, 0), -- tabpage
|
||||
fn.haslocaldir(-1, -1, 0), -- buffer
|
||||
fn.getcwd() == cwd,
|
||||
fn.getcwd(-1, -1) == cwd, -- global
|
||||
}
|
||||
]]
|
||||
eq({ 0, 0, 0, true, true }, out)
|
||||
end)
|
||||
|
||||
it('discards ":bcd"/":lcd" that a `buf`/`win` context would keep', function()
|
||||
local out = exec_lua [[
|
||||
local other_buf, _ = setup_buffers()
|
||||
local other_win, _ = setup_windows()
|
||||
local cwd = fn.getcwd()
|
||||
local dir = vim.fs.joinpath(cwd, 'test')
|
||||
vim._with({ buf = other_buf, keepcwd = true }, function() vim.cmd.bcd(dir) end)
|
||||
vim._with({ win = other_win, keepcwd = true }, function() vim.cmd.lcd(dir) end)
|
||||
return {
|
||||
fn.haslocaldir(-1, -1, other_buf),
|
||||
fn.haslocaldir(fn.win_id2win(other_win)),
|
||||
fn.getcwd() == cwd,
|
||||
}
|
||||
]]
|
||||
eq({ 0, 0, true }, out)
|
||||
end)
|
||||
|
||||
it('restores nothing else: the callback may switch window', function()
|
||||
local out = exec_lua [[
|
||||
local other_win, _ = setup_windows()
|
||||
vim._with({ keepcwd = true }, function() api.nvim_set_current_win(other_win) end)
|
||||
return api.nvim_get_current_win() == other_win
|
||||
]]
|
||||
eq(true, out)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('`emsg_silent` context', function()
|
||||
pending('works', function()
|
||||
local ok = pcall(
|
||||
@@ -1345,17 +1389,16 @@ describe('vim._with', function()
|
||||
eq({ 'col', { { 'leaf', t2_other_win }, { 'leaf', t2_move_win } } }, fn.winlayout(2))
|
||||
end)
|
||||
|
||||
it('restores CWD state', function()
|
||||
it('keeps ":lcd" on the target window, but restores the CWD', function()
|
||||
local out = exec_lua [[
|
||||
local other_win, cur_win = setup_windows()
|
||||
local cwd = fn.getcwd()
|
||||
-- ":lcd" on the target window is discarded.
|
||||
vim._with({ win = other_win }, function()
|
||||
vim.cmd.lcd(vim.fs.joinpath(cwd, 'test'))
|
||||
end)
|
||||
return { fn.haslocaldir(fn.win_id2win(other_win)), fn.getcwd() == cwd }
|
||||
]]
|
||||
eq({ 0, true }, out)
|
||||
eq({ 1, true }, out)
|
||||
end)
|
||||
end)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user