diff --git a/lua/neo-tree.lua b/lua/neo-tree.lua index f6c86482..f3decadd 100644 --- a/lua/neo-tree.lua +++ b/lua/neo-tree.lua @@ -17,7 +17,7 @@ M.ensure_config = function() end end -M.get_prior_window = function(ignore_filetypes) +M.get_prior_window = function(ignore_filetypes, ignore_winfixbuf) ignore_filetypes = ignore_filetypes or {} local ignore = utils.list_to_dict(ignore_filetypes) ignore["neo-tree"] = true @@ -32,7 +32,7 @@ M.get_prior_window = function(ignore_filetypes) local last_win = wins[win_index] if type(last_win) == "number" then local success, is_valid = pcall(vim.api.nvim_win_is_valid, last_win) - if success and is_valid then + if success and is_valid and not (ignore_winfixbuf and utils.is_winfixbuf(last_win)) then local buf = vim.api.nvim_win_get_buf(last_win) local ft = vim.api.nvim_buf_get_option(buf, "filetype") local bt = vim.api.nvim_buf_get_option(buf, "buftype") or "normal" diff --git a/lua/neo-tree/setup/init.lua b/lua/neo-tree/setup/init.lua index ca99ddca..bb5a7b8e 100644 --- a/lua/neo-tree/setup/init.lua +++ b/lua/neo-tree/setup/init.lua @@ -197,8 +197,17 @@ M.buffer_enter_event = function() end last_buffer_enter_filetype = vim.bo.filetype + -- For all others, make sure another buffer is not hijacking our window + -- ..but not if the position is "current" + local prior_buf = vim.fn.bufnr("#") + if prior_buf < 1 then + return + end + local prior_type = vim.api.nvim_buf_get_option(prior_buf, "filetype") + -- there is nothing more we want to do with floating windows - if utils.is_floating() then + -- but when prior_type is neo-tree we might need to redirect buffer somewhere else. + if utils.is_floating() and prior_type ~= "neo-tree" then return end @@ -207,14 +216,6 @@ M.buffer_enter_event = function() return end - -- For all others, make sure another buffer is not hijacking our window - -- ..but not if the position is "current" - local prior_buf = vim.fn.bufnr("#") - if prior_buf < 1 then - return - end - local winid = vim.api.nvim_get_current_win() - local prior_type = vim.api.nvim_buf_get_option(prior_buf, "filetype") if prior_type == "neo-tree" then local success, position = pcall(vim.api.nvim_buf_get_var, prior_buf, "neo_tree_position") if not success then diff --git a/lua/neo-tree/ui/renderer.lua b/lua/neo-tree/ui/renderer.lua index 68babe35..497033b1 100644 --- a/lua/neo-tree/ui/renderer.lua +++ b/lua/neo-tree/ui/renderer.lua @@ -164,15 +164,12 @@ M.close = function(state, focus_prior_window) state.winid = nil end local bufnr = utils.get_value(state, "bufnr", 0, true) - if bufnr > 0 and vim.api.nvim_buf_is_valid(bufnr) then - state.bufnr = nil - local success, err = pcall(vim.api.nvim_buf_delete, bufnr, { force = true }) - if not success and err:match("E523") then - vim.schedule_wrap(function() - vim.api.nvim_buf_delete(bufnr, { force = true }) - end)() + state.bufnr = nil + vim.schedule(function() + if bufnr > 0 and vim.api.nvim_buf_is_valid(bufnr) then + vim.api.nvim_buf_delete(bufnr, { force = true }) end - end + end) return window_existed end @@ -1040,7 +1037,9 @@ M.acquire_window = function(state) M.position.save(state) end) win:on({ "BufDelete" }, function() - win:unmount() + vim.schedule(function () + win:unmount() + end) end, { once = true }) end diff --git a/lua/neo-tree/utils/init.lua b/lua/neo-tree/utils/init.lua index fab1a591..7882f192 100644 --- a/lua/neo-tree/utils/init.lua +++ b/lua/neo-tree/utils/init.lua @@ -508,6 +508,14 @@ M.is_floating = function(win_id) return false end +M.is_winfixbuf = function(win_id) + if vim.fn.exists("&winfixbuf") == 1 then + win_id = win_id or vim.api.nvim_get_current_win() + return vim.api.nvim_get_option_value("winfixbuf", { win = win_id }) + end + return false +end + ---Evaluates the value of , which comes from an autocmd event, and determines if it ---is a valid file or some sort of utility buffer like quickfix or neo-tree itself. ---@param afile string The path or relative path to the file. @@ -565,7 +573,7 @@ M.map = function(tbl, fn) return t end -M.get_appropriate_window = function(state) +M.get_appropriate_window = function(state, ignore_winfixbuf) -- Avoid triggering autocommands when switching windows local eventignore = vim.o.eventignore vim.o.eventignore = "all" @@ -579,7 +587,7 @@ M.get_appropriate_window = function(state) local ignore = M.list_to_dict(ignore_ft) ignore["neo-tree"] = true if nt.config.open_files_in_last_window then - local prior_window = nt.get_prior_window(ignore) + local prior_window = nt.get_prior_window(ignore, ignore_winfixbuf) if prior_window > 0 then local success = pcall(vim.api.nvim_set_current_win, prior_window) if success then @@ -601,6 +609,9 @@ M.get_appropriate_window = function(state) if ignore[vim.bo.filetype] or ignore[bt] or M.is_floating() then attempts = attempts + 1 vim.cmd("wincmd w") + elseif ignore_winfixbuf and M.is_winfixbuf() then + attempts = attempts + 1 + vim.cmd("wincmd w") else suitable_window_found = true end @@ -642,6 +653,28 @@ M.resolve_width = function(width) return math.floor(width) end +M.force_new_split = function(current_position, escaped_path) + local result, err + local split_command = "vsplit" + -- respect window position in user config when Neo-tree is the only window + if current_position == "left" then + split_command = "rightbelow vs" + elseif current_position == "right" then + split_command = "leftabove vs" + end + if escaped_path == M.escape_path_for_cmd("[No Name]") then + -- vim's default behavior is to overwrite [No Name] buffers. + -- We need to split first and then open the path to workaround this behavior. + result, err = pcall(vim.cmd, split_command) + if result then + vim.cmd.edit(escaped_path) + end + else + result, err = pcall(vim.cmd, split_command .. " " .. escaped_path) + end + return result, err +end + ---Open file in the appropriate window. ---@param state table The state of the source ---@param path string The file to open @@ -654,7 +687,8 @@ M.open_file = function(state, path, open_cmd, bufnr) if bufnr <= 0 then bufnr = nil else - local buf_cmd_lookup = { edit = "b", e = "b", split = "sb", sp = "sb", vsplit = "vert sb", vs = "vert sb" } + local buf_cmd_lookup = + { edit = "b", e = "b", split = "sb", sp = "sb", vsplit = "vert sb", vs = "vert sb" } local cmd_for_buf = buf_cmd_lookup[open_cmd] if cmd_for_buf then open_cmd = cmd_for_buf @@ -692,28 +726,24 @@ M.open_file = function(state, path, open_cmd, bufnr) width = M.get_value(state, "window.width", 40, false) width = M.resolve_width(width) end - - local split_command = "vsplit" - -- respect window position in user config when Neo-tree is the only window - if state.current_position == "left" then - split_command = "rightbelow vs" - elseif state.current_position == "right" then - split_command = "leftabove vs" - end - if path == "[No Name]" then - result, err = pcall(vim.cmd, split_command) - if result then - vim.cmd("b" .. bufnr) - end - else - result, err = pcall(vim.cmd, split_command .. " " .. escaped_path) - end - + result, err = M.force_new_split(state.current_position, escaped_path) vim.api.nvim_win_set_width(winid, width) else result, err = pcall(vim.cmd, open_cmd .. " " .. bufnr_or_path) end end + if not result and string.find(err or "", "winfixbuf") and M.is_winfixbuf() then + local winid, is_neo_tree_window = M.get_appropriate_window(state, true) + -- Rescan window list to find a window that is not winfixbuf. + -- If found, retry executing command in that window, + -- otherwise, all windows are either neo-tree or winfixbuf so we make a new split. + if not is_neo_tree_window and not M.is_winfixbuf(winid) then + vim.api.nvim_set_current_win(winid) + result, err = pcall(vim.cmd, open_cmd .. " " .. bufnr_or_path) + else + result, err = M.force_new_split(state.current_position, escaped_path) + end + end if result or err == "Vim(edit):E325: ATTENTION" then -- fixes #321 vim.api.nvim_buf_set_option(0, "buflisted", true) diff --git a/tests/neo-tree/command/command_current_spec.lua b/tests/neo-tree/command/command_current_spec.lua index 324097e7..e5329886 100644 --- a/tests/neo-tree/command/command_current_spec.lua +++ b/tests/neo-tree/command/command_current_spec.lua @@ -15,6 +15,11 @@ local run_in_current_command = function(command, expected_tree_node) end end +local run_close_command = function(command) + vim.cmd(command) + u.wait_for(function() end, { interval = 200, timeout = 200 }) +end + describe("Command", function() local test = u.fs.init_test({ items = { @@ -71,7 +76,7 @@ describe("Command", function() run_in_current_command(cmd, testfile) -- toggle CLOSE - vim.cmd(cmd) + run_close_command(cmd) verify.window_handle_is(tree_winid) verify.buf_name_is(testfile) end) diff --git a/tests/neo-tree/command/command_spec.lua b/tests/neo-tree/command/command_spec.lua index 8202c284..8bc23555 100644 --- a/tests/neo-tree/command/command_spec.lua +++ b/tests/neo-tree/command/command_spec.lua @@ -39,6 +39,11 @@ local run_show_command = function(command, expected_tree_node) end, "Expected to see a new window without focusing it.") end +local run_close_command = function(command) + vim.cmd(command) + u.wait_for(function() end, { interval = 200, timeout = 200 }) +end + describe("Command", function() local test = u.fs.init_test({ items = { @@ -87,7 +92,7 @@ describe("Command", function() local tree_winid = vim.api.nvim_get_current_win() -- toggle CLOSE - vim.cmd(cmd) + run_close_command(cmd) verify.window_handle_is_not(tree_winid) verify.buf_name_is(testfile) @@ -109,7 +114,7 @@ describe("Command", function() local tree_winid = vim.api.nvim_get_current_win() -- toggle CLOSE - vim.cmd("Neotree float reveal toggle") + run_close_command("Neotree float reveal toggle") verify.window_handle_is_not(tree_winid) verify.buf_name_is(testfile) @@ -158,7 +163,7 @@ describe("Command", function() verify.after(500, function() -- toggle CLOSE - vim.cmd(cmd) + run_close_command(cmd) -- toggle OPEN u.editfile(topfile) @@ -189,7 +194,7 @@ describe("Command", function() run_focus_command("Neotree reveal", baz) local expected_tree_node = baz -- toggle CLOSE - vim.cmd(cmd) + run_close_command(cmd) verify.after(500, function() -- toggle OPEN