From 67cbaf58c41a3db19c5014587e72d06be9e3d58e Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Sun, 15 May 2022 14:38:19 -0600 Subject: feat(fs): add vim.fs.parents() vim.fs.parents() is a Lua iterator that returns the next parent directory of the given file or directory on each iteration. --- runtime/lua/vim/_editor.lua | 1 + runtime/lua/vim/fs.lua | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 runtime/lua/vim/fs.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index c8a0aa8260..453aa6ac81 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -50,6 +50,7 @@ for k, v in pairs({ keymap = true, ui = true, health = true, + fs = true, }) do vim._submodules[k] = v end diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua new file mode 100644 index 0000000000..08d2e495d2 --- /dev/null +++ b/runtime/lua/vim/fs.lua @@ -0,0 +1,35 @@ +local M = {} + +--- Iterate over all the parents of the given file or directory. +--- +--- Example: +---
+--- 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
+---     root_dir = dir
+---     break
+---   end
+--- end
+---
+--- if root_dir then
+---   print("Found git repository at", root_dir)
+--- end
+--- 
+--- +---@param start (string) Initial file or directory. +---@return (function) Iterator +function M.parents(start) + return function(_, dir) + local parent = vim.fn.fnamemodify(dir, ":h") + if parent == dir then + return nil + end + + return parent + end, + nil, + start +end + +return M -- cgit From c5526a27c3b61acb33b7c3c3fe518d8f1e0b602f Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Sun, 15 May 2022 19:53:23 -0600 Subject: feat(fs): add vim.fs.dirname() --- runtime/lua/vim/fs.lua | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index 08d2e495d2..a3b7321cc9 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -21,7 +21,7 @@ local M = {} ---@return (function) Iterator function M.parents(start) return function(_, dir) - local parent = vim.fn.fnamemodify(dir, ":h") + local parent = M.dirname(dir) if parent == dir then return nil end @@ -32,4 +32,12 @@ function M.parents(start) start end +--- Return the parent directory of the given file or directory +--- +---@param file (string) File or directory +---@return (string) Parent directory of {file} +function M.dirname(file) + return vim.fn.fnamemodify(file, ':h') +end + return M -- cgit From b740709431f5e68dac5238d455f9f86d5a564f36 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Sun, 15 May 2022 19:55:18 -0600 Subject: feat(fs): add vim.fs.basename() --- runtime/lua/vim/fs.lua | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index a3b7321cc9..29ce394a38 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -40,4 +40,12 @@ function M.dirname(file) return vim.fn.fnamemodify(file, ':h') end +--- Return the basename of the given file or directory +--- +---@param file (string) File or directory +---@return (string) Basename of {file} +function M.basename(file) + return vim.fn.fnamemodify(file, ':t') +end + return M -- cgit From 2a62bec37ced51678ff914700d7165605d5a0d53 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Sun, 15 May 2022 20:10:12 -0600 Subject: feat(fs): add vim.fs.dir() This function is modeled after the path.dir() function from Penlight and the luafilesystem module. --- runtime/lua/vim/fs.lua | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index 29ce394a38..c28b06536b 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -48,4 +48,17 @@ function M.basename(file) return vim.fn.fnamemodify(file, ':t') 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 +---@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(path) +end + return M -- cgit From f271d706611049bc53a6a439b310fe60bf0fab13 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Sun, 15 May 2022 20:37:35 -0600 Subject: feat(fs): add vim.fs.find() This is a pure Lua implementation of the Vim findfile() and finddir() functions without the special syntax. --- runtime/lua/vim/fs.lua | 117 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index c28b06536b..4519f2a1e4 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -61,4 +61,121 @@ function M.dir(path) end, vim.loop.fs_scandir(path) end +--- Find files or directories in the given path. +--- +--- 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 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 opts (table) Optional keyword arguments: +--- - path (string): Path to begin searching from. If +--- omitted, the current working directory is used. +--- - upward (boolean, default false): If true, search +--- upward through parent directories. Otherwise, +--- search through child directories +--- (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 {name} 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 +function M.find(names, opts) + opts = opts or {} + vim.validate({ + names = { names, { 's', 't' } }, + path = { opts.path, 's', true }, + upward = { opts.upward, 'b', true }, + stop = { opts.stop, 's', true }, + type = { opts.type, 's', true }, + limit = { opts.limit, 'n', true }, + }) + + names = type(names) == 'string' and { names } or names + + local path = opts.path or vim.loop.cwd() + local stop = opts.stop + local limit = opts.limit or 1 + + local matches = {} + + ---@private + local function add(match) + matches[#matches + 1] = match + if #matches == limit then + return true + end + 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 + end + end + + return t + end + + for _, match in ipairs(test(path)) do + if add(match) then + return matches + end + end + + for parent in M.parents(path) do + if stop and parent == stop then + break + end + + for _, match in ipairs(test(parent)) do + if add(match) then + return matches + end + end + end + else + local dirs = { path } + while #dirs > 0 do + local dir = table.remove(dirs, 1) + if stop and dir == stop then + 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 + if add(f) then + return matches + end + end + end + + if type == 'directory' then + dirs[#dirs + 1] = f + end + end + end + end + + return matches +end + return M -- cgit From 046b4ed461cb78b8b302a6403cc7ea64ad6b6085 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Tue, 17 May 2022 08:49:33 -0600 Subject: feat(fs): add vim.fs.normalize() --- runtime/lua/vim/fs.lua | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index 4519f2a1e4..9bf38f7bc3 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -51,14 +51,14 @@ 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 +--- over. The path is first normalized |vim.fs.normalize()|. ---@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(path) + end, vim.loop.fs_scandir(M.normalize(path)) end --- Find files or directories in the given path. @@ -178,4 +178,28 @@ function M.find(names, opts) return matches end +--- 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. +--- +--- Example: +---
+--- vim.fs.normalize('C:\\Users\\jdoe')
+--- => 'C:/Users/jdoe'
+---
+--- 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 +---@return (string) Normalized path +function M.normalize(path) + vim.validate({ path = { path, 's' } }) + return (path:gsub('^~/', vim.env.HOME .. '/'):gsub('%$([%w_]+)', vim.env):gsub('\\', '/')) +end + return M -- cgit