commit 1f9d9cb2e56c484562960d6088ac877322500600
parent f11f8546e7fed3bbd77ce30364580433c1bd4196
Author: Justin M. Keyes <justinkz@gmail.com>
Date: Sun, 16 Nov 2025 22:36:03 -0800
fix(vim.fs): abspath(".") returns "/…/." #36583
Diffstat:
2 files changed, 24 insertions(+), 9 deletions(-)
diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
@@ -446,7 +446,7 @@ function M.root(source, marker)
for _, mark in ipairs(markers) do
local paths = M.find(mark, {
upward = true,
- path = vim.fn.fnamemodify(path, ':p:h'),
+ path = M.abspath(path),
})
if #paths ~= 0 then
@@ -756,6 +756,8 @@ end
--- @param path string Path
--- @return string Absolute path
function M.abspath(path)
+ -- TODO(justinmk): mark f_fnamemodify as API_FAST and use it, ":p:h" should be safe...
+
vim.validate('path', path, 'string')
-- Expand ~ to user's home directory
@@ -782,7 +784,10 @@ function M.abspath(path)
-- Convert cwd path separator to `/`
cwd = cwd:gsub(os_sep, '/')
- -- Prefix is not needed for expanding relative paths, as `cwd` already contains it.
+ if path == '.' then
+ return cwd
+ end
+ -- Prefix is not needed for expanding relative paths, `cwd` already contains it.
return M.joinpath(cwd, path)
end
diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua
@@ -421,23 +421,31 @@ describe('vim.fs', function()
)
end)
- it('uses cwd for unnamed buffers', function()
+ it('returns CWD (absolute path) for unnamed buffers', function()
+ assert(n.fn.isabsolutepath(test_source_path) == 1)
command('new')
- eq(test_source_path, exec_lua([[return vim.fs.root(0, 'CMakePresets.json')]]))
+ eq(
+ t.fix_slashes(test_source_path),
+ t.fix_slashes(exec_lua([[return vim.fs.root(0, 'CMakePresets.json')]]))
+ )
end)
- it("uses cwd for buffers with non-empty 'buftype'", function()
+ it("returns CWD (absolute path) for buffers with non-empty 'buftype'", function()
+ assert(n.fn.isabsolutepath(test_source_path) == 1)
command('new')
command('set buftype=nofile')
command('file lua://')
- eq(test_source_path, exec_lua([[return vim.fs.root(0, 'CMakePresets.json')]]))
+ eq(
+ t.fix_slashes(test_source_path),
+ t.fix_slashes(exec_lua([[return vim.fs.root(0, 'CMakePresets.json')]]))
+ )
end)
- it('returns an absolute path for an invalid filename', function()
+ it('returns CWD (absolute path) if no match is found', function()
assert(n.fn.isabsolutepath(test_source_path) == 1)
eq(
t.fix_slashes(test_source_path),
- t.fix_slashes(exec_lua([[return vim.fs.root('file://asd', 'CMakePresets.json')]]))
+ t.fix_slashes(exec_lua([[return vim.fs.root('file://bogus', 'CMakePresets.json')]]))
)
end)
end)
@@ -621,7 +629,9 @@ describe('vim.fs', function()
local cwd = assert(t.fix_slashes(assert(vim.uv.cwd())))
local home = t.fix_slashes(assert(vim.uv.os_homedir()))
- it('works', function()
+ it('expands relative paths', function()
+ assert(n.fn.isabsolutepath(cwd) == 1)
+ eq(cwd, vim.fs.abspath('.'))
eq(cwd .. '/foo', vim.fs.abspath('foo'))
eq(cwd .. '/././foo', vim.fs.abspath('././foo'))
eq(cwd .. '/.././../foo', vim.fs.abspath('.././../foo'))