aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLewis Russell <lewis6991@gmail.com>2024-09-21 16:23:00 +0100
committerLewis Russell <me@lewisr.dev>2024-09-22 15:05:24 +0100
commit511b991e66892b4bb8176ce64c0e8fefb300f638 (patch)
treeaf0524ac4a781c0e719d056502be19983403cb01
parent29bceb4f758097cc6b66726f1dcd3967ad170e35 (diff)
downloadrneovim-511b991e66892b4bb8176ce64c0e8fefb300f638.tar.gz
rneovim-511b991e66892b4bb8176ce64c0e8fefb300f638.tar.bz2
rneovim-511b991e66892b4bb8176ce64c0e8fefb300f638.zip
feat(fs.lua): add vim.fs.rm()
Analogous to the shell `rm` command.
-rw-r--r--runtime/doc/lua.txt10
-rw-r--r--runtime/doc/news.txt2
-rw-r--r--runtime/lua/vim/fs.lua73
-rw-r--r--runtime/lua/vim/lsp/util.lua19
-rw-r--r--test/functional/testnvim.lua43
5 files changed, 83 insertions, 64 deletions
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index 1283aac5ca..f3a40cf17a 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -3021,6 +3021,16 @@ vim.fs.parents({start}) *vim.fs.parents()*
(`nil`)
(`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()*
Find the first parent directory containing a specific "marker", relative
to a file path or buffer.
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index e0e90f78ec..df86cc9244 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -152,7 +152,7 @@ LSP
LUA
-• TODO
+• |vim.fs.rm()| can delete files and directories.
OPTIONS
diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
index b05220ee2c..d145c5d531 100644
--- a/runtime/lua/vim/fs.lua
+++ b/runtime/lua/vim/fs.lua
@@ -1,6 +1,8 @@
+local uv = vim.uv
+
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 '/'
--- Iterate over all the parents of the given path.
@@ -122,12 +124,12 @@ function M.dir(path, opts)
path = M.normalize(path)
if not opts.depth or opts.depth == 1 then
- local fs = vim.uv.fs_scandir(path)
+ local fs = uv.fs_scandir(path)
return function()
if not fs then
return
end
- return vim.uv.fs_scandir_next(fs)
+ return uv.fs_scandir_next(fs)
end
end
@@ -138,9 +140,9 @@ function M.dir(path, opts)
--- @type string, integer
local dir0, level = unpack(table.remove(dirs, 1))
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
- local name, t = vim.uv.fs_scandir_next(fs)
+ local name, t = uv.fs_scandir_next(fs)
if not name then
break
end
@@ -234,7 +236,7 @@ function M.find(names, opts)
names = { names }
end
- local path = opts.path or assert(vim.uv.cwd())
+ local path = opts.path or assert(uv.cwd())
local stop = opts.stop
local limit = opts.limit or 1
@@ -265,7 +267,7 @@ function M.find(names, opts)
local t = {} --- @type string[]
for _, name in ipairs(names) do
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
t[#t + 1] = f
end
@@ -365,7 +367,7 @@ function M.root(source, marker)
path = source
elseif type(source) == 'number' then
if vim.bo[source].buftype ~= '' then
- path = assert(vim.uv.cwd())
+ path = assert(uv.cwd())
else
path = vim.api.nvim_buf_get_name(source)
end
@@ -552,7 +554,7 @@ function M.normalize(path, opts)
-- Expand ~ to users home directory
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
home = home:sub(1, -2)
end
@@ -561,7 +563,7 @@ function M.normalize(path, opts)
-- Expand environment variables if `opts.expand_env` isn't `false`
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
if win then
@@ -609,4 +611,55 @@ function M.normalize(path, opts)
return path
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
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 65f84e8f87..9ee7dc8df7 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -652,6 +652,7 @@ function M.rename(old_fname, new_fname, opts)
end
end
+--- @param change lsp.CreateFile
local function create_file(change)
local opts = change.options or {}
-- from spec: Overwrite wins over `ignoreIfExists`
@@ -666,23 +667,15 @@ local function create_file(change)
vim.fn.bufadd(fname)
end
+--- @param change lsp.DeleteFile
local function delete_file(change)
local opts = change.options or {}
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 result = tonumber(vim.fn.delete(fname, flags))
- assert(result == 0, 'Could not delete file: ' .. fname .. ', stat: ' .. vim.inspect(stat))
+ vim.fs.rm(fname, {
+ force = opts.ignoreIfNotExists,
+ recursive = opts.recursive,
+ })
api.nvim_buf_delete(bufnr, { force = true })
end
diff --git a/test/functional/testnvim.lua b/test/functional/testnvim.lua
index 36e6b4d7bd..8a2281e2a1 100644
--- a/test/functional/testnvim.lua
+++ b/test/functional/testnvim.lua
@@ -759,58 +759,21 @@ function M.assert_visible(bufnr, visible)
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()
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
-- Maybe "Permission denied"; try again after changing the nvim
-- process to the top-level directory.
M.command([[exe 'cd '.fnameescape(']] .. start_dir .. "')")
- ret, _ = pcall(do_rmdir, path)
+ ret, _ = pcall(vim.fs.rm, path, { recursive = true, force = true })
end
-- During teardown, the nvim process may not exit quickly enough, then rmdir()
-- will fail (on Windows).
if not ret then -- Try again.
sleep(1000)
- do_rmdir(path)
+ vim.fs.rm(path, { recursive = true, force = true })
end
end