From 76dcc7029b200e1d85024d7ba4a34c602e730dbe Mon Sep 17 00:00:00 2001 From: dundargoc Date: Wed, 27 Nov 2024 15:36:38 +0100 Subject: docs: add tag `vim.fs.exists()` and document suggested replacement --- runtime/lua/vim/fs.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'runtime/lua/vim/fs.lua') diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index d91eeaf02f..2f007d97c3 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -1,3 +1,15 @@ +--- @brief
help
+--- *vim.fs.exists()*
+--- Use |uv.fs_stat()| to check a file's type, and whether it exists.
+---
+--- Example:
+---
+--- >lua
+---   if vim.uv.fs_stat(file) then
+---     vim.print("file exists")
+---   end
+--- <
+
 local uv = vim.uv
 
 local M = {}
-- 
cgit 


From 518070731003e30ea7eee96e76ccdf7b950c90da Mon Sep 17 00:00:00 2001
From: Famiu Haque 
Date: Fri, 5 Apr 2024 14:48:13 +0600
Subject: feat(lua): add `vim.fs.abspath`

Problem: There is currently no way to check if a given path is absolute or convert a relative path to an absolute path through the Lua stdlib. `vim.fs.joinpath` does not work when the path is absolute. There is also currently no way to resolve `C:foo\bar` style paths in Windows.

Solution: Add `vim.fs.abspath`, which allows converting any path to an absolute path. This also allows checking if current path is absolute by doing `vim.fs.abspath(path) == path`. It also has support for `C:foo\bar` style paths in Windows.
---
 runtime/lua/vim/fs.lua | 69 ++++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 61 insertions(+), 8 deletions(-)

(limited to 'runtime/lua/vim/fs.lua')

diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
index 2f007d97c3..f2cd210cac 100644
--- a/runtime/lua/vim/fs.lua
+++ b/runtime/lua/vim/fs.lua
@@ -505,6 +505,27 @@ local function path_resolve_dot(path)
   return (is_path_absolute and '/' or '') .. table.concat(new_path_components, '/')
 end
 
+--- Expand tilde (~) character at the beginning of the path to the user's home directory.
+---
+--- @param path string Path to expand.
+--- @param sep string|nil Path separator to use. Uses os_sep by default.
+--- @return string Expanded path.
+local function expand_home(path, sep)
+  sep = sep or os_sep
+
+  if vim.startswith(path, '~') then
+    local home = uv.os_homedir() or '~' --- @type string
+
+    if home:sub(-1) == sep then
+      home = home:sub(1, -2)
+    end
+
+    path = home .. path:sub(2)
+  end
+
+  return path
+end
+
 --- @class vim.fs.normalize.Opts
 --- @inlinedoc
 ---
@@ -568,14 +589,8 @@ function M.normalize(path, opts)
     return ''
   end
 
-  -- Expand ~ to users home directory
-  if vim.startswith(path, '~') then
-    local home = uv.os_homedir() or '~'
-    if home:sub(-1) == os_sep_local then
-      home = home:sub(1, -2)
-    end
-    path = home .. path:sub(2)
-  end
+  -- Expand ~ to user's home directory
+  path = expand_home(path, os_sep_local)
 
   -- Expand environment variables if `opts.expand_env` isn't `false`
   if opts.expand_env == nil or opts.expand_env then
@@ -679,4 +694,42 @@ function M.rm(path, opts)
   end
 end
 
+--- Convert path to an absolute path. A tilde (~) character at the beginning of the path is expanded
+--- to the user's home directory. Does not check if the path exists, normalize the path, resolve
+--- symlinks or hardlinks (including `.` and `..`), or expand environment variables. If the path is
+--- already absolute, it is returned unchanged. Also converts `\` path separators to `/`.
+---
+--- @param path string Path
+--- @return string Absolute path
+function M.abspath(path)
+  vim.validate('path', path, 'string')
+
+  -- Expand ~ to user's home directory
+  path = expand_home(path)
+
+  -- Convert path separator to `/`
+  path = path:gsub(os_sep, '/')
+
+  local prefix = ''
+
+  if iswin then
+    prefix, path = split_windows_path(path)
+  end
+
+  if vim.startswith(path, '/') then
+    -- Path is already absolute, do nothing
+    return prefix .. path
+  end
+
+  -- Windows allows paths like C:foo/bar, these paths are relative to the current working directory
+  -- of the drive specified in the path
+  local cwd = (iswin and prefix:match('^%w:$')) and uv.fs_realpath(prefix) or uv.cwd()
+  assert(cwd ~= nil)
+  -- Convert cwd path separator to `/`
+  cwd = cwd:gsub(os_sep, '/')
+
+  -- Prefix is not needed for expanding relative paths, as `cwd` already contains it.
+  return M.joinpath(cwd, path)
+end
+
 return M
-- 
cgit 


From 0bef3b911cc262a007fb4412d864c1832d1268ad Mon Sep 17 00:00:00 2001
From: Gustav Eikaas <46537983+GustavEikaas@users.noreply.github.com>
Date: Tue, 31 Dec 2024 16:40:05 +0100
Subject: fix(vim.fs): joinpath() does not normalize slashes on Windows #31782

---
 runtime/lua/vim/fs.lua | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

(limited to 'runtime/lua/vim/fs.lua')

diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
index f2cd210cac..1b774d5cab 100644
--- a/runtime/lua/vim/fs.lua
+++ b/runtime/lua/vim/fs.lua
@@ -105,14 +105,19 @@ function M.basename(file)
   return file:match('/$') and '' or (file:match('[^/]*$'))
 end
 
---- Concatenate directories and/or file paths into a single path with normalization
---- (e.g., `"foo/"` and `"bar"` get joined to `"foo/bar"`)
+--- Concatenates partial paths into one path. Slashes are normalized (redundant slashes are removed, and on Windows backslashes are replaced with forward-slashes)
+--- (e.g., `"foo/"` and `"/bar"` get joined to `"foo/bar"`)
+--- (windows: e.g `"a\foo\"` and `"\bar"` are joined to `"a/foo/bar"`)
 ---
 ---@since 12
 ---@param ... string
 ---@return string
 function M.joinpath(...)
