diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2023-11-30 20:35:25 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2023-11-30 20:35:25 +0000 |
commit | 1b7b916b7631ddf73c38e3a0070d64e4636cb2f3 (patch) | |
tree | cd08258054db80bb9a11b1061bb091c70b76926a /runtime/lua/vim/fs.lua | |
parent | eaa89c11d0f8aefbb512de769c6c82f61a8baca3 (diff) | |
parent | 4a8bf24ac690004aedf5540fa440e788459e5e34 (diff) | |
download | rneovim-1b7b916b7631ddf73c38e3a0070d64e4636cb2f3.tar.gz rneovim-1b7b916b7631ddf73c38e3a0070d64e4636cb2f3.tar.bz2 rneovim-1b7b916b7631ddf73c38e3a0070d64e4636cb2f3.zip |
Merge remote-tracking branch 'upstream/master' into aucmd_textputpostaucmd_textputpost
Diffstat (limited to 'runtime/lua/vim/fs.lua')
-rw-r--r-- | runtime/lua/vim/fs.lua | 214 |
1 files changed, 138 insertions, 76 deletions
diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index a0d2c4c339..22612a7255 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -1,11 +1,12 @@ local M = {} -local iswin = vim.loop.os_uname().sysname == 'Windows_NT' +local iswin = vim.uv.os_uname().sysname == 'Windows_NT' ---- Iterate over all the parents of the given file or directory. +--- Iterate over all the parents of the given path. --- --- Example: ---- <pre>lua +--- +--- ```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 @@ -17,10 +18,12 @@ local iswin = vim.loop.os_uname().sysname == 'Windows_NT' --- if root_dir then --- print("Found git repository at", root_dir) --- end ---- </pre> +--- ``` --- ----@param start (string) Initial file or directory. ----@return (function) Iterator +---@param start (string) Initial path. +---@return fun(_, dir: string): string? # Iterator +---@return nil +---@return string|nil function M.parents(start) return function(_, dir) local parent = M.dirname(dir) @@ -34,10 +37,10 @@ function M.parents(start) start end ---- Return the parent directory of the given file or directory +--- Return the parent directory of the given path --- ----@param file (string) File or directory ----@return (string) Parent directory of {file} +---@param file (string) Path +---@return string|nil Parent directory of {file} function M.dirname(file) if file == nil then return nil @@ -57,10 +60,10 @@ function M.dirname(file) return (dir:gsub('\\', '/')) end ---- Return the basename of the given file or directory +--- Return the basename of the given path --- ----@param file (string) File or directory ----@return (string) Basename of {file} +---@param file string Path +---@return string|nil Basename of {file} function M.basename(file) if file == nil then return nil @@ -72,12 +75,18 @@ function M.basename(file) return file:match('[/\\]$') and '' or (file:match('[^\\/]*$'):gsub('\\', '/')) end ----@private -local function join_paths(...) +--- Concatenate directories and/or file paths into a single path with normalization +--- (e.g., `"foo/"` and `"bar"` get joined to `"foo/bar"`) +--- +---@param ... string +---@return string +function M.joinpath(...) return (table.concat({ ... }, '/'):gsub('//+', '/')) end ---- Return an iterator over the files and directories located in {path} +---@alias Iterator fun(): string?, string? + +--- Return an iterator over the items 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()|. @@ -87,9 +96,10 @@ end --- 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". +---@return Iterator over items in {path}. Each iteration yields two values: "name" and "type". +--- "name" is the basename of the item relative to {path}. +--- "type" is one of the following: +--- "file", "directory", "link", "fifo", "socket", "char", "block", "unknown". function M.dir(path, opts) opts = opts or {} @@ -100,25 +110,29 @@ function M.dir(path, opts) }) 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)) + local fs = vim.uv.fs_scandir(M.normalize(path)) + return function() + if not fs then + return + end + return vim.uv.fs_scandir_next(fs) + end end --- @async return coroutine.wrap(function() local dirs = { { path, 1 } } while #dirs > 0 do + --- @type string, integer 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)) + local dir = level == 1 and dir0 or M.joinpath(path, dir0) + local fs = vim.uv.fs_scandir(M.normalize(dir)) while fs do - local name, t = vim.loop.fs_scandir_next(fs) + local name, t = vim.uv.fs_scandir_next(fs) if not name then break end - local f = level == 1 and name or join_paths(dir0, name) + local f = level == 1 and name or M.joinpath(dir0, name) coroutine.yield(f, t) if opts.depth @@ -133,22 +147,52 @@ function M.dir(path, opts) end) end ---- Find files or directories in the given path. +--- @class vim.fs.find.opts +--- @field path string +--- @field upward boolean +--- @field stop string +--- @field type string +--- @field limit number + +--- Find files or directories (or other items as specified by `opts.type`) in the given path. +--- +--- Finds items given in {names} starting from {path}. If {upward} is "true" +--- then the search traverses upward through parent directories; otherwise, +--- the search traverses downward. Note that downward 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. You can set {type} +--- to "file", "directory", "link", "socket", "char", "block", or "fifo" +--- to narrow the search to find only that type. --- ---- Finds any files or directories given in {names} starting from {path}. If ---- {upward} is "true" then the search traverses upward through parent ---- directories; otherwise, the search traverses downward. Note that downward ---- 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 only directories by ---- specifying {type} to be "file" or "directory", respectively. +--- Examples: +--- +--- ```lua +--- -- location of Cargo.toml from the current buffer's path +--- local cargo = vim.fs.find('Cargo.toml', { +--- upward = true, +--- stop = vim.uv.os_homedir(), +--- path = vim.fs.dirname(vim.api.nvim_buf_get_name(0)), +--- }) +--- +--- -- list all test directories under the runtime directory +--- local test_dirs = vim.fs.find( +--- {'test', 'tst', 'testdir'}, +--- {limit = math.huge, type = 'directory', path = './runtime/'} +--- ) +--- +--- -- 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$') +--- end, {limit = math.huge, type = 'file'}) +--- ``` --- ----@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 names (string|string[]|fun(name: string, path: string): boolean) Names of the items to find. +--- Must be base names, paths and globs are not supported when {names} is a string or a table. +--- 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 (table) Optional keyword arguments: --- - path (string): Path to begin searching from. If @@ -159,16 +203,14 @@ end --- (recursively). --- - stop (string): Stop searching when this directory is --- reached. The directory itself is not searched. ---- - type (string): Find only files ("file") or ---- directories ("directory"). If omitted, both ---- files and directories that match {names} are ---- included. +--- - type (string): Find only items of the given type. +--- If omitted, all items 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) Normalized paths |vim.fs.normalize()| of all matching files or directories +---@return (string[]) # Normalized paths |vim.fs.normalize()| of all matching items function M.find(names, opts) - opts = opts or {} + opts = opts or {} --[[@as vim.fs.find.opts]] vim.validate({ names = { names, { 's', 't', 'f' } }, path = { opts.path, 's', true }, @@ -178,41 +220,42 @@ function M.find(names, opts) limit = { opts.limit, 'n', true }, }) - names = type(names) == 'string' and { names } or names + if type(names) == 'string' then + names = { names } + end - local path = opts.path or vim.loop.cwd() + local path = opts.path or vim.uv.cwd() local stop = opts.stop local limit = opts.limit or 1 - local matches = {} + local matches = {} --- @type string[] - ---@private local function add(match) - matches[#matches + 1] = match + matches[#matches + 1] = M.normalize(match) if #matches == limit then return true end end if opts.upward then - local test + local test --- @type fun(p: string): string[] 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)) + if (not opts.type or opts.type == type) and names(name, p) then + table.insert(t, M.joinpath(p, name)) end end return t end else test = function(p) - local t = {} + local t = {} --- @type string[] for _, name in ipairs(names) do - local f = join_paths(p, name) - local stat = vim.loop.fs_stat(f) + local f = M.joinpath(p, name) + local stat = vim.uv.fs_stat(f) if stat and (not opts.type or opts.type == stat.type) then t[#t + 1] = f end @@ -248,9 +291,9 @@ function M.find(names, opts) end for other, type_ in M.dir(dir) do - local f = join_paths(dir, other) + local f = M.joinpath(dir, other) if type(names) == 'function' then - if names(other) and (not opts.type or opts.type == type_) then + if (not opts.type or opts.type == type_) and names(other, dir) then if add(f) then return matches end @@ -281,28 +324,47 @@ end --- variables are also expanded. --- --- Examples: ---- <pre>lua ---- vim.fs.normalize('C:\\\\Users\\\\jdoe') ---- --> 'C:/Users/jdoe' --- ---- vim.fs.normalize('~/src/neovim') ---- --> '/home/jdoe/src/neovim' +--- ```lua +--- vim.fs.normalize('C:\\\\Users\\\\jdoe') +--- -- 'C:/Users/jdoe' --- ---- vim.fs.normalize('$XDG_CONFIG_HOME/nvim/init.vim') ---- --> '/Users/jdoe/.config/nvim/init.vim' ---- </pre> +--- vim.fs.normalize('~/src/neovim') +--- -- '/home/jdoe/src/neovim' +--- +--- vim.fs.normalize('$XDG_CONFIG_HOME/nvim/init.vim') +--- -- '/Users/jdoe/.config/nvim/init.vim' +--- ``` --- ---@param path (string) Path to normalize +---@param opts table|nil Options: +--- - expand_env: boolean Expand environment variables (default: true) ---@return (string) Normalized path -function M.normalize(path) - vim.validate({ path = { path, 's' } }) - return ( - path - :gsub('^~$', vim.loop.os_homedir()) - :gsub('^~/', vim.loop.os_homedir() .. '/') - :gsub('%$([%w_]+)', vim.loop.os_getenv) - :gsub('\\', '/') - ) +function M.normalize(path, opts) + opts = opts or {} + + vim.validate({ + path = { path, { 'string' } }, + expand_env = { opts.expand_env, { 'boolean' }, true }, + }) + + if path:sub(1, 1) == '~' then + local home = vim.uv.os_homedir() or '~' + if home:sub(-1) == '\\' or home:sub(-1) == '/' then + home = home:sub(1, -2) + end + path = home .. path:sub(2) + end + + if opts.expand_env == nil or opts.expand_env then + path = path:gsub('%$([%w_]+)', vim.uv.os_getenv) + end + + path = path:gsub('\\', '/'):gsub('/+', '/') + if iswin and path:match('^%w:/$') then + return path + end + return (path:gsub('(.)/$', '%1')) end return M |