Skip to content

Commit

Permalink
Merge pull request #18583 from gpanders/path-root
Browse files Browse the repository at this point in the history
feat(fs): add vim.fs module
  • Loading branch information
gpanders committed May 31, 2022
2 parents e665282 + 046b4ed commit c632f64
Show file tree
Hide file tree
Showing 5 changed files with 435 additions and 0 deletions.
125 changes: 125 additions & 0 deletions runtime/doc/lua.txt
Expand Up @@ -2147,4 +2147,129 @@ set({mode}, {lhs}, {rhs}, {opts}) *vim.keymap.set()*
See also: ~
|nvim_set_keymap()|


==============================================================================
Lua module: fs *lua-fs*

basename({file}) *vim.fs.basename()*
Return the basename of the given file or directory

Parameters: ~
{file} (string) File or directory

Return: ~
(string) Basename of {file}

dir({path}) *vim.fs.dir()*
Return an iterator over the files and directories located in
{path}

Parameters: ~
{path} (string) An absolute or relative path to the
directory to iterate 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".

dirname({file}) *vim.fs.dirname()*
Return the parent directory of the given file or directory

Parameters: ~
{file} (string) File or directory

Return: ~
(string) Parent directory of {file}

find({names}, {opts}) *vim.fs.find()*
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.

Parameters: ~
{names} (string|table) Names of the files and directories
to find. Must be base names, paths and globs are
not supported.
{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

normalize({path}) *vim.fs.normalize()*
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'
<

Parameters: ~
{path} (string) Path to normalize

Return: ~
(string) Normalized path

parents({start}) *vim.fs.parents()*
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
<

Parameters: ~
{start} (string) Initial file or directory.

Return: ~
(function) Iterator

vim:tw=78:ts=8:ft=help:norl:
1 change: 1 addition & 0 deletions runtime/lua/vim/_editor.lua
Expand Up @@ -50,6 +50,7 @@ for k, v in pairs({
keymap = true,
ui = true,
health = true,
fs = true,
}) do
vim._submodules[k] = v
end
Expand Down
205 changes: 205 additions & 0 deletions runtime/lua/vim/fs.lua
@@ -0,0 +1,205 @@
local M = {}

--- Iterate over all the parents of the given file or directory.
---
--- Example:
--- <pre>
--- 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
--- </pre>
---
---@param start (string) Initial file or directory.
---@return (function) Iterator
function M.parents(start)
return function(_, dir)
local parent = M.dirname(dir)
if parent == dir then
return nil
end

return parent
end,
nil,
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 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 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()|.
---@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))
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

--- 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:
--- <pre>
--- 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'
--- </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('\\', '/'))
end

return M
3 changes: 3 additions & 0 deletions scripts/gen_vimdoc.py
Expand Up @@ -134,6 +134,7 @@
'ui.lua',
'filetype.lua',
'keymap.lua',
'fs.lua',
],
'files': [
'runtime/lua/vim/_editor.lua',
Expand All @@ -142,6 +143,7 @@
'runtime/lua/vim/ui.lua',
'runtime/lua/vim/filetype.lua',
'runtime/lua/vim/keymap.lua',
'runtime/lua/vim/fs.lua',
],
'file_patterns': '*.lua',
'fn_name_prefix': '',
Expand All @@ -167,6 +169,7 @@
'ui': 'vim.ui',
'filetype': 'vim.filetype',
'keymap': 'vim.keymap',
'fs': 'vim.fs',
},
'append_only': [
'shared.lua',
Expand Down

0 comments on commit c632f64

Please sign in to comment.