fix(:bcd): do not "inherit" buffer-local dir

Problem:
Buffer-local CWD (:bcd) is "sticky", similar to window-local CWD (:lcd).
But this contradicts one of its main benefits: per-buffer "project root"
for LSP, OSC7.

Other problems:
- A buffer created with :edit/:enew/:new silently inherits b_localdir
  (and b_prevdir) from the previous buffer.
- curbuf_reusable() refuses to recycle a scratch buffer that has
  `b_localdir`.
- After :new/:vnew/:tabnew the CWD sticks to previous buffer's
  `b_localdir` even though the new curbuf has none, so :new is not
  equivalent to ":split | enew", and getcwd() disagrees with
  haslocaldir().
- Requires "which buffer spawned this buffer" semantics that no other
  buffer-local state has.

Solution:
Drop sticky/inherit behavior of buffer-local CWD (:bcd).

- do_ecmd: always apply the new curbuf's dir (`fix_current_dir`), like
  `do_autochdir` already does. :tabnew from a :bcd buffer now reverts to
  global CWD (and fires DirChanged), same as :tabnew from a :lcd window.
- curbuf_reusable(): recycling a scratch buffer frees its b_localdir.

To get sticky/inherit behavior of CWD, use `:lcd`.
This commit is contained in:
Justin M. Keyes
2026-08-04 17:13:32 +02:00
parent 46ca236525
commit 9cd4dd1c19
14 changed files with 140 additions and 135 deletions

View File

@@ -615,14 +615,14 @@ DiffUpdated After diffs have been updated. Depending on
*DirChanged*
DirChanged After the |current-directory| was changed.
The pattern can be:
"auto" to trigger on 'autochdir'.
"buffer" to trigger on `:bcd`
"global" to trigger on `:cd`
"tabpage" to trigger on `:tcd`
"window" to trigger on `:lcd`
"auto" triggered on 'autochdir'.
"buffer" triggered on `:bcd`.
"global" triggered on `:cd`.
"tabpage" triggered on `:tcd`.
"window" triggered on `:lcd`.
Sets these |v:event| keys:
cwd: current working directory
scope: "global", "tabpage", "window"
scope: "global", "tabpage", "window", "buffer"
changed_window: v:true if we fired the event
switching window (or tab)
<afile> is set to the new directory name.

View File

@@ -1418,6 +1418,10 @@ scope in the hierarchy applies.
current buffer. The current directory for other
tabs, windows, or buffers is not changed.
Unlike :lcd and :tcd, this is not "sticky": a new
buffer or window will not "inherit" a buffer-local
directory.
*:bcd-*
:bc[d][!] - Change to the previous buffer-local current directory
(before the last ":bcd [path]" command).
@@ -1448,22 +1452,24 @@ scope in the hierarchy applies.
:verbose pwd
[buffer] /path/to/current
So long as no |:tcd|, |:lcd| or |:bcd| command has been used, all windows
share the same current directory. Using a command to jump to another window
doesn't change anything for the current directory.
So long as |:tcd|, |:lcd| or |:bcd| has not been used, all buffers and windows
share the same directory. Using a command to jump to another window doesn't
change anything for the current directory.
When |:lcd| has been used for a window, the specified directory becomes the
current directory for that window. Windows where the |:lcd| command has not
been used stick to the buffer-local, tab-local or global directory. When
jumping to another window the current directory is changed to the last
specified local current directory. If none was specified, the buffer-local,
tab-local or global directory is used. When creating a new window it
inherits the local directory of the current window.
specified window-local directory. If none was specified, the buffer-local,
tab-local or global directory is used. When creating a new window it inherits
the current window-local directory ("sticky" behavior).
When |:bcd| has been used for a buffer, the specified directory becomes the
current directory whenever that buffer is entered, unless the window has its
own window-local directory. A new buffer created with |:edit| or |:enew|
inherits the buffer-local directory of the previous current buffer.
own window-local directory. The buffer-local directory is never inherited
(not "sticky"): a buffer created with |:edit|, |:enew|, etc., ignores the
current buffer-local (:bcd) directory, if any. Use |:lcd| if you want "sticky"
behavior.
When changing tabs the same behaviour applies. If the current tab has no
local working directory the global working directory is used.

