aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordundargoc <gocdundar@gmail.com>2024-12-30 16:01:00 +0100
committerdundargoc <33953936+dundargoc@users.noreply.github.com>2025-01-13 13:14:52 +0100
commit0631492f9c8044a378dc2a17ea257badfbda6d15 (patch)
tree9fca9c84d79c76df83e0689122874e4f1ff262f9
parenta3ef29d570dd892a1bcbfa80bb242d4aac89a06e (diff)
downloadrneovim-0631492f9c8044a378dc2a17ea257badfbda6d15.tar.gz
rneovim-0631492f9c8044a378dc2a17ea257badfbda6d15.tar.bz2
rneovim-0631492f9c8044a378dc2a17ea257badfbda6d15.zip
feat: add vim.fs.relpath
This is needed to replace the nvim-lspconfig function is_descendant that some lspconfg configurations still use.
-rw-r--r--runtime/doc/lua.txt17
-rw-r--r--runtime/doc/news.txt1
-rw-r--r--runtime/lua/vim/fs.lua33
-rw-r--r--test/functional/lua/fs_spec.lua61
-rw-r--r--test/testutil.lua17
5 files changed, 114 insertions, 15 deletions
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index a84a364847..44cbf238cf 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -3148,6 +3148,23 @@ vim.fs.parents({start}) *vim.fs.parents()*
(`nil`)
(`string?`)
+vim.fs.relpath({base}, {target}, {opts}) *vim.fs.relpath()*
+ Gets `target` path relative to `base`, or `nil` if `base` is not an
+ ancestor.
+
+ Example: >lua
+ vim.fs.relpath('/var', '/var/lib') -- 'lib'
+ vim.fs.relpath('/var', '/usr/bin') -- nil
+<
+
+ Parameters: ~
+ • {base} (`string`)
+ • {target} (`string`)
+ • {opts} (`table?`) Reserved for future use
+
+ Return: ~
+ (`string?`)
+
vim.fs.rm({path}, {opts}) *vim.fs.rm()*
WARNING: This feature is experimental/unstable.
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 3b1c38b8d9..a1868e97d0 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -284,6 +284,7 @@ LUA
supporting two new parameters, `encoding` and `strict_indexing`.
• |vim.json.encode()| has an option to enable forward slash escaping
• |vim.fs.abspath()| converts paths to absolute paths.
+• |vim.fs.relpath()| gets relative path compared to base path.
OPTIONS
diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
index 04a6e43db1..91e06688b3 100644
--- a/runtime/lua/vim/fs.lua
+++ b/runtime/lua/vim/fs.lua
@@ -741,4 +741,37 @@ function M.abspath(path)
return M.joinpath(cwd, path)
end
+--- Gets `target` path relative to `base`, or `nil` if `base` is not an ancestor.
+---
+--- Example:
+---
+--- ```lua
+--- vim.fs.relpath('/var', '/var/lib') -- 'lib'
+--- vim.fs.relpath('/var', '/usr/bin') -- nil
+--- ```
+---
+--- @param base string
+--- @param target string
+--- @param opts table? Reserved for future use
+--- @return string|nil
+function M.relpath(base, target, opts)
+ vim.validate('base', base, 'string')
+ vim.validate('target', target, 'string')
+ vim.validate('opts', opts, 'table', true)
+
+ base = vim.fs.normalize(vim.fs.abspath(base))
+ target = vim.fs.normalize(vim.fs.abspath(target))
+ if base == target then
+ return '.'
+ end
+
+ local prefix = ''
+ if iswin then
+ prefix, base = split_windows_path(base)
+ end
+ base = prefix .. base .. (base ~= '/' and '/' or '')
+
+ return vim.startswith(target, base) and target:sub(#base + 1) or nil
+end
+
return M
diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua
index 6bc1ddbff6..0ba0948eee 100644
--- a/test/functional/lua/fs_spec.lua
+++ b/test/functional/lua/fs_spec.lua
@@ -168,8 +168,8 @@ describe('vim.fs', function()
local function run(dir, depth, skip)
return exec_lua(function()
- local r = {}
- local skip_f
+ local r = {} --- @type table<string, string>
+ local skip_f --- @type function
if skip then
skip_f = function(n0)
if vim.tbl_contains(skip or {}, n0) then
@@ -493,8 +493,8 @@ describe('vim.fs', function()
end)
describe('abspath()', function()
- local cwd = is_os('win') and vim.uv.cwd():gsub('\\', '/') or vim.uv.cwd()
- local home = is_os('win') and vim.uv.os_homedir():gsub('\\', '/') or vim.uv.os_homedir()
+ local cwd = assert(t.fix_slashes(assert(vim.uv.cwd())))
+ local home = t.fix_slashes(assert(vim.uv.os_homedir()))
it('works', function()
eq(cwd .. '/foo', vim.fs.abspath('foo'))
@@ -526,4 +526,57 @@ describe('vim.fs', function()
end)
end
end)
+
+ describe('relpath()', function()
+ it('works', function()
+ local cwd = assert(t.fix_slashes(assert(vim.uv.cwd())))
+ local my_dir = vim.fs.joinpath(cwd, 'foo')
+
+ eq(nil, vim.fs.relpath('/var/lib', '/var'))
+ eq(nil, vim.fs.relpath('/var/lib', '/bin'))
+ eq(nil, vim.fs.relpath(my_dir, 'bin'))
+ eq(nil, vim.fs.relpath(my_dir, './bin'))
+ eq(nil, vim.fs.relpath(my_dir, '././'))
+ eq(nil, vim.fs.relpath(my_dir, '../'))
+ eq(nil, vim.fs.relpath('/var/lib', '/'))
+ eq(nil, vim.fs.relpath('/var/lib', '//'))
+ eq(nil, vim.fs.relpath(' ', '/var'))
+ eq(nil, vim.fs.relpath(' ', '/var'))
+ eq('.', vim.fs.relpath('/var/lib', '/var/lib'))
+ eq('lib', vim.fs.relpath('/var/', '/var/lib'))
+ eq('var/lib', vim.fs.relpath('/', '/var/lib'))
+ eq('bar/package.json', vim.fs.relpath('/foo/test', '/foo/test/bar/package.json'))
+ eq('foo/bar', vim.fs.relpath(cwd, 'foo/bar'))
+ eq('foo/bar', vim.fs.relpath('.', vim.fs.joinpath(cwd, 'foo/bar')))
+ eq('bar', vim.fs.relpath('foo', 'foo/bar'))
+ eq(nil, vim.fs.relpath('/var/lib', '/var/library/foo'))
+
+ if is_os('win') then
+ eq(nil, vim.fs.relpath('/', ' '))
+ eq(nil, vim.fs.relpath('/', 'var'))
+ else
+ local cwd_rel_root = cwd:sub(2)
+ eq(cwd_rel_root .. '/ ', vim.fs.relpath('/', ' '))
+ eq(cwd_rel_root .. '/var', vim.fs.relpath('/', 'var'))
+ end
+
+ if is_os('win') then
+ eq(nil, vim.fs.relpath('c:/aaaa/', '/aaaa/cccc'))
+ eq(nil, vim.fs.relpath('c:/aaaa/', './aaaa/cccc'))
+ eq(nil, vim.fs.relpath('c:/aaaa/', 'aaaa/cccc'))
+ eq(nil, vim.fs.relpath('c:/blah\\blah', 'd:/games'))
+ eq(nil, vim.fs.relpath('c:/games', 'd:/games'))
+ eq(nil, vim.fs.relpath('c:/games', 'd:/games/foo'))
+ eq(nil, vim.fs.relpath('c:/aaaa/bbbb', 'c:/aaaa'))
+ eq('cccc', vim.fs.relpath('c:/aaaa/', 'c:/aaaa/cccc'))
+ eq('aaaa/bbbb', vim.fs.relpath('C:/', 'c:\\aaaa\\bbbb'))
+ eq('bar/package.json', vim.fs.relpath('C:\\foo\\test', 'C:\\foo\\test\\bar\\package.json'))
+ eq('baz', vim.fs.relpath('\\\\foo\\bar', '\\\\foo\\bar\\baz'))
+ eq(nil, vim.fs.relpath('a/b/c', 'a\\b'))
+ eq('d', vim.fs.relpath('a/b/c', 'a\\b\\c\\d'))
+ eq('.', vim.fs.relpath('\\\\foo\\bar\\baz', '\\\\foo\\bar\\baz'))
+ eq(nil, vim.fs.relpath('C:\\foo\\test', 'C:\\foo\\Test\\bar\\package.json'))
+ end
+ end)
+ end)
end)
diff --git a/test/testutil.lua b/test/testutil.lua
index 007e017ac6..3226bfeb1e 100644
--- a/test/testutil.lua
+++ b/test/testutil.lua
@@ -22,13 +22,6 @@ local M = {
paths = Paths,
}
---- @param p string
---- @return string
-local function relpath(p)
- p = vim.fs.normalize(p)
- return (p:gsub('^' .. uv.cwd, ''))
-end
-
--- @param path string
--- @return boolean
function M.isdir(path)
@@ -45,14 +38,15 @@ end
--- (Only on Windows) Replaces yucky "\\" slashes with delicious "/" slashes in a string, or all
--- string values in a table (recursively).
---
---- @param obj string|table
---- @return any
+--- @generic T: string|table
+--- @param obj T
+--- @return T|nil
function M.fix_slashes(obj)
if not M.is_os('win') then
return obj
end
if type(obj) == 'string' then
- local ret = obj:gsub('\\', '/')
+ local ret = string.gsub(obj, '\\', '/')
return ret
elseif type(obj) == 'table' then
--- @cast obj table<any,any>
@@ -482,7 +476,8 @@ function M.check_cores(app, force) -- luacheck: ignore
-- "./Xtest-tmpdir/" => "Xtest%-tmpdir"
local local_tmpdir = nil
if tmpdir_is_local and tmpdir then
- local_tmpdir = vim.pesc(relpath(tmpdir):gsub('^[ ./]+', ''):gsub('%/+$', ''))
+ local_tmpdir =
+ vim.pesc(vim.fs.relpath(assert(vim.uv.cwd()), tmpdir):gsub('^[ ./]+', ''):gsub('%/+$', ''))
end
local db_cmd --- @type string