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:
Yochem van Rosmalen
2026-02-27 23:45:07 +01:00
committed by GitHub
parent 4747975754
commit dc5d313d66
3 changed files with 22 additions and 6 deletions

View File

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