mirror of
https://github.com/neovim/neovim.git
synced 2026-03-28 03:12:00 +00:00
fix(vim.fs): joinpath() should ignore empty items #38077
Problem:
vim.fs.joinpath treats empty string as a path segment
(it adds a path separator for each empty item):
print(vim.fs.joinpath('', 'after/lsp', '')) -- '/after/lsp/'
print(vim.fs.joinpath('', '')) -- '/'
Especially problematic if the empty segment is the first segment, as
that converts the path to an absolute path.
Solution:
Ignore empty (length of 0) path segments.
Benchmark:
local function test(func)
local t = vim.uv.hrtime()
for _ = 1, 100000, 1 do
func('', 'this/is', 'a/very/long/path', '', 'it', 'really', 'is')
end
print(math.floor((vim.uv.hrtime() - t) / 1e6), 'ms')
end
- with Iter():filter() --> 370 ms
- building new segments table --> 208 ms
- with vim.tbl_filter --> 232 ms
- Instead of gsub split on `/` in all parts --> 1870 ms
This commit is contained in:
committed by
GitHub
parent
4747975754
commit
dc5d313d66
@@ -2592,10 +2592,11 @@ vim.fs.joinpath({...}) *vim.fs.joinpath()*
|
||||
Concatenates partial paths (one absolute or relative path followed by zero
|
||||
or more relative paths). Slashes are normalized: redundant slashes are
|
||||
removed, and (on Windows) backslashes are replaced with forward-slashes.
|
||||
Paths are not expanded/resolved.
|
||||
Empty segments are removed. Paths are not expanded/resolved.
|
||||
|
||||
Examples:
|
||||
• "foo/", "/bar" => "foo/bar"
|
||||
• "", "after/plugin" => "after/plugin"
|
||||
• Windows: "a\foo\", "\bar" => "a/foo/bar"
|
||||
|
||||
Attributes: ~
|
||||
|
||||
@@ -116,21 +116,30 @@ end
|
||||
|
||||
--- Concatenates partial paths (one absolute or relative path followed by zero or more relative
|
||||
--- paths). Slashes are normalized: redundant slashes are removed, and (on Windows) backslashes are
|
||||
--- replaced with forward-slashes. Paths are not expanded/resolved.
|
||||
--- replaced with forward-slashes. Empty segments are removed. Paths are not expanded/resolved.
|
||||
---
|
||||
--- Examples:
|
||||
--- - "foo/", "/bar" => "foo/bar"
|
||||
--- - "", "after/plugin" => "after/plugin"
|
||||
--- - Windows: "a\foo\", "\bar" => "a/foo/bar"
|
||||
---
|
||||
---@since 12
|
||||
---@param ... string
|
||||
---@return string
|
||||
function M.joinpath(...)
|
||||
local path = table.concat({ ... }, '/')
|
||||
if iswin then
|
||||
path = path:gsub('\\', '/')
|
||||
local n = select('#', ...)
|
||||
---@type string[]
|
||||
local segments = {}
|
||||
for i = 1, n do
|
||||
local s = select(i, ...)
|
||||
if s and #s > 0 then
|
||||
segments[#segments + 1] = s
|
||||
end
|
||||
end
|
||||
return (path:gsub('//+', '/'))
|
||||
|
||||
local path = table.concat(segments, '/')
|
||||
|
||||
return (path:gsub(iswin and '[/\\][/\\]*' or '//+', '/'))
|
||||
end
|
||||
|
||||
--- @class vim.fs.dir.Opts
|
||||
|
||||
@@ -470,6 +470,12 @@ describe('vim.fs', function()
|
||||
eq('foo/bar/baz/zub/', vim.fs.joinpath([[foo]], [[//bar////baz]], [[zub/]]))
|
||||
end
|
||||
end)
|
||||
it('handles empty segments', function()
|
||||
eq('foo/bar', vim.fs.joinpath('', 'foo', '', 'bar', ''))
|
||||
eq('foo/bar', vim.fs.joinpath('', '', 'foo', 'bar', '', ''))
|
||||
eq('', vim.fs.joinpath(''))
|
||||
eq('', vim.fs.joinpath('', '', '', ''))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('normalize()', function()
|
||||
|
||||
Reference in New Issue
Block a user