fix(utils.path_expand): improve windows support (#2999)

In order to maintain plenary compatibility (for now, and slightly for
other edgecase reasons), avoid converting backslashes to forward
slashes.
This commit is contained in:
James Trew
2024-03-22 23:56:56 -04:00
committed by GitHub
parent 221778e93b
commit 1e59188575
2 changed files with 54 additions and 17 deletions

View File

@@ -17,15 +17,32 @@ local utils = {}
local iswin = vim.loop.os_uname().sysname == "Windows_NT" local iswin = vim.loop.os_uname().sysname == "Windows_NT"
--- `vim.fs.normalize` co-opted for 0.1.x (neovim 0.7) compat --- Hybrid of `vim.fn.expand()` and custom `vim.fs.normalize()`
--- TODO: get rid of this and use `vim.fs.normalize` directly for future releases ---
--- Paths starting with '%', '#' or '<' are expanded with `vim.fn.expand()`.
--- Otherwise avoids using `vim.fn.expand()` due to its overly aggressive
--- expansion behavior which can sometimes lead to errors or the creation of
--- non-existent paths when dealing with valid absolute paths.
---
--- Other paths will have '~' and environment variables expanded.
--- Unlike `vim.fs.normalize()`, backslashes are preserved. This has better
--- compatibility with `plenary.path` and also avoids mangling valid Unix paths
--- with literal backslashes.
---
--- Trailing slashes are trimmed. With the exception of root paths.
--- eg. `/` on Unix or `C:\` on Windows
---
---@param path string ---@param path string
---@return string ---@return string
local function path_normalize(path) utils.path_expand = function(path)
vim.validate { vim.validate {
path = { path, { "string" } }, path = { path, { "string" } },
} }
if path:match "^[%%#<]" then
path = vim.fn.expand(path)
end
if path:sub(1, 1) == "~" then if path:sub(1, 1) == "~" then
local home = vim.loop.os_homedir() or "~" local home = vim.loop.os_homedir() or "~"
if home:sub(-1) == "\\" or home:sub(-1) == "/" then if home:sub(-1) == "\\" or home:sub(-1) == "/" then
@@ -35,23 +52,18 @@ local function path_normalize(path)
end end
path = path:gsub("%$([%w_]+)", vim.loop.os_getenv) path = path:gsub("%$([%w_]+)", vim.loop.os_getenv)
path = path:gsub("\\", "/"):gsub("/+", "/") path = path:gsub("/+", "/")
if iswin and path:match "^%w:/$" then if iswin then
return path path = path:gsub("\\+", "\\")
if path:match "^%w:\\$" then
return path
else
return (path:gsub("(.)\\$", "%1"))
end
end end
return (path:gsub("(.)/$", "%1")) return (path:gsub("(.)/$", "%1"))
end end
--- Selective `expand`.
--- `vim.fn.expand` is overly aggressive, sometimes expanding valid absolute paths into
--- non-existent paths or straight up erroring
---
---@param path string
---@return string
utils.path_expand = function(path)
return path:match "^[%%#<]" and vim.fn.expand(path) or path_normalize(path)
end
utils.get_separator = function() utils.get_separator = function()
return Path.path.sep return Path.path.sep
end end

View File

@@ -1,5 +1,30 @@
local utils = require "telescope.utils" local utils = require "telescope.utils"
describe("path_expand()", function()
it("removes trailing /", function()
assert.is.equal("/home/user", utils.path_expand "/home/user/")
end)
it("works with /", function()
assert.is.equal("/", utils.path_expand "/")
end)
it("works with ~", function()
assert.is.equal(vim.loop.os_homedir() .. "/src/foo", utils.path_expand "~/src/foo")
end)
it("handles duplicate /", function()
assert.is.equal("/home/user", utils.path_expand "/home///user")
end)
it("preserves fake whitespace characters and whitespace", function()
local path_space = "/home/user/hello world"
assert.is.equal(path_space, utils.path_expand(path_space))
local path_newline = [[/home/user/hello\nworld]]
assert.is.equal(path_newline, utils.path_expand(path_newline))
end)
end)
describe("is_uri", function() describe("is_uri", function()
describe("detects valid uris", function() describe("detects valid uris", function()
local uris = { local uris = {
@@ -79,7 +104,7 @@ describe("is_uri", function()
end) end)
end) end)
describe("separates file path location", function() describe("__separates_file_path_location", function()
local suites = { local suites = {
{ {
input = "file.txt:12:4", input = "file.txt:12:4",