diff options
author | Lewis Russell <lewis6991@gmail.com> | 2024-09-21 16:23:00 +0100 |
---|---|---|
committer | Lewis Russell <me@lewisr.dev> | 2024-09-22 15:05:24 +0100 |
commit | 511b991e66892b4bb8176ce64c0e8fefb300f638 (patch) | |
tree | af0524ac4a781c0e719d056502be19983403cb01 | |
parent | 29bceb4f758097cc6b66726f1dcd3967ad170e35 (diff) | |
download | rneovim-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.txt | 10 | ||||
-rw-r--r-- | runtime/doc/news.txt | 2 | ||||
-rw-r--r-- | runtime/lua/vim/fs.lua | 73 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 19 | ||||
-rw-r--r-- | test/functional/testnvim.lua | 43 |
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 |