fix(cwd): nvim_win_set_buf of :bcd buf, changes caller CWD #41329

Problem:
Setting a :bcd buffer into another window, modifies the caller's CWD.

    local b = vim.api.nvim_create_buf(true, true)
    vim.api.nvim_buf_call(b, function() vim.cmd.bcd('..') end)
    vim.cmd('vsplit')
    vim.api.nvim_win_set_buf(vim.fn.win_getid(2), b)

    :echo haslocaldir(0) haslocaldir(-1,0) haslocaldir(-1,-1,0)
    0 0 0
    :echo getcwd() ==# getcwd(-1,-1)
    0

Analysis:
`ctx_dirs_save` only saves CWD if it predicts the switch can change it.
But win_set_buf() replaces the target window's buffer *after* the
switch, which cannot be "predicted" from `ctx_dirs_save`.

Solution:
Always snapshot whenever the switch enters another window.
Skipping `os_dirname` was a micro-optimization.
This commit is contained in:
Justin M. Keyes
2026-08-15 13:15:37 -04:00
committed by GitHub
parent cd2db7913a
commit fb180287d0
2 changed files with 17 additions and 10 deletions

View File

@@ -338,15 +338,8 @@ static void ctx_dirs_save(CtxSwitch *cs, win_T *wp, tabpage_T *tp, buf_T *buf)
cs->cs_tp_localdir = tp->tp_localdir == NULL ? NULL : xstrdup(tp->tp_localdir);
}
// 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, or if kCtxKeepDirs was set.
char cwd[MAXPATHL];
if ((cs->cs_flags & kCtxKeepDirs)
|| (curwin != wp
&& (curwin->w_localdir != NULL || wp->w_localdir != NULL
|| curbuf->b_localdir != NULL || wp->w_buffer->b_localdir != NULL
|| (curtab != tp && (curtab->tp_localdir != NULL || tp->tp_localdir != NULL))
|| p_acd))) {
if ((cs->cs_flags & kCtxKeepDirs) || curwin != wp) {
if (os_dirname(cwd, MAXPATHL) == OK) {
cs->cs_cwd = xstrdup(cwd); // allocated on demand: keeps CtxSwitch small
}
@@ -392,17 +385,19 @@ static void ctx_dirs_restore(CtxSwitch *cs)
}
XFREE_CLEAR(cs->cs_globaldir);
// Restore the CWD itself.
// Restore the CWD itself. After an explicit chdir, ctx_restore() re-derives it instead.
if (cs->cs_apply_acd) {
xfree(cs->cs_save_sfname);
do_autochdir();
} else if (cs->cs_cwd != NULL) {
} else if (cs->cs_cwd != NULL && ((cs->cs_flags & kCtxKeepDirs) || !_ctx_did_chdir)) {
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;
}
} else {
xfree(cs->cs_save_sfname);
}
XFREE_CLEAR(cs->cs_cwd);
}

View File

@@ -603,6 +603,18 @@ describe('cd during temp context-switch', function()
command('wincmd w')
eq({ startdir, startdir }, { cwd(), cwd(-1, -1) })
command('only')
-- Setting a :bcd buffer into another window, should not modify the caller's CWD.
local bcdbuf = call('nvim_create_buf', true, true)
n.exec_lua(function(b, d)
vim.api.nvim_buf_call(b, function()
vim.cmd.bcd(d)
end)
end, bcdbuf, bufdir)
command('split')
call('nvim_win_set_buf', call('win_getid', 2), bcdbuf)
eq({ bufdir, startdir, startdir }, { cwd(-1, -1, bcdbuf), cwd(), cwd(-1, -1) })
command('only')
end)
end)