View File

@@ -1323,7 +1323,7 @@ A jump table for the options with a short description can be found at |Q_op|.
'cdhome' 'cdh' boolean (default on on Unix, off on Windows)
global
Disallowed in |modeline|. |no-modeline-option|
When on, |:cd|, |:tcd| and |:lcd| without an argument changes the
When on, |:cd|, |:tcd|, |:lcd| and |:bcd| without an argument changes the
current working directory to the |$HOME| directory like in Unix.
When off, those commands just print the current directory name.
@@ -1332,7 +1332,7 @@ A jump table for the options with a short description can be found at |Q_op|.
global
Disallowed in |modeline|. |no-modeline-option|
This is a list of directories which will be searched when using the
|:cd|, |:tcd| and |:lcd| commands, provided that the directory being
|:cd|, |:tcd|, |:lcd| and |:bcd| commands, provided that the directory being
searched for has a relative path, not an absolute part starting with
"/", "./" or "../", the 'cdpath' option is not used then.
The 'cdpath' option's value has the same form and semantics as
@@ -2058,7 +2058,7 @@ A jump table for the options with a short description can be found at |Q_op|.
following occurrence.
*cpo-~*
~ When included, don't resolve symbolic links when
changing directory with |:cd|, |:lcd|, or |:tcd|.
changing directory with |:cd|, |:tcd|, |:lcd|, or |:bcd|.
This preserves the symbolic link path in buffer names
and when displaying the current directory. When
excluded (default), symbolic links are resolved to

View File

@@ -1125,8 +1125,8 @@ chdir({dir} [, {scope}]) *chdir()*
If {scope} is present, changes the current working directory
for the specified scope:
"window" Changes the window local directory. |:lcd|
"buffer" Changes the buffer local directory. |:bcd|
"window" Changes the window local directory. |:lcd|
"tabpage" Changes the tabpage local directory. |:tcd|
"global" Changes the global directory. |:cd|
@@ -3759,23 +3759,27 @@ getcwd([{winnr} [, {tabnr} [, {bufnr}]]])
is ignored.
Tabs, windows and buffers are identified by their respective
numbers, 0 means current tab or window or buffer. Missing tab
number
implies 0. Thus the following are equivalent: >vim
numbers, 0 means current tab/window/buffer. Missing {tabnr}
implies 0 (missing {bufnr} does not; see below). Thus the
following are equivalent: >vim
getcwd(0)
getcwd(0, 0)
< If {winnr} is -1 it is ignored, only the tab is resolved.
{winnr} is a |window-number| or |window-ID|.
If both {winnr} and {tabnr} are -1 and {bufnr} is missing the
global working directory is returned.
Note: When {tabnr} is -1 Vim returns an empty string to
signal that it is invalid, whereas Nvim returns either the
global working directory if {winnr} is -1 or the working
directory of the window indicated by {winnr}.
If {bufnr} is provided, {winnr} and {tabnr} must be -1 and the
working directory of that buffer is returned. An argument may
be -1 only if all preceding arguments are -1.
If {bufnr} is provided, {winnr} and {tabnr} must be -1, then
the buffer-local working directory is returned.
An argument may be -1 only if all preceding arguments are -1.
Examples of buffer usage: >vim
getcwd(-1, -1, 0) " Get current buffer's directory
getcwd(-1, -1, 3) " Get directory of buffer #3
@@ -5019,8 +5023,9 @@ haslocaldir([{winnr} [, {tabnr} [, {bufnr} ]]]) *haslocaldir()*
and {bufnr} has set a local path via |:bcd|, otherwise 0.
Tabs, windows and buffers are identified by their respective
numbers, 0 means current tab, window or buffer. Missing
argument implies 0. Thus the following are equivalent: >vim
numbers, 0 means current tab/window/buffer. Missing {winnr}
or {tabnr} implies 0 (missing {bufnr} does not; see below).
Thus the following are equivalent: >vim
echo haslocaldir()
echo haslocaldir(0)
echo haslocaldir(0, 0)

