mirror of
https://github.com/neovim/neovim.git
synced 2025-09-07 11:58:17 +00:00
feat(fs.lua): add vim.fs.rm()
Analogous to the shell `rm` command.
This commit is contained in:

committed by
Lewis Russell

parent
29bceb4f75
commit
511b991e66
@@ -3021,6 +3021,16 @@ vim.fs.parents({start}) *vim.fs.parents()*
|
|||||||
(`nil`)
|
(`nil`)
|
||||||
(`string?`)
|
(`string?`)
|
||||||
|
|
||||||
|
vim.fs.rm({path}, {opts}) *vim.fs.rm()*
|
||||||
|
Remove files or directories
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
• {path} (`string`) Path to remove
|
||||||
|
• {opts} (`table?`) A table with the following fields:
|
||||||
|
• {recursive}? (`boolean`) Remove directories and their
|
||||||
|
contents recursively
|
||||||
|
• {force}? (`boolean`) Ignore nonexistent files and arguments
|
||||||
|
|
||||||
vim.fs.root({source}, {marker}) *vim.fs.root()*
|
vim.fs.root({source}, {marker}) *vim.fs.root()*
|
||||||
Find the first parent directory containing a specific "marker", relative
|
Find the first parent directory containing a specific "marker", relative
|
||||||
to a file path or buffer.
|
to a file path or buffer.
|
||||||
|
@@ -152,7 +152,7 @@ LSP
|
|||||||
|
|
||||||
LUA
|
LUA
|
||||||
|
|
||||||
• TODO
|
• |vim.fs.rm()| can delete files and directories.
|
||||||
|
|
||||||
OPTIONS
|
OPTIONS
|
||||||
|
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
|
local uv = vim.uv
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local iswin = vim.uv.os_uname().sysname == 'Windows_NT'
|
local iswin = uv.os_uname().sysname == 'Windows_NT'
|
||||||
local os_sep = iswin and '\\' or '/'
|
local os_sep = iswin and '\\' or '/'
|
||||||
|
|
||||||
--- Iterate over all the parents of the given path.
|
--- Iterate over all the parents of the given path.
|
||||||
@@ -122,12 +124,12 @@ function M.dir(path, opts)
|
|||||||
|
|
||||||
path = M.normalize(path)
|
path = M.normalize(path)
|
||||||
if not opts.depth or opts.depth == 1 then
|
if not opts.depth or opts.depth == 1 then
|
||||||
local fs = vim.uv.fs_scandir(path)
|
local fs = uv.fs_scandir(path)
|
||||||
return function()
|
return function()
|
||||||
if not fs then
|
if not fs then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
return vim.uv.fs_scandir_next(fs)
|
return uv.fs_scandir_next(fs)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -138,9 +140,9 @@ function M.dir(path, opts)
|
|||||||
--- @type string, integer
|
--- @type string, integer
|
||||||
local dir0, level = unpack(table.remove(dirs, 1))
|
local dir0, level = unpack(table.remove(dirs, 1))
|
||||||
local dir = level == 1 and dir0 or M.joinpath(path, dir0)
|
local dir = level == 1 and dir0 or M.joinpath(path, dir0)
|
||||||
local fs = vim.uv.fs_scandir(dir)
|
local fs = uv.fs_scandir(dir)
|
||||||
while fs do
|
while fs do
|
||||||
local name, t = vim.uv.fs_scandir_next(fs)
|
local name, t = uv.fs_scandir_next(fs)
|
||||||
if not name then
|
if not name then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@@ -234,7 +236,7 @@ function M.find(names, opts)
|
|||||||
names = { names }
|
names = { names }
|
||||||
end
|
end
|
||||||
|
|
||||||
local path = opts.path or assert(vim.uv.cwd())
|
local path = opts.path or assert(uv.cwd())
|
||||||
local stop = opts.stop
|
local stop = opts.stop
|
||||||
local limit = opts.limit or 1
|
local limit = opts.limit or 1
|
||||||
|
|
||||||
@@ -265,7 +267,7 @@ function M.find(names, opts)
|
|||||||
local t = {} --- @type string[]
|
local t = {} --- @type string[]
|
||||||
for _, name in ipairs(names) do
|
for _, name in ipairs(names) do
|
||||||
local f = M.joinpath(p, name)
|
local f = M.joinpath(p, name)
|
||||||
local stat = vim.uv.fs_stat(f)
|
local stat = uv.fs_stat(f)
|
||||||
if stat and (not opts.type or opts.type == stat.type) then
|
if stat and (not opts.type or opts.type == stat.type) then
|
||||||
t[#t + 1] = f
|
t[#t + 1] = f
|
||||||
end
|
end
|
||||||
@@ -365,7 +367,7 @@ function M.root(source, marker)
|
|||||||
path = source
|
path = source
|
||||||
elseif type(source) == 'number' then
|
elseif type(source) == 'number' then
|
||||||
if vim.bo[source].buftype ~= '' then
|
if vim.bo[source].buftype ~= '' then
|
||||||
path = assert(vim.uv.cwd())
|
path = assert(uv.cwd())
|
||||||
else
|
else
|
||||||
path = vim.api.nvim_buf_get_name(source)
|
path = vim.api.nvim_buf_get_name(source)
|
||||||
end
|
end
|
||||||
@@ -552,7 +554,7 @@ function M.normalize(path, opts)
|
|||||||
|
|
||||||
-- Expand ~ to users home directory
|
-- Expand ~ to users home directory
|
||||||
if vim.startswith(path, '~') then
|
if vim.startswith(path, '~') then
|
||||||
local home = vim.uv.os_homedir() or '~'
|
local home = uv.os_homedir() or '~'
|
||||||
if home:sub(-1) == os_sep_local then
|
if home:sub(-1) == os_sep_local then
|
||||||
home = home:sub(1, -2)
|
home = home:sub(1, -2)
|
||||||
end
|
end
|
||||||
@@ -561,7 +563,7 @@ function M.normalize(path, opts)
|
|||||||
|
|
||||||
-- Expand environment variables if `opts.expand_env` isn't `false`
|
-- Expand environment variables if `opts.expand_env` isn't `false`
|
||||||
if opts.expand_env == nil or opts.expand_env then
|
if opts.expand_env == nil or opts.expand_env then
|
||||||
path = path:gsub('%$([%w_]+)', vim.uv.os_getenv)
|
path = path:gsub('%$([%w_]+)', uv.os_getenv)
|
||||||
end
|
end
|
||||||
|
|
||||||
if win then
|
if win then
|
||||||
@@ -609,4 +611,55 @@ function M.normalize(path, opts)
|
|||||||
return path
|
return path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- @param path string Path to remove
|
||||||
|
--- @param ty string type of path
|
||||||
|
--- @param recursive? boolean
|
||||||
|
--- @param force? boolean
|
||||||
|
local function rm(path, ty, recursive, force)
|
||||||
|
--- @diagnostic disable-next-line:no-unknown
|
||||||
|
local rm_fn
|
||||||
|
|
||||||
|
if ty == 'directory' then
|
||||||
|
if recursive then
|
||||||
|
for file, fty in vim.fs.dir(path) do
|
||||||
|
rm(M.joinpath(path, file), fty, true, force)
|
||||||
|
end
|
||||||
|
elseif not force then
|
||||||
|
error(string.format('%s is a directory', path))
|
||||||
|
end
|
||||||
|
|
||||||
|
rm_fn = uv.fs_rmdir
|
||||||
|
else
|
||||||
|
rm_fn = uv.fs_unlink
|
||||||
|
end
|
||||||
|
|
||||||
|
local ret, err, errnm = rm_fn(path)
|
||||||
|
if ret == nil and (not force or errnm ~= 'ENOENT') then
|
||||||
|
error(err)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @class vim.fs.rm.Opts
|
||||||
|
--- @inlinedoc
|
||||||
|
---
|
||||||
|
--- Remove directories and their contents recursively
|
||||||
|
--- @field recursive? boolean
|
||||||
|
---
|
||||||
|
--- Ignore nonexistent files and arguments
|
||||||
|
--- @field force? boolean
|
||||||
|
|
||||||
|
--- Remove files or directories
|
||||||
|
--- @param path string Path to remove
|
||||||
|
--- @param opts? vim.fs.rm.Opts
|
||||||
|
function M.rm(path, opts)
|
||||||
|
opts = opts or {}
|
||||||
|
|
||||||
|
local stat, err, errnm = uv.fs_stat(path)
|
||||||
|
if stat then
|
||||||
|
rm(path, stat.type, opts.recursive, opts.force)
|
||||||
|
elseif not opts.force or errnm ~= 'ENOENT' then
|
||||||
|
error(err)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
@@ -652,6 +652,7 @@ function M.rename(old_fname, new_fname, opts)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- @param change lsp.CreateFile
|
||||||
local function create_file(change)
|
local function create_file(change)
|
||||||
local opts = change.options or {}
|
local opts = change.options or {}
|
||||||
-- from spec: Overwrite wins over `ignoreIfExists`
|
-- from spec: Overwrite wins over `ignoreIfExists`
|
||||||
@@ -666,23 +667,15 @@ local function create_file(change)
|
|||||||
vim.fn.bufadd(fname)
|
vim.fn.bufadd(fname)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- @param change lsp.DeleteFile
|
||||||
local function delete_file(change)
|
local function delete_file(change)
|
||||||
local opts = change.options or {}
|
local opts = change.options or {}
|
||||||
local fname = vim.uri_to_fname(change.uri)
|
local fname = vim.uri_to_fname(change.uri)
|
||||||
local stat = uv.fs_stat(fname)
|
|
||||||
if opts.ignoreIfNotExists and not stat then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
assert(stat, 'Cannot delete not existing file or folder ' .. fname)
|
|
||||||
local flags
|
|
||||||
if stat and stat.type == 'directory' then
|
|
||||||
flags = opts.recursive and 'rf' or 'd'
|
|
||||||
else
|
|
||||||
flags = ''
|
|
||||||
end
|
|
||||||
local bufnr = vim.fn.bufadd(fname)
|
local bufnr = vim.fn.bufadd(fname)
|
||||||
local result = tonumber(vim.fn.delete(fname, flags))
|
vim.fs.rm(fname, {
|
||||||
assert(result == 0, 'Could not delete file: ' .. fname .. ', stat: ' .. vim.inspect(stat))
|
force = opts.ignoreIfNotExists,
|
||||||
|
recursive = opts.recursive,
|
||||||
|
})
|
||||||
api.nvim_buf_delete(bufnr, { force = true })
|
api.nvim_buf_delete(bufnr, { force = true })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@@ -759,58 +759,21 @@ function M.assert_visible(bufnr, visible)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @param path string
|
|
||||||
local function do_rmdir(path)
|
|
||||||
local stat = uv.fs_stat(path)
|
|
||||||
if stat == nil then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if stat.type ~= 'directory' then
|
|
||||||
error(string.format('rmdir: not a directory: %s', path))
|
|
||||||
end
|
|
||||||
for file in vim.fs.dir(path) do
|
|
||||||
if file ~= '.' and file ~= '..' then
|
|
||||||
local abspath = path .. '/' .. file
|
|
||||||
if t.isdir(abspath) then
|
|
||||||
do_rmdir(abspath) -- recurse
|
|
||||||
else
|
|
||||||
local ret, err = os.remove(abspath)
|
|
||||||
if not ret then
|
|
||||||
if not session then
|
|
||||||
error('os.remove: ' .. err)
|
|
||||||
else
|
|
||||||
-- Try Nvim delete(): it handles `readonly` attribute on Windows,
|
|
||||||
-- and avoids Lua cross-version/platform incompatibilities.
|
|
||||||
if -1 == M.call('delete', abspath) then
|
|
||||||
local hint = (is_os('win') and ' (hint: try :%bwipeout! before rmdir())' or '')
|
|
||||||
error('delete() failed' .. hint .. ': ' .. abspath)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local ret, err = uv.fs_rmdir(path)
|
|
||||||
if not ret then
|
|
||||||
error('luv.fs_rmdir(' .. path .. '): ' .. err)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local start_dir = uv.cwd()
|
local start_dir = uv.cwd()
|
||||||
|
|
||||||
function M.rmdir(path)
|
function M.rmdir(path)
|
||||||
local ret, _ = pcall(do_rmdir, path)
|
local ret, _ = pcall(vim.fs.rm, path, { recursive = true, force = true })
|
||||||
if not ret and is_os('win') then
|
if not ret and is_os('win') then
|
||||||
-- Maybe "Permission denied"; try again after changing the nvim
|
-- Maybe "Permission denied"; try again after changing the nvim
|
||||||
-- process to the top-level directory.
|
-- process to the top-level directory.
|
||||||
M.command([[exe 'cd '.fnameescape(']] .. start_dir .. "')")
|
M.command([[exe 'cd '.fnameescape(']] .. start_dir .. "')")
|
||||||
ret, _ = pcall(do_rmdir, path)
|
ret, _ = pcall(vim.fs.rm, path, { recursive = true, force = true })
|
||||||
end
|
end
|
||||||
-- During teardown, the nvim process may not exit quickly enough, then rmdir()
|
-- During teardown, the nvim process may not exit quickly enough, then rmdir()
|
||||||
-- will fail (on Windows).
|
-- will fail (on Windows).
|
||||||
if not ret then -- Try again.
|
if not ret then -- Try again.
|
||||||
sleep(1000)
|
sleep(1000)
|
||||||
do_rmdir(path)
|
vim.fs.rm(path, { recursive = true, force = true })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user