diff options
| author | Yochem van Rosmalen <git@yochem.nl> | 2026-02-27 23:45:07 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-27 17:45:07 -0500 |
| commit | dc5d313d66c620699a926aeefaf20bb0ec0d653e (patch) | |
| tree | 6f9d4bade619e5f7e20e0357f2b935a2d13768e6 /runtime/lua/vim/fs.lua | |
| parent | 47479757540fa3b56a48145c74eeb93db81c90f7 (diff) | |
| download | rneovim-dc5d313d66c620699a926aeefaf20bb0ec0d653e.tar.gz rneovim-dc5d313d66c620699a926aeefaf20bb0ec0d653e.tar.bz2 rneovim-dc5d313d66c620699a926aeefaf20bb0ec0d653e.zip | |
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
Diffstat (limited to 'runtime/lua/vim/fs.lua')
| -rw-r--r-- | runtime/lua/vim/fs.lua | 19 |
1 files changed, 14 insertions, 5 deletions
diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index 6e590178d2..dc26790d14 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -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 |