diff --git a/lua/nvim-tree.lua b/lua/nvim-tree.lua index 5ccdf85c..fec2ba2a 100644 --- a/lua/nvim-tree.lua +++ b/lua/nvim-tree.lua @@ -8,13 +8,8 @@ local renderer = require'nvim-tree.renderer' local fs = require'nvim-tree.fs' local view = require'nvim-tree.view' local utils = require'nvim-tree.utils' -local trash = require'nvim-tree.trash' -local _config = { - is_windows = vim.fn.has('win32') == 1 or vim.fn.has('win32unix') == 1, - is_macos = vim.fn.has('mac') == 1 or vim.fn.has('macunix') == 1, - is_unix = vim.fn.has('unix') == 1, -} +local _config = {} local M = {} @@ -79,12 +74,12 @@ local keypress_funcs = { remove = fs.remove, rename = fs.rename(false), full_rename = fs.rename(true), - copy = fs.copy, - copy_name = fs.copy_filename, - copy_path = fs.copy_path, - copy_absolute_path = fs.copy_absolute_path, - cut = fs.cut, - paste = fs.paste, + copy = require'nvim-tree.actions.copy-paste'.copy, + copy_name = require'nvim-tree.actions.copy-paste'.copy_filename, + copy_path = require'nvim-tree.actions.copy-paste'.copy_path, + copy_absolute_path = require'nvim-tree.actions.copy-paste'.copy_absolute_path, + cut = require'nvim-tree.actions.copy-paste'.cut, + paste = require'nvim-tree.actions.copy-paste'.paste, close_node = lib.close_node, parent_node = lib.parent_node, toggle_ignored = lib.toggle_ignored, @@ -103,56 +98,8 @@ local keypress_funcs = { if node.entries ~= nil or node.name == '..' then return end return lib.open_file('preview', node.absolute_path) end, - system_open = function(node) - if not _config.system_open.cmd then - if _config.is_windows then - _config.system_open = { - cmd = "cmd", - args = {'/c', 'start', '""'} - } - elseif _config.is_macos then - _config.system_open.cmd = 'open' - elseif _config.is_unix then - _config.system_open.cmd = 'xdg-open' - else - require'nvim-tree.utils'.warn("Cannot open file with system application. Unrecognized platform.") - return - end - end - - local process = { - cmd = _config.system_open.cmd, - args = _config.system_open.args, - errors = '\n', - stderr = luv.new_pipe(false) - } - table.insert(process.args, node.link_to or node.absolute_path) - process.handle, process.pid = luv.spawn(process.cmd, - { args = process.args, stdio = { nil, nil, process.stderr }, detached = true }, - function(code) - process.stderr:read_stop() - process.stderr:close() - process.handle:close() - if code ~= 0 then - process.errors = process.errors .. string.format('NvimTree system_open: return code %d.', code) - error(process.errors) - end - end - ) - table.remove(process.args) - if not process.handle then - error("\n" .. process.pid .. "\nNvimTree system_open: failed to spawn process using '" .. process.cmd .. "'.") - return - end - luv.read_start(process.stderr, - function(err, data) - if err then return end - if data then process.errors = process.errors .. data end - end - ) - luv.unref(process.handle) - end, - trash = function(node) trash.trash_node(node, _config) end, + system_open = require'nvim-tree.actions.system-open'.fn, + trash = require'nvim-tree.actions.trash'.fn, } function M.on_keypress(action) @@ -186,10 +133,6 @@ function M.on_keypress(action) end end -function M.print_clipboard() - fs.print_clipboard() -end - function M.hijack_current_window() local View = require'nvim-tree.view'.View if not View.bufnr then @@ -366,7 +309,7 @@ local function setup_vim_commands() command! NvimTreeToggle lua require'nvim-tree'.toggle(false) command! NvimTreeFocus lua require'nvim-tree'.focus() command! NvimTreeRefresh lua require'nvim-tree.lib'.refresh_tree() - command! NvimTreeClipboard lua require'nvim-tree'.print_clipboard() + command! NvimTreeClipboard lua require'nvim-tree.actions.copy-paste'.print_clipboard() command! NvimTreeFindFile lua require'nvim-tree'.find_file(true) command! NvimTreeFindFileToggle lua require'nvim-tree'.toggle(true) command! -nargs=1 NvimTreeResize lua require'nvim-tree'.resize() @@ -462,10 +405,8 @@ function M.setup(conf) manage_netrw(opts.disable_netrw, opts.hijack_netrw) _config.update_focused_file = opts.update_focused_file - _config.system_open = opts.system_open _config.open_on_setup = opts.open_on_setup _config.ignore_ft_on_setup = opts.ignore_ft_on_setup - _config.trash = opts.trash or {} if type(opts.update_to_buf_dir) == "boolean" then utils.warn("update_to_buf_dir is now a table, see :help nvim-tree.update_to_buf_dir") _config.update_to_buf_dir = { @@ -481,6 +422,7 @@ function M.setup(conf) end require'nvim-tree.colors'.setup() + require'nvim-tree.actions'.setup(opts) require'nvim-tree.view'.setup(opts.view or {}) require'nvim-tree.diagnostics'.setup(opts) require'nvim-tree.populate'.setup(opts) diff --git a/lua/nvim-tree/actions/copy-paste.lua b/lua/nvim-tree/actions/copy-paste.lua new file mode 100644 index 00000000..a05881fa --- /dev/null +++ b/lua/nvim-tree/actions/copy-paste.lua @@ -0,0 +1,172 @@ +local a = vim.api +local uv = vim.loop + +local lib = require'nvim-tree.lib' +local utils = require'nvim-tree.utils' + +local M = {} + +local clipboard = { + move = {}, + copy = {} +} + +local function do_copy(source, destination) + local source_stats = uv.fs_stat(source) + + if source_stats and source_stats.type == 'file' then + return uv.fs_copyfile(source, destination) + end + + local handle = uv.fs_scandir(source) + + if type(handle) == 'string' then + return false, handle + end + + uv.fs_mkdir(destination, source_stats.mode) + + while true do + local name, _ = uv.fs_scandir_next(handle) + if not name then break end + + local new_name = utils.path_join({source, name}) + local new_destination = utils.path_join({destination, name}) + local success, msg = do_copy(new_name, new_destination) + if not success then return success, msg end + end + + return true +end + +local function do_single_paste(source, dest, action_type, action_fn) + local dest_stats = uv.fs_stat(dest) + local should_process = true + local should_rename = false + + if dest_stats then + print(dest..' already exists. Overwrite? y/n/r(ename)') + local ans = utils.get_user_input_char() + utils.clear_prompt() + should_process = ans:match('^y') + should_rename = ans:match('^r') + end + + if should_rename then + local new_dest = vim.fn.input('New name: ', dest) + return do_single_paste(source, new_dest, action_type, action_fn) + end + + if should_process then + local success, errmsg = action_fn(source, dest) + if not success then + a.nvim_err_writeln('Could not '..action_type..' '..source..' - '..errmsg) + end + end +end + +local function add_to_clipboard(node, clip) + if node.name == '..' then return end + + for idx, entry in ipairs(clip) do + if entry.absolute_path == node.absolute_path then + table.remove(clip, idx) + return a.nvim_out_write(node.absolute_path..' removed to clipboard.\n') + end + end + table.insert(clip, node) + a.nvim_out_write(node.absolute_path..' added to clipboard.\n') +end + +function M.copy(node) + add_to_clipboard(node, clipboard.copy) +end + +function M.cut(node) + add_to_clipboard(node, clipboard.move) +end + +local function do_paste(node, action_type, action_fn) + node = lib.get_last_group_node(node) + if node.name == '..' then return end + local clip = clipboard[action_type] + if #clip == 0 then return end + + local destination = node.absolute_path + local stats = uv.fs_stat(destination) + local is_dir = stats and stats.type == 'directory' + + if not is_dir then + destination = vim.fn.fnamemodify(destination, ':p:h') + elseif not node.open then + destination = vim.fn.fnamemodify(destination, ':p:h:h') + end + + for _, entry in ipairs(clip) do + local dest = utils.path_join({destination, entry.name }) + do_single_paste(entry.absolute_path, dest, action_type, action_fn) + end + + clipboard[action_type] = {} + return lib.refresh_tree() +end + +local function do_cut(source, destination) + local success = uv.fs_rename(source, destination) + if not success then + return success + end + utils.rename_loaded_buffers(source, destination) + return true +end + +function M.paste(node) + if clipboard.move[1] ~= nil then + return do_paste(node, 'move', do_cut) + end + + return do_paste(node, 'copy', do_copy) +end + +function M.print_clipboard() + local content = {} + if #clipboard.move > 0 then + table.insert(content, 'Cut') + for _, item in pairs(clipboard.move) do + table.insert(content, ' * '..item.absolute_path) + end + end + if #clipboard.copy > 0 then + table.insert(content, 'Copy') + for _, item in pairs(clipboard.copy) do + table.insert(content, ' * '..item.absolute_path) + end + end + + return a.nvim_out_write(table.concat(content, '\n')..'\n') +end + +local function copy_to_clipboard(content) + vim.fn.setreg('+', content); + vim.fn.setreg('"', content); + return a.nvim_out_write(string.format('Copied %s to system clipboard! \n', content)) +end + +function M.copy_filename(node) + return copy_to_clipboard(node.name) +end + +function M.copy_path(node) + local absolute_path = node.absolute_path + local relative_path = utils.path_relative(absolute_path, lib.Tree.cwd) + local content = node.entries ~= nil and utils.path_add_trailing(relative_path) or relative_path + return copy_to_clipboard(content) +end + +function M.copy_absolute_path(node) + local absolute_path = node.absolute_path + local content = node.entries ~= nil and utils.path_add_trailing(absolute_path) or absolute_path + return copy_to_clipboard(content) +end + +return M diff --git a/lua/nvim-tree/actions/init.lua b/lua/nvim-tree/actions/init.lua new file mode 100644 index 00000000..28bb5bd6 --- /dev/null +++ b/lua/nvim-tree/actions/init.lua @@ -0,0 +1,8 @@ +local M = {} + +function M.setup(opts) + require'nvim-tree.actions.system-open'.setup(opts.system_open) + require'nvim-tree.actions.trash'.setup(opts.trash) +end + +return M diff --git a/lua/nvim-tree/actions/system-open.lua b/lua/nvim-tree/actions/system-open.lua new file mode 100644 index 00000000..72ec8577 --- /dev/null +++ b/lua/nvim-tree/actions/system-open.lua @@ -0,0 +1,68 @@ +local uv = vim.loop + +local M = { + config = { + is_windows = vim.fn.has('win32') == 1 or vim.fn.has('win32unix') == 1, + is_macos = vim.fn.has('mac') == 1 or vim.fn.has('macunix') == 1, + is_unix = vim.fn.has('unix') == 1, + } +} + +function M.fn(node) + if not M.config.system_open.cmd then + require'nvim-tree.utils'.warn("Cannot open file with system application. Unrecognized platform.") + return + end + + local process = { + cmd = M.config.system_open.cmd, + args = M.config.system_open.args, + errors = '\n', + stderr = uv.new_pipe(false) + } + table.insert(process.args, node.link_to or node.absolute_path) + process.handle, process.pid = uv.spawn(process.cmd, + { args = process.args, stdio = { nil, nil, process.stderr }, detached = true }, + function(code) + process.stderr:read_stop() + process.stderr:close() + process.handle:close() + if code ~= 0 then + process.errors = process.errors .. string.format('NvimTree system_open: return code %d.', code) + error(process.errors) + end + end + ) + table.remove(process.args) + if not process.handle then + error("\n" .. process.pid .. "\nNvimTree system_open: failed to spawn process using '" .. process.cmd .. "'.") + return + end + uv.read_start(process.stderr, + function(err, data) + if err then return end + if data then process.errors = process.errors .. data end + end + ) + uv.unref(process.handle) + +end + +function M.setup(opts) + M.config.system_open = opts or {} + + if not M.config.system_open.cmd then + if M.config.is_windows then + M.config.system_open = { + cmd = "cmd", + args = {'/c', 'start', '""'} + } + elseif M.config.is_macos then + M.config.system_open.cmd = 'open' + elseif M.config.is_unix then + M.config.system_open.cmd = 'xdg-open' + end + end +end + +return M diff --git a/lua/nvim-tree/trash.lua b/lua/nvim-tree/actions/trash.lua similarity index 61% rename from lua/nvim-tree/trash.lua rename to lua/nvim-tree/actions/trash.lua index 78c5910c..ed5ceac0 100644 --- a/lua/nvim-tree/trash.lua +++ b/lua/nvim-tree/actions/trash.lua @@ -1,19 +1,26 @@ -local M = {} +local a = vim.api + +local M = { + config = { + is_windows = vim.fn.has('win32') == 1 or vim.fn.has('win32unix') == 1, + is_macos = vim.fn.has('mac') == 1 or vim.fn.has('macunix') == 1, + is_unix = vim.fn.has('unix') == 1, + } +} local lib = require'nvim-tree.lib' local utils = require'nvim-tree.utils' local events = require'nvim-tree.events' -local api = vim.api local function clear_buffer(absolute_path) local bufs = vim.fn.getbufinfo({bufloaded = 1, buflisted = 1}) for _, buf in pairs(bufs) do if buf.name == absolute_path then if buf.hidden == 0 and #bufs > 1 then - local winnr = api.nvim_get_current_win() - api.nvim_set_current_win(buf.windows[1]) + local winnr = a.nvim_get_current_win() + a.nvim_set_current_win(buf.windows[1]) vim.cmd(':bn') - api.nvim_set_current_win(winnr) + a.nvim_set_current_win(winnr) end vim.api.nvim_buf_delete(buf.bufnr, {}) return @@ -21,20 +28,21 @@ local function clear_buffer(absolute_path) end end -function M.trash_node(node, cfg) +function M.trash_node(node) if node.name == '..' then return end -- configs - if cfg.is_unix then - if cfg.trash.cmd == nil then cfg.trash.cmd = 'trash' end - if cfg.trash.require_confirm == nil then cfg.trash.require_confirm = true end + if M.config.is_unix then + if M.config.trash.cmd == nil then M.config.trash.cmd = 'trash' end + if M.config.trash.require_confirm == nil then M.config.trash.require_confirm = true end else - print('trash is currently a UNIX only feature!') + utils.warn('Trash is currently a UNIX only feature!') + return end -- trashes a path (file or folder) local function trash_path(on_exit) - vim.fn.jobstart(cfg.trash.cmd.." "..node.absolute_path, { + vim.fn.jobstart(M.config.trash.cmd.." "..node.absolute_path, { detach = true, on_exit = on_exit, }) @@ -43,7 +51,7 @@ function M.trash_node(node, cfg) local is_confirmed = true -- confirmation prompt - if cfg.trash.require_confirm then + if M.config.trash.require_confirm then is_confirmed = false print("Trash " ..node.name.. " ? y/n") local ans = utils.get_user_input_char() @@ -69,4 +77,8 @@ function M.trash_node(node, cfg) end end +function M.setup(opts) + M.config.trash = opts or {} +end + return M diff --git a/lua/nvim-tree/fs.lua b/lua/nvim-tree/fs.lua index 85e1b1b6..2b4c9779 100644 --- a/lua/nvim-tree/fs.lua +++ b/lua/nvim-tree/fs.lua @@ -5,18 +5,8 @@ local utils = require'nvim-tree.utils' local view = require'nvim-tree.view' local lib = require'nvim-tree.lib' local events = require'nvim-tree.events' -local M = {} -local clipboard = { - move = {}, - copy = {} -} ---- @param path string path to file or directory ---- @return boolean -local function exist(path) - local _, error = luv.fs_stat(path) - return error == nil -end +local M = {} local function focus_file(file) local _, i = utils.find_node( @@ -27,7 +17,7 @@ local function focus_file(file) end local function create_file(file) - if exist(file) then + if utils.file_exists(file) then print(file..' already exists. Overwrite? y/n') local ans = utils.get_user_input_char() utils.clear_prompt() @@ -76,7 +66,7 @@ function M.create(node) local ans = vim.fn.input('Create file ', add_into) utils.clear_prompt() - if not ans or #ans == 0 or exist(ans) then return end + if not ans or #ans == 0 or utils.file_exists(ans) then return end -- create a folder for each path element if the folder does not exist -- if the answer ends with a /, create a file for the last entry @@ -96,7 +86,7 @@ function M.create(node) end if is_last_path_file and idx == num_entries then create_file(path_to_create) - elseif not exist(path_to_create) then + elseif not utils.file_exists(path_to_create) then local success = luv.fs_mkdir(path_to_create, 493) if not success then api.nvim_err_writeln('Could not create folder '..path_to_create) @@ -132,18 +122,6 @@ local function clear_buffer(absolute_path) end end -local function rename_loaded_buffers(old_name, new_name) - for _, buf in pairs(api.nvim_list_bufs()) do - if api.nvim_buf_is_loaded(buf) then - if api.nvim_buf_get_name(buf) == old_name then - api.nvim_buf_set_name(buf, new_name) - -- to avoid the 'overwrite existing file' error message on write - vim.api.nvim_buf_call(buf, function() vim.cmd("silent! w!") end) - end - end - end -end - local function remove_dir(cwd) local handle = luv.fs_scandir(cwd) if type(handle) == 'string' then @@ -168,106 +146,6 @@ local function remove_dir(cwd) return luv.fs_rmdir(cwd) end -local function do_copy(source, destination) - local source_stats = luv.fs_stat(source) - - if source_stats and source_stats.type == 'file' then - return luv.fs_copyfile(source, destination) - end - - local handle = luv.fs_scandir(source) - - if type(handle) == 'string' then - return false, handle - end - - luv.fs_mkdir(destination, source_stats.mode) - - while true do - local name, _ = luv.fs_scandir_next(handle) - if not name then break end - - local new_name = utils.path_join({source, name}) - local new_destination = utils.path_join({destination, name}) - local success, msg = do_copy(new_name, new_destination) - if not success then return success, msg end - end - - return true -end - -local function do_cut(source, destination) - local success = luv.fs_rename(source, destination) - if not success then - return success - end - rename_loaded_buffers(source, destination) - return true -end - -local function do_single_paste(source, dest, action_type, action_fn) - local dest_stats = luv.fs_stat(dest) - local should_process = true - local should_rename = false - - if dest_stats then - print(dest..' already exists. Overwrite? y/n/r(ename)') - local ans = utils.get_user_input_char() - utils.clear_prompt() - should_process = ans:match('^y') - should_rename = ans:match('^r') - end - - if should_rename then - local new_dest = vim.fn.input('New name: ', dest) - return do_single_paste(source, new_dest, action_type, action_fn) - end - - if should_process then - local success, errmsg = action_fn(source, dest) - if not success then - api.nvim_err_writeln('Could not '..action_type..' '..source..' - '..errmsg) - end - end -end - -local function do_paste(node, action_type, action_fn) - node = lib.get_last_group_node(node) - if node.name == '..' then return end - local clip = clipboard[action_type] - if #clip == 0 then return end - - local destination = node.absolute_path - local stats = luv.fs_stat(destination) - local is_dir = stats and stats.type == 'directory' - - if not is_dir then - destination = vim.fn.fnamemodify(destination, ':p:h') - elseif not node.open then - destination = vim.fn.fnamemodify(destination, ':p:h:h') - end - - for _, entry in ipairs(clip) do - local dest = utils.path_join({destination, entry.name }) - do_single_paste(entry.absolute_path, dest, action_type, action_fn) - end - - clipboard[action_type] = {} - return lib.refresh_tree() -end - -local function add_to_clipboard(node, clip) - if node.name == '..' then return end - - for idx, entry in ipairs(clip) do - if entry.absolute_path == node.absolute_path then - table.remove(clip, idx) - return api.nvim_out_write(node.absolute_path..' removed to clipboard.\n') - end - end - table.insert(clip, node) - api.nvim_out_write(node.absolute_path..' added to clipboard.\n') -end function M.remove(node) if node.name == '..' then return end @@ -306,7 +184,7 @@ function M.rename(with_sub) if not new_name or #new_name == 0 then return end - if exist(new_name) then + if utils.file_exists(new_name) then utils.warn("Cannot rename: file already exists") return end @@ -316,67 +194,10 @@ function M.rename(with_sub) return api.nvim_err_writeln('Could not rename '..node.absolute_path..' to '..new_name) end api.nvim_out_write(node.absolute_path..' ➜ '..new_name..'\n') - rename_loaded_buffers(node.absolute_path, new_name) + utils.rename_loaded_buffers(node.absolute_path, new_name) events._dispatch_node_renamed(abs_path, new_name) lib.refresh_tree() end end -function M.copy(node) - add_to_clipboard(node, clipboard.copy) -end - -function M.cut(node) - add_to_clipboard(node, clipboard.move) -end - -function M.paste(node) - if clipboard.move[1] ~= nil then - return do_paste(node, 'move', do_cut) - end - - return do_paste(node, 'copy', do_copy) -end - -function M.print_clipboard() - local content = {} - if #clipboard.move > 0 then - table.insert(content, 'Cut') - for _, item in pairs(clipboard.move) do - table.insert(content, ' * '..item.absolute_path) - end - end - if #clipboard.copy > 0 then - table.insert(content, 'Copy') - for _, item in pairs(clipboard.copy) do - table.insert(content, ' * '..item.absolute_path) - end - end - - return api.nvim_out_write(table.concat(content, '\n')..'\n') -end - -local function copy_to_clipboard(content) - vim.fn.setreg('+', content); - vim.fn.setreg('"', content); - return api.nvim_out_write(string.format('Copied %s to system clipboard! \n', content)) -end - -function M.copy_filename(node) - return copy_to_clipboard(node.name) -end - -function M.copy_path(node) - local absolute_path = node.absolute_path - local relative_path = utils.path_relative(absolute_path, lib.Tree.cwd) - local content = node.entries ~= nil and utils.path_add_trailing(relative_path) or relative_path - return copy_to_clipboard(content) -end - -function M.copy_absolute_path(node) - local absolute_path = node.absolute_path - local content = node.entries ~= nil and utils.path_add_trailing(absolute_path) or absolute_path - return copy_to_clipboard(content) -end - return M diff --git a/lua/nvim-tree/utils.lua b/lua/nvim-tree/utils.lua index 3bd0e208..786b794d 100644 --- a/lua/nvim-tree/utils.lua +++ b/lua/nvim-tree/utils.lua @@ -1,6 +1,8 @@ -local M = {} +local a = vim.api local uv = vim.loop +local M = {} + function M.path_to_matching_str(path) return path:gsub('(%-)', '(%%-)'):gsub('(%.)', '(%%.)'):gsub('(%_)', '(%%_)') end @@ -160,8 +162,8 @@ end ---@param comparator function|nil function M.merge_sort(t, comparator) if not comparator then - comparator = function (a, b) - return a < b + comparator = function (left, right) + return left < right end end @@ -182,4 +184,23 @@ function M.is_windows_exe(ext) return pathexts[ext:upper()] end +function M.rename_loaded_buffers(old_name, new_name) + for _, buf in pairs(a.nvim_list_bufs()) do + if a.nvim_buf_is_loaded(buf) then + if a.nvim_buf_get_name(buf) == old_name then + a.nvim_buf_set_name(buf, new_name) + -- to avoid the 'overwrite existing file' error message on write + vim.api.nvim_buf_call(buf, function() vim.cmd("silent! w!") end) + end + end + end +end + +--- @param path string path to file or directory +--- @return boolean +function M.file_exists(path) + local _, error = vim.loop.fs_stat(path) + return error == nil +end + return M