aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/fs.lua
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2023-01-25 18:31:31 +0000
committerJosh Rahm <joshuarahm@gmail.com>2023-01-25 18:31:31 +0000
commit9243becbedbb6a1592208051f8fa2b090dcc5e7d (patch)
tree607c2a862ec3f4399b8766383f6f8e04c4aa43b4 /runtime/lua/vim/fs.lua
parent9e40b6e9e1bc67f2d856adb837ee64dd0e25b717 (diff)
parent3c48d3c83fc21dbc0841f9210f04bdb073d73cd1 (diff)
downloadrneovim-usermarks.tar.gz
rneovim-usermarks.tar.bz2
rneovim-usermarks.zip
Merge remote-tracking branch 'upstream/master' into usermarksusermarks
Diffstat (limited to 'runtime/lua/vim/fs.lua')
-rw-r--r--runtime/lua/vim/fs.lua175
1 files changed, 137 insertions, 38 deletions
diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
index ce845eda15..a0d2c4c339 100644
--- a/runtime/lua/vim/fs.lua
+++ b/runtime/lua/vim/fs.lua
@@ -1,9 +1,11 @@
local M = {}
+local iswin = vim.loop.os_uname().sysname == 'Windows_NT'
+
--- Iterate over all the parents of the given file or directory.
---
--- Example:
---- <pre>
+--- <pre>lua
--- local root_dir
--- for dir in vim.fs.parents(vim.api.nvim_buf_get_name(0)) do
--- if vim.fn.isdirectory(dir .. "/.git") == 1 then
@@ -40,7 +42,19 @@ function M.dirname(file)
if file == nil then
return nil
end
- return vim.fn.fnamemodify(file, ':h')
+ vim.validate({ file = { file, 's' } })
+ if iswin and file:match('^%w:[\\/]?$') then
+ return (file:gsub('\\', '/'))
+ elseif not file:match('[\\/]') then
+ return '.'
+ elseif file == '/' or file:match('^/[^/]+$') then
+ return '/'
+ end
+ 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('\\', '/'))
end
--- Return the basename of the given file or directory
@@ -48,21 +62,75 @@ end
---@param file (string) File or directory
---@return (string) Basename of {file}
function M.basename(file)
- return vim.fn.fnamemodify(file, ':t')
+ if file == nil then
+ return nil
+ end
+ vim.validate({ file = { file, 's' } })
+ if iswin and file:match('^%w:[\\/]?$') then
+ return ''
+ end
+ return file:match('[/\\]$') and '' or (file:match('[^\\/]*$'):gsub('\\', '/'))
+end
+
+---@private
+local function join_paths(...)
+ return (table.concat({ ... }, '/'):gsub('//+', '/'))
end
--- Return an iterator over the files and directories located in {path}
---
---@param path (string) An absolute or relative path to the directory to iterate
--- over. The path is first normalized |vim.fs.normalize()|.
+--- @param opts table|nil Optional keyword arguments:
+--- - depth: integer|nil How deep the traverse (default 1)
+--- - skip: (fun(dir_name: string): boolean)|nil Predicate
+--- to control traversal. Return false to stop searching the current directory.
+--- Only useful when depth > 1
+---
---@return Iterator over files and directories in {path}. Each iteration yields
--- two values: name and type. Each "name" is the basename of the file or
--- directory relative to {path}. Type is one of "file" or "directory".
-function M.dir(path)
- return function(fs)
- return vim.loop.fs_scandir_next(fs)
- end,
- vim.loop.fs_scandir(M.normalize(path))
+function M.dir(path, opts)
+ opts = opts or {}
+
+ vim.validate({
+ path = { path, { 'string' } },
+ depth = { opts.depth, { 'number' }, true },
+ skip = { opts.skip, { 'function' }, true },
+ })
+
+ if not opts.depth or opts.depth == 1 then
+ return function(fs)
+ return vim.loop.fs_scandir_next(fs)
+ end,
+ vim.loop.fs_scandir(M.normalize(path))
+ end
+
+ --- @async
+ return coroutine.wrap(function()
+ local dirs = { { path, 1 } }
+ while #dirs > 0 do
+ local dir0, level = unpack(table.remove(dirs, 1))
+ local dir = level == 1 and dir0 or join_paths(path, dir0)
+ local fs = vim.loop.fs_scandir(M.normalize(dir))
+ while fs do
+ local name, t = vim.loop.fs_scandir_next(fs)
+ if not name then
+ break
+ end
+ local f = level == 1 and name or join_paths(dir0, name)
+ coroutine.yield(f, t)
+ if
+ opts.depth
+ and level < opts.depth
+ and t == 'directory'
+ and (not opts.skip or opts.skip(f) ~= false)
+ then
+ dirs[#dirs + 1] = { f, level + 1 }
+ end
+ end
+ end
+ end)
end
--- Find files or directories in the given path.
@@ -73,14 +141,18 @@ end
--- searches are recursive and may search through many directories! If {stop}
--- is non-nil, then the search stops when the directory given in {stop} is
--- reached. The search terminates when {limit} (default 1) matches are found.
---- The search can be narrowed to find only files or or only directories by
+--- The search can be narrowed to find only files or only directories by
--- specifying {type} to be "file" or "directory", respectively.
---
----@param names (string|table) Names of the files and directories to find. Must
---- be base names, paths and globs are not supported.
+---@param names (string|table|fun(name: string): boolean) Names of the files
+--- and directories to find.
+--- Must be base names, paths and globs are not supported.
+--- The function is called per file and directory within the
+--- traversed directories to test if they match {names}.
+---
---@param opts (table) Optional keyword arguments:
--- - path (string): Path to begin searching from. If
---- omitted, the current working directory is used.
+--- omitted, the |current-directory| is used.
--- - upward (boolean, default false): If true, search
--- upward through parent directories. Otherwise,
--- search through child directories
@@ -89,16 +161,16 @@ end
--- reached. The directory itself is not searched.
--- - type (string): Find only files ("file") or
--- directories ("directory"). If omitted, both
---- files and directories that match {name} are
+--- files and directories that match {names} are
--- included.
--- - limit (number, default 1): Stop the search after
--- finding this many matches. Use `math.huge` to
--- place no limit on the number of matches.
----@return (table) The paths of all matching files or directories
+---@return (table) Normalized paths |vim.fs.normalize()| of all matching files or directories
function M.find(names, opts)
opts = opts or {}
vim.validate({
- names = { names, { 's', 't' } },
+ names = { names, { 's', 't', 'f' } },
path = { opts.path, 's', true },
upward = { opts.upward, 'b', true },
stop = { opts.stop, 's', true },
@@ -123,18 +195,31 @@ function M.find(names, opts)
end
if opts.upward then
- ---@private
- local function test(p)
- local t = {}
- for _, name in ipairs(names) do
- local f = p .. '/' .. name
- local stat = vim.loop.fs_stat(f)
- if stat and (not opts.type or opts.type == stat.type) then
- t[#t + 1] = f
+ local test
+
+ if type(names) == 'function' then
+ test = function(p)
+ local t = {}
+ for name, type in M.dir(p) do
+ if names(name) and (not opts.type or opts.type == type) then
+ table.insert(t, join_paths(p, name))
+ end
end
+ return t
end
+ else
+ test = function(p)
+ local t = {}
+ for _, name in ipairs(names) do
+ local f = join_paths(p, name)
+ local stat = vim.loop.fs_stat(f)
+ if stat and (not opts.type or opts.type == stat.type) then
+ t[#t + 1] = f
+ end
+ end
- return t
+ return t
+ end
end
for _, match in ipairs(test(path)) do
@@ -162,17 +247,25 @@ function M.find(names, opts)
break
end
- for other, type in M.dir(dir) do
- local f = dir .. '/' .. other
- for _, name in ipairs(names) do
- if name == other and (not opts.type or opts.type == type) then
+ for other, type_ in M.dir(dir) do
+ local f = join_paths(dir, other)
+ if type(names) == 'function' then
+ if names(other) and (not opts.type or opts.type == type_) then
if add(f) then
return matches
end
end
+ else
+ for _, name in ipairs(names) do
+ if name == other and (not opts.type or opts.type == type_) then
+ if add(f) then
+ return matches
+ end
+ end
+ end
end
- if type == 'directory' then
+ if type_ == 'directory' then
dirs[#dirs + 1] = f
end
end
@@ -187,23 +280,29 @@ end
--- backslash (\\) characters are converted to forward slashes (/). Environment
--- variables are also expanded.
---
---- Example:
---- <pre>
---- vim.fs.normalize('C:\\Users\\jdoe')
---- => 'C:/Users/jdoe'
+--- Examples:
+--- <pre>lua
+--- vim.fs.normalize('C:\\\\Users\\\\jdoe')
+--- --> 'C:/Users/jdoe'
---
---- vim.fs.normalize('~/src/neovim')
---- => '/home/jdoe/src/neovim'
+--- vim.fs.normalize('~/src/neovim')
+--- --> '/home/jdoe/src/neovim'
---
---- vim.fs.normalize('$XDG_CONFIG_HOME/nvim/init.vim')
---- => '/Users/jdoe/.config/nvim/init.vim'
+--- vim.fs.normalize('$XDG_CONFIG_HOME/nvim/init.vim')
+--- --> '/Users/jdoe/.config/nvim/init.vim'
--- </pre>
---
---@param path (string) Path to normalize
---@return (string) Normalized path
function M.normalize(path)
vim.validate({ path = { path, 's' } })
- return (path:gsub('^~/', vim.env.HOME .. '/'):gsub('%$([%w_]+)', vim.env):gsub('\\', '/'))
+ return (
+ path
+ :gsub('^~$', vim.loop.os_homedir())
+ :gsub('^~/', vim.loop.os_homedir() .. '/')
+ :gsub('%$([%w_]+)', vim.loop.os_getenv)
+ :gsub('\\', '/')
+ )
end
return M