-  return (table.concat({ ... }, '/'):gsub('//+', '/'))
+  local path = table.concat({ ... }, '/')
+  if iswin then
+    path = path:gsub('\\', '/')
+  end
+  return (path:gsub('//+', '/'))
 end
 
 ---@alias Iterator fun(): string?, string?
-- 
cgit 


From dc692f553aae367a03f286e0d59561247941f96c Mon Sep 17 00:00:00 2001
From: "Justin M. Keyes" 
Date: Wed, 1 Jan 2025 12:29:51 -0800
Subject: docs: misc #31479

---
 runtime/lua/vim/fs.lua | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

(limited to 'runtime/lua/vim/fs.lua')

diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
index 1b774d5cab..d042df96e5 100644
--- a/runtime/lua/vim/fs.lua
+++ b/runtime/lua/vim/fs.lua
@@ -105,9 +105,13 @@ function M.basename(file)
   return file:match('/$') and '' or (file:match('[^/]*$'))
 end
 
---- Concatenates partial paths into one path. Slashes are normalized (redundant slashes are removed, and on Windows backslashes are replaced with forward-slashes)
---- (e.g., `"foo/"` and `"/bar"` get joined to `"foo/bar"`)
---- (windows: e.g `"a\foo\"` and `"\bar"` are joined to `"a/foo/bar"`)
+--- 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.
+---
+--- Examples:
+--- - "foo/", "/bar" => "foo/bar"
+--- - Windows: "a\foo\", "\bar" => "a/foo/bar"
 ---
 ---@since 12
 ---@param ... string
-- 
cgit 


From 6dc0eb9f41e6453fe003dd3a28c58b701fd003c9 Mon Sep 17 00:00:00 2001
From: dundargoc 
Date: Wed, 1 Jan 2025 13:13:40 +0100
Subject: fix(vim.fs.abspath): correctly handle UNC paths

---
 runtime/lua/vim/fs.lua | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'runtime/lua/vim/fs.lua')

diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
index d042df96e5..ee713e4b58 100644
--- a/runtime/lua/vim/fs.lua
+++ b/runtime/lua/vim/fs.lua
@@ -725,7 +725,7 @@ function M.abspath(path)
     prefix, path = split_windows_path(path)
   end
 
-  if vim.startswith(path, '/') then
+  if prefix == '//' or vim.startswith(path, '/') then
     -- Path is already absolute, do nothing
     return prefix .. path
   end
-- 
cgit 


From a8ace2c58a318552869462a36859aabf1cdfaa68 Mon Sep 17 00:00:00 2001
From: dundargoc 
Date: Thu, 2 Jan 2025 16:29:00 +0100
Subject: fix(vim.fs.normalize): normalize case for windows drive letter

Also add tests for the current path casing behavior so it doesn't get
accidentally changed.
---
 runtime/lua/vim/fs.lua | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'runtime/lua/vim/fs.lua')

diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
index ee713e4b58..04a6e43db1 100644
--- a/runtime/lua/vim/fs.lua
+++ b/runtime/lua/vim/fs.lua
@@ -629,8 +629,8 @@ function M.normalize(path, opts)
       return prefix .. path
     end
 
-    -- Remove extraneous slashes from the prefix
-    prefix = prefix:gsub('/+', '/')
+    -- Ensure capital drive and remove extraneous slashes from the prefix
+    prefix = prefix:gsub('^%a:', string.upper):gsub('/+', '/')
   end
 
   if not opts._fast then
-- 
cgit 


From 0631492f9c8044a378dc2a17ea257badfbda6d15 Mon Sep 17 00:00:00 2001
From: dundargoc 
Date: Mon, 30 Dec 2024 16:01:00 +0100
Subject: feat: add vim.fs.relpath

This is needed to replace the nvim-lspconfig function is_descendant that
some lspconfg configurations still use.
---
 runtime/lua/vim/fs.lua | 33 +++++++++++++++++++++++++++++++++
 1 file changed, 33 insertions(+)

(limited to 'runtime/lua/vim/fs.lua')

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