View File

@@ -734,7 +734,7 @@ vim.o.cmp = vim.o.casemap
vim.go.casemap = vim.o.casemap
vim.go.cmp = vim.go.casemap
--- When on, `:cd`, `:tcd` and `:lcd` without an argument changes the
--- When on, `:cd`, `:tcd`, `:lcd` and `:bcd` without an argument changes the
--- current working directory to the `$HOME` directory like in Unix.
--- When off, those commands just print the current directory name.
---
@@ -745,7 +745,7 @@ vim.go.cdhome = vim.o.cdhome
vim.go.cdh = vim.go.cdhome
--- This is a list of directories which will be searched when using the
--- `:cd`, `:tcd` and `:lcd` commands, provided that the directory being
--- `:cd`, `:tcd`, `:lcd` and `:bcd` commands, provided that the directory being
--- searched for has a relative path, not an absolute part starting with
--- "/", "./" or "../", the 'cdpath' option is not used then.
--- The 'cdpath' option's value has the same form and semantics as
@@ -1572,7 +1572,7 @@ vim.bo.ci = vim.bo.copyindent
--- following occurrence.
--- *cpo-~*
--- ~ When included, don't resolve symbolic links when
--- changing directory with `:cd`, `:lcd`, or `:tcd`.
--- changing directory with `:cd`, `:tcd`, `:lcd`, or `:bcd`.
--- This preserves the symbolic link path in buffer names
--- and when displaying the current directory. When
--- excluded (default), symbolic links are resolved to

View File

@@ -960,8 +960,8 @@ function vim.fn.charidx(string, idx, countcc, utf16) end
---
--- If {scope} is present, changes the current working directory
--- for the specified scope:
--- "window" Changes the window local directory. |:lcd|
--- "buffer" Changes the buffer local directory. |:bcd|
--- "window" Changes the window local directory. |:lcd|
--- "tabpage" Changes the tabpage local directory. |:tcd|
--- "global" Changes the global directory. |:cd|
---
@@ -3326,23 +3326,27 @@ function vim.fn.getcursorcharpos(winid) end
--- is ignored.
---
--- Tabs, windows and buffers are identified by their respective
--- numbers, 0 means current tab or window or buffer. Missing tab
--- number
--- implies 0. Thus the following are equivalent: >vim
--- numbers, 0 means current tab/window/buffer. Missing {tabnr}
--- implies 0 (missing {bufnr} does not; see below). Thus the
--- following are equivalent: >vim
--- getcwd(0)
--- getcwd(0, 0)
--- <If {winnr} is -1 it is ignored, only the tab is resolved.
--- {winnr} is a |window-number| or |window-ID|.
---
--- If both {winnr} and {tabnr} are -1 and {bufnr} is missing the
--- global working directory is returned.
---
--- Note: When {tabnr} is -1 Vim returns an empty string to
--- signal that it is invalid, whereas Nvim returns either the
--- global working directory if {winnr} is -1 or the working
--- directory of the window indicated by {winnr}.
---
--- If {bufnr} is provided, {winnr} and {tabnr} must be -1 and the
--- working directory of that buffer is returned. An argument may
--- be -1 only if all preceding arguments are -1.
--- If {bufnr} is provided, {winnr} and {tabnr} must be -1, then
--- the buffer-local working directory is returned.
---
--- An argument may be -1 only if all preceding arguments are -1.
---
--- Examples of buffer usage: >vim
--- getcwd(-1, -1, 0) " Get current buffer's directory
--- getcwd(-1, -1, 3) " Get directory of buffer #3
@@ -4468,8 +4472,9 @@ function vim.fn.has_key(dict, key) end
--- and {bufnr} has set a local path via |:bcd|, otherwise 0.
---
--- Tabs, windows and buffers are identified by their respective
--- numbers, 0 means current tab, window or buffer. Missing
--- argument implies 0. Thus the following are equivalent: >vim
--- numbers, 0 means current tab/window/buffer. Missing {winnr}
--- or {tabnr} implies 0 (missing {bufnr} does not; see below).
--- Thus the following are equivalent: >vim
--- echo haslocaldir()
--- echo haslocaldir(0)
--- echo haslocaldir(0, 0)

