fix(fs): allow backslash characters in unix paths

Backslashes are valid characters in unix style paths.

Fix the conversion of backslashes to forward slashes in several `vim.fs`
functions when not on Windows. On Windows, backslashes will still be converted
to forward slashes.
This commit is contained in:
James Trew 2024-03-29 12:23:01 -04:00 committed by GitHub
parent 36acb2a8ec
commit 38e38d1b40
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 65 additions and 49 deletions

View File

@ -2954,13 +2954,14 @@ vim.fs.joinpath({...}) *vim.fs.joinpath()*
vim.fs.normalize({path}, {opts}) *vim.fs.normalize()*
Normalize a path to a standard format. A tilde (~) character at the
beginning of the path is expanded to the user's home directory and any
backslash (\) characters are converted to forward slashes (/). Environment
variables are also expanded.
beginning of the path is expanded to the user's home directory and
environment variables are also expanded.
On Windows, backslash (\) characters are converted to forward slashes (/).
Examples: >lua
vim.fs.normalize('C:\\\\Users\\\\jdoe')
-- 'C:/Users/jdoe'
-- On Windows: 'C:/Users/jdoe'
vim.fs.normalize('~/src/neovim')
-- '/home/jdoe/src/neovim'

View File

@ -1,6 +1,7 @@
local M = {}
local iswin = vim.uv.os_uname().sysname == 'Windows_NT'
local os_sep = iswin and '\\' or '/'
--- Iterate over all the parents of the given path.
---
@ -47,19 +48,23 @@ function M.dirname(file)
return nil
end
vim.validate({ file = { file, 's' } })
if iswin and file:match('^%w:[\\/]?$') then
return (file:gsub('\\', '/'))
elseif not file:match('[\\/]') then
if iswin then
file = file:gsub(os_sep, '/') --[[@as string]]
if file:match('^%w:/?$') then
return file
end
end
if not file:match('/') then
return '.'
elseif file == '/' or file:match('^/[^/]+$') then
return '/'
end
---@type string
local dir = file:match('[/\\]$') and file:sub(1, #file - 1) or file:match('^([/\\]?.+)[/\\]')
local dir = file:match('/$') and file:sub(1, #file - 1) or file:match('^(/?.+)/')
if iswin and dir:match('^%w:$') then
return dir .. '/'
end
return (dir:gsub('\\', '/'))
return dir
end
--- Return the basename of the given path
@ -72,10 +77,13 @@ function M.basename(file)
return nil
end
vim.validate({ file = { file, 's' } })
if iswin and file:match('^%w:[\\/]?$') then
return ''
if iswin then
file = file:gsub(os_sep, '/') --[[@as string]]
if file:match('^%w:/?$') then
return ''
end
end
return file:match('[/\\]$') and '' or (file:match('[^\\/]*$'):gsub('\\', '/'))
return file:match('/$') and '' or (file:match('[^/]*$'))
end
--- Concatenate directories and/or file paths into a single path with normalization
@ -334,15 +342,16 @@ end
--- @field expand_env boolean
--- Normalize a path to a standard format. A tilde (~) character at the
--- beginning of the path is expanded to the user's home directory and any
--- backslash (\) characters are converted to forward slashes (/). Environment
--- variables are also expanded.
--- beginning of the path is expanded to the user's home directory and
--- environment variables are also expanded.
---
--- On Windows, backslash (\) characters are converted to forward slashes (/).
---
--- Examples:
---
--- ```lua
--- vim.fs.normalize('C:\\\\Users\\\\jdoe')
--- -- 'C:/Users/jdoe'
--- -- On Windows: 'C:/Users/jdoe'
---
--- vim.fs.normalize('~/src/neovim')
--- -- '/home/jdoe/src/neovim'
@ -364,7 +373,7 @@ function M.normalize(path, opts)
if path:sub(1, 1) == '~' then
local home = vim.uv.os_homedir() or '~'
if home:sub(-1) == '\\' or home:sub(-1) == '/' then
if home:sub(-1) == os_sep then
home = home:sub(1, -2)
end
path = home .. path:sub(2)
@ -374,7 +383,7 @@ function M.normalize(path, opts)
path = path:gsub('%$([%w_]+)', vim.uv.os_getenv)
end
path = path:gsub('\\', '/'):gsub('/+', '/')
path = path:gsub(os_sep, '/'):gsub('/+', '/')
if iswin and path:match('^%w:/$') then
return path
end

View File

@ -36,6 +36,7 @@ local test_basename_dirname_eq = {
'c:/users/foo',
'c:/users/foo/bar.lua',
'c:/users/foo/bar/../',
'~/foo/bar\\baz',
}
local tests_windows_paths = {
@ -70,26 +71,26 @@ describe('vim.fs', function()
it('works', function()
eq(test_build_dir, vim.fs.dirname(nvim_dir))
--- @param paths string[]
local function test_paths(paths)
---@param paths string[]
---@param is_win? boolean
local function test_paths(paths, is_win)
local gsub = is_win and [[:gsub('\\', '/')]] or ''
local code = string.format(
[[
local path = ...
return vim.fn.fnamemodify(path,':h')%s
]],
gsub
)
for _, path in ipairs(paths) do
eq(
exec_lua(
[[
local path = ...
return vim.fn.fnamemodify(path,':h'):gsub('\\', '/')
]],
path
),
vim.fs.dirname(path),
path
)
eq(exec_lua(code, path), vim.fs.dirname(path), path)
end
end
test_paths(test_basename_dirname_eq)
if is_os('win') then
test_paths(tests_windows_paths)
test_paths(tests_windows_paths, true)
end
end)
end)
@ -98,26 +99,26 @@ describe('vim.fs', function()
it('works', function()
eq(nvim_prog_basename, vim.fs.basename(nvim_prog))
--- @param paths string[]
local function test_paths(paths)
---@param paths string[]
---@param is_win? boolean
local function test_paths(paths, is_win)
local gsub = is_win and [[:gsub('\\', '/')]] or ''
local code = string.format(
[[
local path = ...
return vim.fn.fnamemodify(path,':t')%s
]],
gsub
)
for _, path in ipairs(paths) do
eq(
exec_lua(
[[
local path = ...
return vim.fn.fnamemodify(path,':t'):gsub('\\', '/')
]],
path
),
vim.fs.basename(path),
path
)
eq(exec_lua(code, path), vim.fs.basename(path), path)
end
end
test_paths(test_basename_dirname_eq)
if is_os('win') then
test_paths(tests_windows_paths)
test_paths(tests_windows_paths, true)
end
end)
end)
@ -284,9 +285,6 @@ describe('vim.fs', function()
end)
describe('normalize()', function()
it('works with backward slashes', function()
eq('C:/Users/jdoe', vim.fs.normalize('C:\\Users\\jdoe'))
end)
it('removes trailing /', function()
eq('/home/user', vim.fs.normalize('/home/user/'))
end)
@ -309,10 +307,18 @@ describe('vim.fs', function()
)
)
end)
if is_os('win') then
it('Last slash is not truncated from root drive', function()
eq('C:/', vim.fs.normalize('C:/'))
end)
it('converts backward slashes', function()
eq('C:/Users/jdoe', vim.fs.normalize('C:\\Users\\jdoe'))
end)
else
it('allows backslashes on unix-based os', function()
eq('/home/user/hello\\world', vim.fs.normalize('/home/user/hello\\world'))
end)
end
end)
end)