From 611ef354919f1c6564efd2ff8074545941458ccc Mon Sep 17 00:00:00 2001
From: Mike <4576770+mike325@users.noreply.github.com>
Date: Wed, 15 Jan 2025 01:39:17 +0100
Subject: feat(vim.fs): find(), dir() can "follow" symlinks #31551

Problem:
vim.fs.dir(), vim.fs.find() do not follow symlinks.

Solution:
- Add "follow" flag.
- Enable it by default.
---
 runtime/lua/vim/fs.lua | 23 ++++++++++++++++++++---
 1 file changed, 20 insertions(+), 3 deletions(-)

(limited to 'runtime/lua/vim/fs.lua')

diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
index 91e06688b3..5940fa4386 100644
--- a/runtime/lua/vim/fs.lua
+++ b/runtime/lua/vim/fs.lua
@@ -136,6 +136,7 @@ end
 ---             - skip: (fun(dir_name: string): boolean)|nil Predicate
 ---               to control traversal. Return false to stop searching the current directory.
 ---               Only useful when depth > 1
+---             - follow: boolean|nil Follow symbolic links. (default: true)
 ---
 ---@return Iterator over items in {path}. Each iteration yields two values: "name" and "type".
 ---        "name" is the basename of the item relative to {path}.
@@ -147,6 +148,7 @@ function M.dir(path, opts)
   vim.validate('path', path, 'string')
   vim.validate('depth', opts.depth, 'number', true)
   vim.validate('skip', opts.skip, 'function', true)
+  vim.validate('follow', opts.follow, 'boolean', true)
 
   path = M.normalize(path)
   if not opts.depth or opts.depth == 1 then
@@ -177,7 +179,9 @@ function M.dir(path, opts)
         if
           opts.depth
           and level < opts.depth
-          and t == 'directory'
+          and (t == 'directory' or (t == 'link' and opts.follow ~= false and (vim.uv.fs_stat(
+            M.joinpath(path, f)
+          ) or {}).type == 'directory'))
           and (not opts.skip or opts.skip(f) ~= false)
         then
           dirs[#dirs + 1] = { f, level + 1 }
@@ -211,6 +215,10 @@ end
 --- Use `math.huge` to place no limit on the number of matches.
 --- (default: `1`)
 --- @field limit? number
+---
+--- Follow symbolic links.
+--- (default: `true`)
+--- @field follow? boolean
 
 --- Find files or directories (or other items as specified by `opts.type`) in the given path.
 ---
@@ -234,7 +242,7 @@ end
 ---
 --- -- get all files ending with .cpp or .hpp inside lib/
 --- local cpp_hpp = vim.fs.find(function(name, path)
----   return name:match('.*%.[ch]pp$') and path:match('[/\\\\]lib$')
+---   return name:match('.*%.[ch]pp$') and path:match('[/\\]lib$')
 --- end, {limit = math.huge, type = 'file'})
 --- ```
 ---
@@ -244,6 +252,7 @@ end
 ---             If {names} is a function, it is called for each traversed item with args:
 ---             - name: base name of the current item
 ---             - path: full path of the current item
+---
 ---             The function should return `true` if the given item is considered a match.
 ---
 ---@param opts vim.fs.find.Opts Optional keyword arguments:
@@ -256,6 +265,7 @@ function M.find(names, opts)
   vim.validate('stop', opts.stop, 'string', true)
   vim.validate('type', opts.type, 'string', true)
   vim.validate('limit', opts.limit, 'number', true)
+  vim.validate('follow', opts.follow, 'boolean', true)
 
   if type(names) == 'string' then
     names = { names }
@@ -345,7 +355,14 @@ function M.find(names, opts)
           end
         end
 
-        if type_ == 'directory' then
+        if
+          type_ == 'directory'
+          or (
+            type_ == 'link'
+            and opts.follow ~= false
+            and (vim.uv.fs_stat(f) or {}).type == 'directory'
+          )
+        then
           dirs[#dirs + 1] = f
         end
       end
-- 
cgit 


From 6aa42e8f92bd8bea49b7b2accfe4ab67a5344e41 Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Fri, 24 Jan 2025 13:01:25 +0000
Subject: fix: resolve all remaining LuaLS diagnostics

---
 runtime/lua/vim/fs.lua | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'runtime/lua/vim/fs.lua')

diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
index 5940fa4386..8b4242223a 100644
--- a/runtime/lua/vim/fs.lua
+++ b/runtime/lua/vim/fs.lua
@@ -546,7 +546,7 @@ local function expand_home(path, sep)
       home = home:sub(1, -2)
     end
 
-    path = home .. path:sub(2)
+    path = home .. path:sub(2) --- @type string
   end
 
   return path
@@ -620,7 +620,7 @@ function M.normalize(path, opts)
 
   -- Expand environment variables if `opts.expand_env` isn't `false`
   if opts.expand_env == nil or opts.expand_env then
-    path = path:gsub('%$([%w_]+)', uv.os_getenv)
+    path = path:gsub('%$([%w_]+)', uv.os_getenv) --- @type string
   end
 
   if win then
-- 
cgit