View File

@@ -1778,7 +1778,7 @@ void set_curbuf(buf_T *buf, int action, bool update_jumplist)
}
// Maybe cd to buffer-local directory
fix_current_dir(false);
update_cwd(kCdCauseBuffer);
}
/// Enter a new current buffer.
@@ -2031,8 +2031,6 @@ buf_T *buflist_new(char *ffname_arg, char *sfname_arg, linenr_T lnum, int flags)
trigger_undo_ftplugin(buf, curwin);
// It's like this buffer is deleted. Watch out for autocommands that
// change curbuf! If that happens, allocate a new buffer anyway.
// We also ask it to not free the buffer-local directory so we can reuse
// it.
buf_freeall(buf, BFA_WIPE | BFA_DEL);
if (aborting()) { // autocmds may abort script processing
xfree(ffname);
@@ -2162,8 +2160,6 @@ bool curbuf_reusable(void)
&& curbuf->b_ffname == NULL
&& curbuf->b_nwindows <= 1
&& !curbuf->terminal
&& curbuf->b_localdir == NULL
&& curbuf->b_prevdir == NULL
&& (curbuf->b_ml.ml_mfp == NULL || buf_is_empty(curbuf))
&& !bt_quickfix(curbuf)
&& !curbufIsChanged());

View File

