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

@@ -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