diff --git a/lua/broot/init.lua b/lua/broot/init.lua index 5c35384..a3912f2 100644 --- a/lua/broot/init.lua +++ b/lua/broot/init.lua @@ -16,6 +16,7 @@ local M = { ---@field config_files string[]? Paths to configuration files to pass to broot with the `--conf` argument. Values are passed to `vim.fn.expand` before being stored, so you can use environment variables and `~` in them. ---@field default_directory string|(fun(): string?)|nil `broot.broot()` calls this to determine the directory to launch broot in if the user doesn't supply one explicitly. If this function returns nil, `vim.fn.getcwd()` is used instead. ---@field create_user_commands boolean? If true, define the `:Broot` command +---@field replace_netrw boolean? If true, replace the netrw plugin and `Explore` commands ---@param opts SetupOpts function M.setup(opts) @@ -36,6 +37,10 @@ function M.setup(opts) require("broot.user_commands").create_user_commands() end + if opts.replace_netrw then + require("broot.netrw").replace_netrw() + end + M.config = vim.tbl_extend("force", M.config, opts) end @@ -73,9 +78,26 @@ function M._window_size() } end +local function reset_cursor(window) + -- Moving the cursor back to {1, 0} puts the window where we expect. + vim.api.nvim_win_set_cursor(window, { 1, 0 }) +end + +local function on_resize(window) + if vim.api.nvim_win_get_config(window).relative ~= "" then + -- Resize floating windows to full screen. + local window_resize = M._window_size() + vim.api.nvim_win_set_width(window, window_resize.width) + vim.api.nvim_win_set_height(window, window_resize.height) + end + reset_cursor(window) +end + ---@class BrootOpts ---@field extra_args string[]? A list of extra arguments to pass to broot. These are used in addition to, rather than instead of, the global `extra_args` set with `broot.setup()`. ---@field directory string? The directory to launch broot in. If not given, the `default_directory` function set in `broot.setup()` is used. +---@field buffer integer? The buffer to launch broot in. If not given, a new buffer is created. +---@field window integer? The window to launch broot in. If not given, a new floating window is opened. ---@param opts BrootOpts? function M.broot(opts) @@ -88,28 +110,43 @@ function M.broot(opts) extra_args = vim.tbl_map(vim.fn.shellescape, opts.extra_args) end - -- Create an unlisted `scratch-buffer`. - local buffer_id = vim.api.nvim_create_buf(false, true) - if buffer_id == 0 then - error "Failed to create buffer" + local buffer + if opts.buffer ~= nil then + ---@type integer + buffer = opts.buffer + else + -- Create an unlisted `scratch-buffer`. + buffer = vim.api.nvim_create_buf(false, true) + if buffer == 0 then + error "Failed to create buffer" + end end -- Don't warn when exiting the Broot buffer. - vim.api.nvim_buf_set_option(buffer_id, "modified", false) + vim.api.nvim_buf_set_option(buffer, "modified", false) - local window_size = M._window_size() - - local window_id = vim.api.nvim_open_win(buffer_id, true, { - relative = "editor", - row = 0, - col = 0, - width = window_size.width, - height = window_size.height, - style = "minimal", - }) - if window_id == 0 then - error "Failed to open window" + local window + if opts.window ~= nil then + ---@type window + window = opts.window + M._make_window_minimal(window) + vim.api.nvim_win_set_buf(window, buffer) + else + local window_size = M._window_size() + window = vim.api.nvim_open_win(buffer, true, { + relative = "editor", + row = 0, + col = 0, + width = window_size.width, + height = window_size.height, + style = "minimal", + }) + if window == 0 then + error "Failed to open window" + end end + vim.notify("Launching broot in buffer " .. buffer .. " in window " .. window, vim.log.levels.INFO) + local cmd_path = M._mktemp() local out_path = M._mktemp() local cmd = vim.fn.shellescape(M.config.broot_binary) @@ -127,7 +164,7 @@ function M.broot(opts) local cmd_opts = { on_exit = function(_job_id, exit_code, _event_type) - M._on_broot_exit(exit_code, window_id, buffer_id, cmd_path, out_path) + M._on_broot_exit(exit_code, window, buffer, cmd_path, out_path) end, } @@ -145,17 +182,31 @@ function M.broot(opts) end vim.cmd ":startinsert" - vim.api.nvim_create_augroup("Broot", {}) + local autocmd_group = vim.api.nvim_create_augroup("Broot", { clear = false }) vim.api.nvim_create_autocmd("VimResized", { - buffer = buffer_id, - group = "Broot", + buffer = buffer, + group = autocmd_group, + nested = true, + callback = function() + on_resize(window) + end, + }) + vim.api.nvim_create_autocmd("WinResized", { + buffer = buffer, + group = autocmd_group, nested = true, callback = function() - local window_resize = M._window_size() - vim.api.nvim_win_set_width(window_id, window_resize.width) - vim.api.nvim_win_set_height(window_id, window_resize.height) - -- Moving the cursor back to {1, 0} puts the window where we expect. - vim.api.nvim_win_set_cursor(window_id, { 1, 0 }) + for _, resized in ipairs(vim.v.event.windows) do + on_resize(resized) + end + end, + }) + vim.api.nvim_create_autocmd("WinLeave", { + buffer = buffer, + group = autocmd_group, + nested = true, + callback = function() + reset_cursor(window) end, }) end @@ -169,8 +220,14 @@ function M._on_broot_exit(exit_code, window_id, buffer_id, cmd_path, out_path) if exit_code ~= 0 then error("Broot failed with exit code " .. exit_code) end - vim.api.nvim_win_close(window_id, true) + vim.api.nvim_buf_delete(buffer_id, { force = true }) + + -- Only close the window if it's not the last one. + if vim.fn.winnr "$" > 1 then + vim.api.nvim_win_close(window_id, true) + end + M._read_outcmd_path(cmd_path) M._read_stdout_path(out_path) end @@ -211,4 +268,35 @@ function M._read_stdout_path(out_path) end end +---Like the 'minimal' window style but for non-floating windows. +function M._make_window_minimal(window) + vim.api.nvim_win_set_option(window, "number", false) + vim.api.nvim_win_set_option(window, "relativenumber", false) + vim.api.nvim_win_set_option(window, "cursorline", false) + vim.api.nvim_win_set_option(window, "cursorcolumn", false) + vim.api.nvim_win_set_option(window, "foldcolumn", "auto") + vim.api.nvim_win_set_option(window, "spell", false) + vim.api.nvim_win_set_option(window, "list", false) + vim.api.nvim_win_set_option(window, "signcolumn", "auto") + vim.api.nvim_win_set_option(window, "colorcolumn", "") + vim.api.nvim_win_set_option(window, "statuscolumn", "") + + local fillchars = vim.api.nvim_win_get_option(window, "fillchars") + if #fillchars == 0 then + fillchars = "eob: " + else + fillchars = fillchars .. ",eob: " + end + vim.api.nvim_win_set_option(window, "fillchars", fillchars) + + local winhighlight = vim.api.nvim_win_get_option(window, "winhighlight") + if #winhighlight == 0 then + winhighlight = "EndOfBuffer:NONE" + else + winhighlight = winhighlight .. ",EndOfBuffer:NONE" + end + + vim.api.nvim_win_set_option(window, "winhighlight", winhighlight) +end + return M diff --git a/lua/broot/netrw.lua b/lua/broot/netrw.lua new file mode 100644 index 0000000..157631e --- /dev/null +++ b/lua/broot/netrw.lua @@ -0,0 +1,104 @@ +local M = {} + +function M.replace_netrw() + local group_id = vim.api.nvim_create_augroup("BrootNetrw", {}) + vim.api.nvim_create_autocmd("VimEnter", { + group = group_id, + desc = "Clear the FileExplorer group", + callback = function(_event) + vim.api.nvim_clear_autocmds { + group = "FileExplorer", + } + vim.api.nvim_create_augroup("FileExplorer", {}) + M._replace_netrw_commands() + end, + }) + + vim.api.nvim_create_autocmd("BufEnter", { + group = group_id, + desc = "Use Broot to browse directories", + callback = function(event) + if vim.fn.isdirectory(event.file) == 1 then + require("broot").broot { + buffer = event.buf, + window = vim.fn.win_getid(), + directory = event.file, + } + end + end, + }) + + M._replace_netrw_commands() +end + +function M._replace_netrw_commands() + local current_file = require("broot.default_directory").current_file() + + vim.api.nvim_create_user_command("Explore", function(opts) + require("broot").broot { + directory = opts.args or current_file(), + window = vim.fn.win_getid(), + } + end, { + nargs = "?", + desc = "Explore the given directory with broot", + }) + + vim.api.nvim_create_user_command("Hexplore", function(opts) + vim.cmd [[split]] + require("broot").broot { + directory = opts.args or current_file(), + window = vim.fn.win_getid(), + } + end, { + nargs = "?", + desc = "Explore the given directory with broot", + }) + + vim.api.nvim_create_user_command("Vexplore", function(opts) + vim.cmd [[vsplit]] + require("broot").broot { + directory = opts.args or current_file(), + window = vim.fn.win_getid(), + } + end, { + nargs = "?", + desc = "Explore the given directory with broot", + }) + + vim.api.nvim_create_user_command("Lexplore", function(opts) + vim.cmd [[leftabove vsplit]] + require("broot").broot { + directory = opts.args or current_file(), + window = vim.fn.win_getid(), + } + end, { + nargs = "?", + desc = "Explore the given directory with broot", + }) + + vim.api.nvim_create_user_command("Sexplore", function(opts) + vim.cmd [[leftabove split]] + require("broot").broot { + directory = opts.args or current_file(), + window = vim.fn.win_getid(), + } + end, { + nargs = "?", + desc = "Explore the given directory with broot", + }) + + vim.api.nvim_create_user_command("Texplore", function(opts) + vim.cmd [[tabnew]] + require("broot").broot { + directory = opts.args or current_file(), + window = vim.fn.win_getid(), + buffer = vim.fn.bufnr(), + } + end, { + nargs = "?", + desc = "Explore the given directory with broot", + }) +end + +return M diff --git a/nix/luarc.nix b/nix/luarc.nix index 179675c..599e94f 100644 --- a/nix/luarc.nix +++ b/nix/luarc.nix @@ -9,6 +9,7 @@ text = builtins.toJSON { "workspace.library" = [ "./lua" + "./scripts/lua" "${neodev}/types/stable" "\${3rd}/luv/library" "\${3rd}/luassert/library" diff --git a/tests/screenshots/tests-test_netrw.lua---can_explore_side_by_side b/tests/screenshots/tests-test_netrw.lua---can_explore_side_by_side new file mode 100644 index 0000000..3d65688 --- /dev/null +++ b/tests/screenshots/tests-test_netrw.lua---can_explore_side_by_side @@ -0,0 +1,51 @@ +--|---------|---------|---------|---------|---------|---------|---------|---------| +01|. │. +02|├──conf.toml │├──conf.toml +03|├──doggy │├──doggy +04|├──flavors │├──flavors +05|│ ├──goldie ││ ├──goldie +06|│ ├──samoyed ││ ├──samoyed +07|│ └──weenie ││ └──weenie +08|├──nvim.toml │├──nvim.toml +09|└──puppy │└──puppy +10| │ +11| │ +12| │ +13| │ +14| │ +15| │ +16| │ +17| │ +18| │ +19| │ +20| │ +21| Hit enter to go up, ? for help │ Hit e…r to go up, ? for h…,…l…s to s…h +22| h:n gi:y │ h:n gi:y +23|3' 1' +24|-- TERMINAL -- + +--|---------|---------|---------|---------|---------|---------|---------|---------| +01|01111111111111111111111111111111111111112011111111111111111111111111111111111111 +02|33344444444444444444444444444444444444442333444444444444444444444444444444444444 +03|33344444444444444444444444444444444444442333444444444444444444444444444444444444 +04|33355555554444444444444444444444444444442333555555544444444444444444444444444444 +05|33333344444444444444444444444444444444442333333444444444444444444444444444444444 +06|33333344444444444444444444444444444444442333333444444444444444444444444444444444 +07|33333344444444444444444444444444444444442333333444444444444444444444444444444444 +08|33344444444444444444444444444444444444442333444444444444444444444444444444444444 +09|33344444444444444444444444444444444444442333444444444444444444444444444444444444 +10|44444444444444444444444444444444444444442444444444444444444444444444444444444444 +11|44444444444444444444444444444444444444442444444444444444444444444444444444444444 +12|44444444444444444444444444444444444444442444444444444444444444444444444444444444 +13|44444444444444444444444444444444444444442444444444444444444444444444444444444444 +14|44444444444444444444444444444444444444442444444444444444444444444444444444444444 +15|44444444444444444444444444444444444444442444444444444444444444444444444444444444 +16|44444444444444444444444444444444444444442444444444444444444444444444444444444444 +17|44444444444444444444444444444444444444442444444444444444444444444444444444444444 +18|44444444444444444444444444444444444444442444444444444444444444444444444444444444 +19|44444444444444444444444444444444444444442444444444444444444444444444444444444444 +20|44444444444444444444444444444444444444442444444444444444444444444444444444444444 +21|66666777776666666666676666666666666666662666667876666666666676666668686866666686 +22|9:::::::::::::::::::::::::::;;;<;;;;;<;429::::::::::::::::::::::::::;;;<;;;;;<;: +23|=========================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +24|??????????????222222222222222222222222222222222222222222222222222222222222222222 diff --git a/tests/test_netrw.lua b/tests/test_netrw.lua new file mode 100644 index 0000000..89c3b31 --- /dev/null +++ b/tests/test_netrw.lua @@ -0,0 +1,21 @@ +local MiniTest = require "mini.test" +local expect = MiniTest.expect + +local T, child = require("test").new_set { + module = "broot.netrw", + pre_case = [[ + require("broot").setup { + replace_netrw = true, + } + ]], +} + +T["can_explore_side_by_side"] = function() + child.cmd [[Explore]] + vim.loop.sleep(250) + child.cmd [[Lexplore]] + vim.loop.sleep(250) + expect.reference_screenshot(child.get_screenshot()) +end + +return T