From fb180287d0d3068b5873f4a7a39f506b4d52086d Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 15 Aug 2026 13:15:37 -0400 Subject: [PATCH] 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. --- src/nvim/context.c | 15 +++++---------- test/functional/ex_cmds/cd_spec.lua | 12 ++++++++++++ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/nvim/context.c b/src/nvim/context.c index 27fa060de6..df77828a17 100644 --- a/src/nvim/context.c +++ b/src/nvim/context.c @@ -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); } diff --git a/test/functional/ex_cmds/cd_spec.lua b/test/functional/ex_cmds/cd_spec.lua index fb1193379d..df2d7a4817 100644 --- a/test/functional/ex_cmds/cd_spec.lua +++ b/test/functional/ex_cmds/cd_spec.lua @@ -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)