@@ -583,7 +583,7 @@ void ctx_restore(CtxSwitch *cs)
// If :lcd has been used in the autocommand window, correct current
// directory before restoring b_localdir, tp_localdir and globaldir.
if (cwp->w_localdir != NULL) {
fix_current_dir(true);
update_cwd(kCdCauseWindow);
}
if (bufref_valid(&cs->cs_new_curbuf)) {
xfree(cs->cs_new_curbuf.br_buf->b_localdir);

View File

@@ -1265,8 +1265,8 @@ M.funcs = {
If {scope} is present, changes the current working directory
for the specified scope:
"window" Changes the window local directory. |:lcd|
"buffer" Changes the buffer local directory. |:bcd|
"window" Changes the window local directory. |:lcd|
"tabpage" Changes the tabpage local directory. |:tcd|
"global" Changes the global directory. |:cd|
@@ -4101,23 +4101,27 @@ M.funcs = {
is ignored.
Tabs, windows and buffers are identified by their respective
numbers, 0 means current tab or window or buffer. Missing tab
number
implies 0. Thus the following are equivalent: >vim
numbers, 0 means current tab/window/buffer. Missing {tabnr}
implies 0 (missing {bufnr} does not; see below). Thus the
following are equivalent: >vim
getcwd(0)
getcwd(0, 0)
<If {winnr} is -1 it is ignored, only the tab is resolved.
{winnr} is a |window-number| or |window-ID|.
If both {winnr} and {tabnr} are -1 and {bufnr} is missing the
global working directory is returned.
Note: When {tabnr} is -1 Vim returns an empty string to
signal that it is invalid, whereas Nvim returns either the
global working directory if {winnr} is -1 or the working
directory of the window indicated by {winnr}.
If {bufnr} is provided, {winnr} and {tabnr} must be -1 and the
working directory of that buffer is returned. An argument may
be -1 only if all preceding arguments are -1.
If {bufnr} is provided, {winnr} and {tabnr} must be -1, then
the buffer-local working directory is returned.
An argument may be -1 only if all preceding arguments are -1.
Examples of buffer usage: >vim
getcwd(-1, -1, 0) " Get current buffer's directory
getcwd(-1, -1, 3) " Get directory of buffer #3
@@ -5435,8 +5439,9 @@ M.funcs = {
and {bufnr} has set a local path via |:bcd|, otherwise 0.
Tabs, windows and buffers are identified by their respective
numbers, 0 means current tab, window or buffer. Missing
argument implies 0. Thus the following are equivalent: >vim
numbers, 0 means current tab/window/buffer. Missing {winnr}
or {tabnr} implies 0 (missing {bufnr} does not; see below).
Thus the following are equivalent: >vim
echo haslocaldir()
echo haslocaldir(0)
echo haslocaldir(0, 0)

View File

@@ -2937,23 +2937,8 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
redraw_curbuf_later(UPD_NOT_VALID); // redraw this buffer later
}
// A new buffer inherits the buffer-local directory of the buffer it was created from (unless
// autocommands already set one).
if (!oldbuf && curbuf->b_localdir == NULL
&& bufref_valid(&old_curbuf)
&& old_curbuf.br_buf != curbuf
&& old_curbuf.br_buf->b_localdir != NULL) {
curbuf->b_localdir = xstrdup(old_curbuf.br_buf->b_localdir);
if (old_curbuf.br_buf->b_prevdir != NULL) {
curbuf->b_prevdir = xstrdup(old_curbuf.br_buf->b_prevdir);
}
}
// If editing a buffer in the current window, make sure to update to the
// buffer's working directory.
if (oldwin == curwin) {
fix_current_dir(false);
}
// Update to the working directory of the new current buffer.
update_cwd(kCdCauseBuffer);
// Change directories when the 'acd' option is set.
do_autochdir();

View File

@@ -1093,7 +1093,7 @@ local options = {
doc = [[on on Unix, off on Windows]],
},
desc = [=[
When on, |:cd|, |:tcd| and |:lcd| without an argument changes the
When on, |:cd|, |:tcd|, |:lcd| and |:bcd| without an argument changes the
current working directory to the |$HOME| directory like in Unix.
When off, those commands just print the current directory name.
]=],
@@ -1113,7 +1113,7 @@ local options = {
deny_duplicates = true,
desc = [=[
This is a list of directories which will be searched when using the
|:cd|, |:tcd| and |:lcd| commands, provided that the directory being
|:cd|, |:tcd|, |:lcd| and |:bcd| commands, provided that the directory being
searched for has a relative path, not an absolute part starting with
"/", "./" or "../", the 'cdpath' option is not used then.
The 'cdpath' option's value has the same form and semantics as
@@ -2119,7 +2119,7 @@ local options = {
following occurrence.
*cpo-~*
~ When included, don't resolve symbolic links when
changing directory with |:cd|, |:lcd|, or |:tcd|.
changing directory with |:cd|, |:tcd|, |:lcd|, or |:bcd|.
This preserves the symbolic link path in buffer names
and when displaying the current directory. When
excluded (default), symbolic links are resolved to

View File

@@ -5300,7 +5300,7 @@ static void win_enter_ext(win_T *const wp, const int flags)
win_fix_cursor(get_real_state() & (MODE_NORMAL|MODE_CMDLINE|MODE_TERMINAL));
}
fix_current_dir(true);
update_cwd(kCdCauseWindow);
entering_window(curwin);
// Careful: autocommands may close the window and make "wp" invalid
@@ -5352,11 +5352,12 @@ static void win_enter_ext(win_T *const wp, const int flags)
do_autochdir();
}
/// Used after making another window or buffer the current one: change directory if needed.
void fix_current_dir(bool caused_by_win)
/// Applies the effective current-directory of the current window (its window-local, buffer-local,
/// tab-local or the global directory). Called after setting curbuf/curwin.
///
/// @param cause What caused the switch, reported by the DirChanged event.
void update_cwd(CdCause cause)
{
CdCause cause = caused_by_win ? kCdCauseWindow : kCdCauseBuffer;
// New directory is either the local directory of the window, buffer, tab or NULL.
char *new_dir;
CdScope scope;

View File

@@ -6,7 +6,6 @@ local describe, it, before_each, setup, teardown =
local clear = n.clear
local command = n.command
local eq = t.eq
local pcall_err = t.pcall_err
local eval = n.eval
local request = n.request
local is_os = t.is_os
@@ -162,7 +161,7 @@ describe('autocmd DirChanged and DirChangedPre', function()
eq('E344:', string.match(err3, 'E%d*:'))
end)
it("are triggered by 'autochdir'", function()
it("triggered by 'autochdir'", function()
command('set autochdir')
command('split ' .. dirs[1] .. '/foo')
@@ -181,7 +180,7 @@ describe('autocmd DirChanged and DirChangedPre', function()
eq(2, eval('g:cdcount'))
end)
it('do not trigger if directory has not changed', function()
it('not triggered if directory has not changed', function()
command('lcd ' .. dirs[1])
eq({ directory = dirs[1], scope = 'window', changed_window = false }, eval('g:evpre'))
eq({ cwd = dirs[1], scope = 'window', changed_window = false }, eval('g:ev'))
@@ -299,7 +298,7 @@ describe('autocmd DirChanged and DirChangedPre', function()
end
end)
it('are triggered by switching to win/tab with different CWD #6054', function()
it('triggered by switching to win/tab with different CWD #6054', function()
command('lcd ' .. dirs[3]) -- window 3
command('split ' .. dirs[2] .. '/foo') -- window 2
command('lcd ' .. dirs[2])
@@ -376,7 +375,7 @@ describe('autocmd DirChanged and DirChangedPre', function()
end
end)
it('are triggered by switching to buf/tab with different CWD', function()
it('triggered by switching to buf/tab with different CWD', function()
local files = {
dirs[1] .. '/file',
dirs[2] .. '/file',
@@ -400,9 +399,10 @@ describe('autocmd DirChanged and DirChangedPre', function()
eq(5, eval('g:cdprecount'))
eq(5, eval('g:cdcount'))
command('tabnew') -- tab 2 (tab-local CWD)
eq(5, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(5, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabnew') -- tab 2: its new empty buffer has no local CWD, reverts to global
eq({ cwd = curdir, scope = 'global', changed_window = false }, eval('g:ev'))
eq(6, eval('g:cdprecount'))
eq(6, eval('g:cdcount'))
command('tcd ' .. dirs[2])
command('tabnext') -- tab 1 (no tab-local CWD)
eq({ directory = dirs[3], scope = 'buffer', changed_window = true }, eval('g:evpre'))
@@ -414,55 +414,55 @@ describe('autocmd DirChanged and DirChangedPre', function()
eq({ cwd = dirs[2], scope = 'tabpage', changed_window = true }, eval('g:ev'))
eq('tabpage', eval('g:amatchpre'))
eq('tabpage', eval('g:amatch'))
eq(8, eval('g:cdprecount'))
eq(8, eval('g:cdcount'))
eq(9, eval('g:cdprecount'))
eq(9, eval('g:cdcount'))
command('tabnext') -- tab 1
command('b ' .. files[2]) -- buffer 2
eq(10, eval('g:cdprecount'))
eq(10, eval('g:cdcount'))
eq(11, eval('g:cdprecount'))
eq(11, eval('g:cdcount'))
command('tabnext') -- tab 2 (has the *same* CWD)
eq(10, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(10, eval('g:cdcount')) -- same CWD, no DirChanged event
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
if is_os('win') then
command('tabnew') -- tab 3
eq(10, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(10, eval('g:cdcount')) -- same CWD, no DirChanged event
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tcd ' .. dirs[2])
eq(10, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(10, eval('g:cdcount')) -- same CWD, no DirChanged event
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabnext') -- tab 1
eq(10, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(10, eval('g:cdcount')) -- same CWD, no DirChanged event
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabprevious') -- tab 3
eq(10, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(10, eval('g:cdcount')) -- same CWD, no DirChanged event
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabprevious') -- tab 2
eq(10, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(10, eval('g:cdcount')) -- same CWD, no DirChanged event
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabprevious') -- tab 1
eq(10, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(10, eval('g:cdcount')) -- same CWD, no DirChanged event
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
command('bcd ' .. dirs[2]) -- buffer 2
eq(10, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(10, eval('g:cdcount')) -- same CWD, no DirChanged event
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabnext') -- tab 2
eq(10, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(10, eval('g:cdcount')) -- same CWD, no DirChanged event
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabnext') -- tab 3
eq(10, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(10, eval('g:cdcount')) -- same CWD, no DirChanged event
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabnext') -- tab 1
eq(10, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(10, eval('g:cdcount')) -- same CWD, no DirChanged event
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
command('tabprevious') -- tab 3
eq(10, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(10, eval('g:cdcount')) -- same CWD, no DirChanged event
eq(11, eval('g:cdprecount')) -- same CWD, no DirChangedPre event
eq(11, eval('g:cdcount')) -- same CWD, no DirChanged event
end
end)
it('are triggered by switching to buf/win with different CWD', function()
it('triggered by switching to buf/win with different CWD', function()
command('lcd ' .. dirs[3]) -- window 3
command(('split %s/file'):format(dirs[2])) -- window 2
command('lcd ' .. dirs[2])
@@ -511,7 +511,7 @@ describe('autocmd DirChanged and DirChangedPre', function()
eq(8, eval('g:cdcount')) -- no DirChanged event, window-local CWD has higher priority
end)
it('are triggered by nvim_set_current_dir()', function()
it('triggered by nvim_set_current_dir()', function()
request('nvim_set_current_dir', dirs[1])
eq({ directory = dirs[1], scope = 'global', changed_window = false }, eval('g:evpre'))
eq({ cwd = dirs[1], scope = 'global', changed_window = false }, eval('g:ev'))

View File

@@ -179,13 +179,9 @@ for _, cmd in ipairs { 'cd', 'chdir' } do
eq(globalDir, cwd())
eq(0, blwd())
-- A new buffer created with :edit inherits the buffer-local directory; editing an
-- existing buffer keeps that buffer's own directory.
-- A new buffer created with :edit does not inherit the buffer-local directory.
command('b# ')
command(('e %s3'):format(tmpfile))
eq(1, blwd())
eq(join(globalDir, directories.buffer), cwd())
command(('e ..%s%s1'):format(pathsep, tmpfile))
eq(0, blwd())
eq(globalDir, cwd())
@@ -406,26 +402,32 @@ for _, cmd in ipairs { 'bcd', 'bchdir' } do
command('bd') -- delete buffer
end)
it('makes :new/:vnew/:enew use the buffer-local directory', function()
it('buffer-local directory is NOT sticky/inherited', function()
local bufdir = join(directories.start, directories.buffer)
command('edit ' .. tmpfile)
command(('%s %s'):format(cmd, directories.buffer))
command(':new')
eq(bufdir, cwd())
command('wincmd x') -- close :new window
command(':vnew')
eq(bufdir, cwd())
command('wincmd x') -- close :vnew window
command(':enew')
eq(bufdir, cwd())
-- Also in a split.
command(':vsplit')
-- A new buffer starts without a buffer-local directory.
command('new')
eq(directories.start, cwd())
eq(0, blwd())
command('close')
eq(bufdir, cwd())
command(':enew')
command('enew')
eq(directories.start, cwd())
eq(0, blwd())
command('b# ')
eq(bufdir, cwd())
-- Recycling an empty unnamed buffer (:edit) drops its directory with it.
command('enew')
command(('%s %s'):format(cmd, directories.buffer))
eq(bufdir, cwd())
command('edit ' .. tmpfile .. '2')
eq(directories.start, cwd())
eq(0, blwd())
end)
end)
end