Skip to content

Commit 915efc3

Browse files
authored
fix: reuse windows and buffers when possible, prevents flashing when switching (#1011)
fixes #713
1 parent eb9bbba commit 915efc3

File tree

3 files changed

+102
-55
lines changed

3 files changed

+102
-55
lines changed

lua/neo-tree/command/init.lua

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,9 @@ M.execute = function(args)
126126

127127
-- All set, now show or focus the window
128128
local force_navigate = path_changed or do_reveal or git_base_changed or state.dirty
129-
if position_changed and args.position ~= "current" and current_position ~= "current" then
130-
manager.close(args.source)
131-
end
129+
--if position_changed and args.position ~= "current" and current_position ~= "current" then
130+
-- manager.close(args.source)
131+
--end
132132
if do_reveal then
133133
handle_reveal(args, state)
134134
else
@@ -161,7 +161,7 @@ do_show_or_focus = function(args, state, force_navigate)
161161
-- There's nothing to do here, we are already at the target state
162162
return
163163
end
164-
close_other_sources()
164+
-- close_other_sources()
165165
local current_win = vim.api.nvim_get_current_win()
166166
manager.navigate(state, args.dir, args.reveal_file, function()
167167
-- navigate changes the window to neo-tree, so just quickly hop back to the original window
@@ -173,7 +173,7 @@ do_show_or_focus = function(args, state, force_navigate)
173173
vim.api.nvim_set_current_win(state.winid)
174174
end
175175
if force_navigate or not window_exists then
176-
close_other_sources()
176+
-- close_other_sources()
177177
manager.navigate(state, args.dir, args.reveal_file, nil, false)
178178
end
179179
end

lua/neo-tree/ui/renderer.lua

Lines changed: 73 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ local events = require("neo-tree.events")
1010
local keymap = require("nui.utils.keymap")
1111
local autocmd = require("nui.utils.autocmd")
1212
local log = require("neo-tree.log")
13+
local windows = require("neo-tree.ui.windows")
1314

1415
local M = { resize_timer_interval = 50 }
1516
local ESC_KEY = vim.api.nvim_replace_termcodes("<ESC>", true, false, true)
@@ -107,7 +108,13 @@ local start_resize_monitor = function()
107108
vim.defer_fn(check_window_size, interval)
108109
end
109110

110-
M.close = function(state)
111+
---Safely closes the window and deletes the buffer associated with the state
112+
---@param state table State of the source to close
113+
---@param focus_prior_window boolean | nil if true or nil, focus the window that was previously focused
114+
M.close = function(state, focus_prior_window)
115+
if focus_prior_window == nil then
116+
focus_prior_window = true
117+
end
111118
local window_existed = false
112119
if state and state.winid then
113120
if M.window_exists(state) then
@@ -125,16 +132,16 @@ M.close = function(state)
125132
end
126133
vim.api.nvim_win_set_buf(state.winid, new_buf)
127134
else
135+
events.fire_event(events.NEO_TREE_WINDOW_BEFORE_CLOSE, args)
128136
local win_list = vim.api.nvim_tabpage_list_wins(0)
129-
if #win_list > 1 then
137+
if focus_prior_window and #win_list > 1 then
130138
local args = {
131139
position = state.current_position,
132140
source = state.name,
133141
winid = state.winid,
134142
tabnr = tabid_to_tabnr(state.tabid), -- for compatibility
135143
tabid = state.tabid,
136144
}
137-
events.fire_event(events.NEO_TREE_WINDOW_BEFORE_CLOSE, args)
138145
-- focus the prior used window if we are closing the currently focused window
139146
local current_winid = vim.api.nvim_get_current_win()
140147
if current_winid == state.winid then
@@ -143,21 +150,24 @@ M.close = function(state)
143150
pcall(vim.api.nvim_set_current_win, pwin)
144151
end
145152
end
146-
-- if the window was a float, changing the current win would have closed it already
147-
pcall(vim.api.nvim_win_close, state.winid, true)
148-
events.fire_event(events.NEO_TREE_WINDOW_AFTER_CLOSE, args)
149153
end
154+
-- if the window was a float, changing the current win would have closed it already
155+
pcall(vim.api.nvim_win_close, state.winid, true)
156+
events.fire_event(events.NEO_TREE_WINDOW_AFTER_CLOSE, args)
150157
end
151158
end
152159
end
153160
state.winid = nil
154161
end
155162
local bufnr = utils.get_value(state, "bufnr", 0, true)
156-
if bufnr > 0 then
157-
if vim.api.nvim_buf_is_valid(bufnr) then
158-
vim.api.nvim_buf_delete(bufnr, { force = true })
159-
end
163+
if bufnr > 0 and vim.api.nvim_buf_is_valid(bufnr) then
160164
state.bufnr = nil
165+
local success, err = pcall(vim.api.nvim_buf_delete, bufnr, { force = true })
166+
if not success and err:match("E523") then
167+
vim.schedule_wrap(function()
168+
vim.api.nvim_buf_delete(bufnr, { force = true })
169+
end)()
170+
end
161171
end
162172
return window_existed
163173
end
@@ -744,6 +754,7 @@ create_tree = function(state)
744754
if state.tree and state.tree.bufnr == state.bufnr then
745755
if buffer_is_usable(state.tree.bufnr) then
746756
log.debug("Tree already exists and buffer is valid, skipping creation", state.name, state.id)
757+
state.tree.winid = state.winid
747758
return
748759
end
749760
end
@@ -780,7 +791,7 @@ local get_selected_nodes = function(state)
780791
return selected_nodes
781792
end
782793

783-
local set_window_mappings = function(state)
794+
local set_buffer_mappings = function(state)
784795
local resolved_mappings = {}
785796
local skip_this_mapping = {
786797
["none"] = true,
@@ -883,16 +894,29 @@ local function create_floating_window(state, win_options, bufname)
883894
return win
884895
end
885896

886-
local get_existing_buffer = function(bufname)
897+
local get_buffer = function(bufname, state)
887898
local bufnr = vim.fn.bufnr(bufname)
888899
if bufnr > 0 then
889-
if buffer_is_usable(bufnr) then
900+
if vim.api.nvim_buf_is_valid(bufnr) and vim.api.nvim_buf_is_loaded(bufnr) then
890901
return bufnr
891902
else
892903
pcall(vim.api.nvim_buf_delete, bufnr, { force = true })
904+
bufnr = 0
893905
end
894906
end
895-
return 0
907+
if bufnr < 1 then
908+
bufnr = vim.api.nvim_create_buf(false, false)
909+
vim.api.nvim_buf_set_name(bufnr, bufname)
910+
vim.api.nvim_buf_set_option(bufnr, "buftype", "nofile")
911+
vim.api.nvim_buf_set_option(bufnr, "swapfile", false)
912+
vim.api.nvim_buf_set_option(bufnr, "filetype", "neo-tree")
913+
vim.api.nvim_buf_set_option(bufnr, "modifiable", false)
914+
vim.api.nvim_buf_set_option(bufnr, "undolevels", -1)
915+
autocmd.buf.define(bufnr, "WinLeave", function()
916+
M.position.save(state)
917+
end)
918+
end
919+
return bufnr
896920
end
897921

898922
create_window = function(state)
@@ -944,29 +968,34 @@ create_window = function(state)
944968
log.warn("Window ", winid, " is no longer valid!")
945969
return
946970
end
947-
local bufnr = get_existing_buffer(bufname)
948-
if bufnr < 1 then
949-
bufnr = vim.api.nvim_create_buf(false, false)
950-
vim.api.nvim_buf_set_name(bufnr, bufname)
951-
vim.api.nvim_buf_set_option(bufnr, "buftype", "nofile")
952-
vim.api.nvim_buf_set_option(bufnr, "swapfile", false)
953-
vim.api.nvim_buf_set_option(bufnr, "filetype", "neo-tree")
954-
vim.api.nvim_buf_set_option(bufnr, "modifiable", false)
955-
vim.api.nvim_buf_set_option(bufnr, "undolevels", -1)
956-
end
957971
state.winid = winid
958-
state.bufnr = bufnr
959-
vim.api.nvim_win_set_buf(winid, bufnr)
972+
state.bufnr = get_buffer(bufname, state)
973+
vim.api.nvim_win_set_buf(state.winid, state.bufnr)
960974
else
961-
win = NuiSplit(win_options)
962-
local bufnr = get_existing_buffer(bufname)
963-
if bufnr > 0 then
964-
win.bufnr = bufnr
975+
local close_old_window = function(new_winid)
976+
if state.winid and new_winid ~= state.winid then
977+
-- This state may be showing in another window, close it first because
978+
-- each state can only be shown in one window at a time.
979+
M.close(state, false)
980+
end
965981
end
966-
win:mount()
967-
state.winid = win.winid
968-
state.bufnr = win.bufnr
969-
vim.api.nvim_buf_set_name(state.bufnr, bufname)
982+
local location = windows.get_location(state.current_position)
983+
if location.winid > 0 then
984+
close_old_window(location.winid)
985+
state.bufnr = get_buffer(bufname, state)
986+
state.winid = location.winid
987+
vim.api.nvim_win_set_buf(state.winid, state.bufnr)
988+
else
989+
close_old_window()
990+
win = NuiSplit(win_options)
991+
win:mount()
992+
state.bufnr = win.bufnr
993+
state.winid = win.winid
994+
location.winid = state.winid
995+
vim.api.nvim_buf_set_name(state.bufnr, bufname)
996+
vim.api.nvim_set_current_win(state.winid)
997+
end
998+
location.source = state.name
970999
end
9711000
event_args.winid = state.winid
9721001
events.fire_event(events.NEO_TREE_WINDOW_AFTER_OPEN, event_args)
@@ -979,11 +1008,7 @@ create_window = function(state)
9791008
vim.api.nvim_buf_set_var(state.bufnr, "neo_tree_winid", state.winid)
9801009
end
9811010

982-
if win == nil then
983-
autocmd.buf.define(state.bufnr, "WinLeave", function()
984-
M.position.save(state)
985-
end)
986-
else
1011+
if win ~= nil then
9871012
-- Used to track the position of the cursor within the tree as it gains and loses focus
9881013
--
9891014
-- Note `WinEnter` is often too early to restore the cursor position so we do not set
@@ -999,7 +1024,7 @@ create_window = function(state)
9991024
end, { once = true })
10001025
end
10011026

1002-
set_window_mappings(state)
1027+
set_buffer_mappings(state)
10031028
return win
10041029
end
10051030

@@ -1048,16 +1073,14 @@ M.window_exists = function(state)
10481073
else
10491074
local isvalid = M.is_window_valid(winid)
10501075
window_exists = isvalid and (vim.api.nvim_win_get_number(winid) > 0)
1051-
if not window_exists then
1052-
state.winid = nil
1053-
if bufnr > 0 and vim.api.nvim_buf_is_valid(bufnr) then
1054-
state.bufnr = nil
1055-
local success, err = pcall(vim.api.nvim_buf_delete, bufnr, { force = true })
1056-
if not success and err:match("E523") then
1057-
vim.schedule_wrap(function()
1058-
vim.api.nvim_buf_delete(bufnr, { force = true })
1059-
end)()
1060-
end
1076+
if window_exists then
1077+
local bufnr = vim.api.nvim_win_get_buf(winid)
1078+
if bufnr > 0 and bufnr ~= state.bufnr then
1079+
window_exists = false
1080+
end
1081+
local buf_position = vim.api.nvim_buf_get_var(bufnr, "neo_tree_position")
1082+
if buf_position ~= position then
1083+
window_exists = false
10611084
end
10621085
end
10631086
end

lua/neo-tree/ui/windows.lua

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
local locations = {}
2+
3+
local get_location = function(location)
4+
local loc = locations[location]
5+
if loc then
6+
if loc.winid ~= 0 then
7+
-- verify the window before we return it
8+
if not vim.api.nvim_win_is_valid(loc.winid) then
9+
loc.winid = 0
10+
end
11+
end
12+
return loc
13+
end
14+
loc = {
15+
source = nil,
16+
name = location,
17+
winid = 0,
18+
}
19+
locations[location] = loc
20+
return loc
21+
end
22+
23+
local M = { get_location = get_location }
24+
return M

0 commit comments

Comments
 (0)