Skip to content

Commit 88a9ab1

Browse files
committed
refactor(ui): separate stack view state and window lifecycle
1 parent 90af8bd commit 88a9ab1

5 files changed

Lines changed: 572 additions & 1575 deletions

File tree

Lines changed: 28 additions & 266 deletions
Original file line numberDiff line numberDiff line change
@@ -1,274 +1,59 @@
1-
local config = require("peekstack.config")
21
local keymaps = require("peekstack.ui.stack_view.keymaps")
32
local renderer = require("peekstack.ui.stack_view.render")
3+
local state = require("peekstack.ui.stack_view.state")
4+
local window = require("peekstack.ui.stack_view.window")
45

56
local M = {}
67

7-
---@type table<integer, PeekstackStackViewState>
8-
local states = {}
9-
---@type integer?
10-
local tab_cleanup_group = nil
11-
12-
---@param s PeekstackStackViewState
13-
local function cleanup_state(s)
14-
if s.help_winid and vim.api.nvim_win_is_valid(s.help_winid) then
15-
pcall(vim.api.nvim_win_close, s.help_winid, true)
16-
end
17-
s.help_winid = nil
18-
s.help_bufnr = nil
19-
20-
if s.help_augroup then
21-
pcall(vim.api.nvim_del_augroup_by_id, s.help_augroup)
22-
s.help_augroup = nil
23-
end
24-
25-
if s.autoclose_group then
26-
pcall(vim.api.nvim_del_augroup_by_id, s.autoclose_group)
27-
s.autoclose_group = nil
28-
end
29-
end
30-
31-
local function cleanup_invalid_states()
32-
for tabpage, s in pairs(states) do
33-
if not vim.api.nvim_tabpage_is_valid(tabpage) then
34-
cleanup_state(s)
35-
states[tabpage] = nil
36-
end
37-
end
38-
end
39-
40-
---Setup stack view autocmds.
41-
function M.setup()
42-
if tab_cleanup_group then
43-
return
44-
end
45-
46-
tab_cleanup_group = vim.api.nvim_create_augroup("PeekstackStackViewTabCleanup", { clear = true })
47-
vim.api.nvim_create_autocmd("TabClosed", {
48-
group = tab_cleanup_group,
49-
callback = function()
50-
cleanup_invalid_states()
51-
end,
52-
})
53-
end
54-
55-
---@return PeekstackStackViewState
56-
local function get_state()
57-
local tabpage = vim.api.nvim_get_current_tabpage()
58-
if not states[tabpage] then
59-
states[tabpage] = {
60-
bufnr = nil,
61-
winid = nil,
62-
root_winid = nil,
63-
line_to_id = {},
64-
render_keys = {},
65-
filter = nil,
66-
header_lines = 0,
67-
help_bufnr = nil,
68-
help_winid = nil,
69-
help_augroup = nil,
70-
autoclose_group = nil,
71-
autoclose_suspended = 0,
72-
preview_ts_cache = {},
73-
}
74-
end
75-
return states[tabpage]
76-
end
77-
78-
---@param s PeekstackStackViewState
79-
---@return boolean
80-
local function is_open(s)
81-
return s.winid ~= nil and vim.api.nvim_win_is_valid(s.winid)
82-
end
83-
84-
---@param s PeekstackStackViewState
85-
---@return boolean
86-
local function is_ready(s)
87-
return s.bufnr ~= nil and s.winid ~= nil and vim.api.nvim_buf_is_valid(s.bufnr) and vim.api.nvim_win_is_valid(s.winid)
88-
end
89-
90-
---@param s PeekstackStackViewState
91-
local function focus_stack_view(s)
92-
if s.winid and vim.api.nvim_win_is_valid(s.winid) then
93-
vim.api.nvim_set_current_win(s.winid)
94-
end
95-
end
96-
97-
---@param s PeekstackStackViewState
98-
---@return boolean
99-
local function should_autoclose(s)
100-
if s.autoclose_suspended and s.autoclose_suspended > 0 then
101-
return false
102-
end
103-
if not is_open(s) then
104-
return false
105-
end
106-
107-
local current = vim.api.nvim_get_current_win()
108-
if s.winid and current == s.winid then
109-
return false
110-
end
111-
if s.help_winid and vim.api.nvim_win_is_valid(s.help_winid) and current == s.help_winid then
112-
return false
113-
end
114-
115-
return true
116-
end
117-
1188
---@param s PeekstackStackViewState
1199
local function render_state(s)
120-
renderer.render(s, is_ready)
10+
renderer.render(s, window.is_ready)
12111
end
12212

12313
---@return PeekstackStackViewKeymapDeps
12414
local function keymap_deps()
12515
return {
126-
render = function(s)
127-
render_state(s)
128-
end,
16+
render = render_state,
12917
toggle = function()
13018
M.toggle()
13119
end,
13220
is_open = function(s)
133-
return is_open(s)
21+
return window.is_open(s)
13422
end,
13523
focus_stack_view = function(s)
136-
focus_stack_view(s)
24+
window.focus(s)
13725
end,
13826
}
13927
end
14028

14129
---@param s PeekstackStackViewState
142-
local function reset_open_state(s)
143-
if s.autoclose_group then
144-
pcall(vim.api.nvim_del_augroup_by_id, s.autoclose_group)
145-
end
146-
s.autoclose_group = nil
147-
148-
if s.winid and vim.api.nvim_win_is_valid(s.winid) then
149-
pcall(vim.api.nvim_win_close, s.winid, true)
150-
end
151-
152-
s.winid = nil
153-
s.bufnr = nil
154-
s.root_winid = nil
155-
s.render_keys = {}
156-
s.autoclose_suspended = 0
157-
s.help_augroup = nil
158-
s.preview_ts_cache = {}
30+
---@param opts? { refocus: boolean }
31+
local function close_stack_view(s, opts)
32+
keymaps.close_help(s, opts, keymap_deps())
33+
window.close(s)
15934
end
16035

161-
---Find a non-floating window to use as root.
162-
---@return integer
163-
local function find_root_winid()
164-
local winid = vim.api.nvim_get_current_win()
165-
local cfg = vim.api.nvim_win_get_config(winid)
166-
if cfg.relative == "" then
167-
return winid
168-
end
169-
for _, w in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
170-
local c = vim.api.nvim_win_get_config(w)
171-
if c.relative == "" then
172-
return w
173-
end
174-
end
175-
return winid
176-
end
177-
178-
---@return integer
179-
local function editor_lines()
180-
return math.max(vim.o.lines - vim.o.cmdheight, 1)
181-
end
182-
183-
---@return PeekstackRenderWinOpts
184-
local function stack_view_win_config()
185-
local columns = vim.o.columns
186-
local lines = editor_lines()
187-
local position = config.get().ui.stack_view.position or "right"
188-
189-
local width = math.max(30, math.floor(columns * 0.3))
190-
width = math.min(width, columns)
191-
192-
local height = math.max(6, lines - 2)
193-
height = math.min(height, lines)
194-
195-
local row = 0
196-
local col = math.max(columns - width, 0)
197-
198-
if position == "left" then
199-
col = 0
200-
elseif position == "bottom" then
201-
width = columns
202-
height = math.max(6, math.floor(lines * 0.3))
203-
height = math.min(height, lines)
204-
row = math.max(lines - height, 0)
205-
col = 0
206-
end
207-
208-
return {
209-
relative = "editor",
210-
row = row,
211-
col = col,
212-
width = width,
213-
height = height,
214-
style = "minimal",
215-
border = "rounded",
216-
focusable = true,
217-
zindex = 100,
218-
title = "Stack View",
219-
title_pos = "center",
220-
}
36+
---Setup stack view autocmds.
37+
function M.setup()
38+
state.setup()
22139
end
22240

22341
---Open the stack view.
22442
function M.open()
22543
M.setup()
22644

227-
local s = get_state()
228-
if is_open(s) then
229-
vim.api.nvim_set_current_win(s.winid)
45+
local s = state.current()
46+
if window.is_open(s) then
47+
window.focus(s)
23048
render_state(s)
23149
return
23250
end
23351

234-
s.autoclose_suspended = 0
235-
s.root_winid = find_root_winid()
236-
s.bufnr = vim.api.nvim_create_buf(false, true)
237-
s.winid = vim.api.nvim_open_win(s.bufnr, true, stack_view_win_config())
238-
s.render_keys = {}
239-
240-
vim.wo[s.winid].cursorline = true
241-
vim.wo[s.winid].winhighlight = "CursorLine:PeekstackStackViewCursorLine"
242-
vim.api.nvim_win_set_var(s.winid, "peekstack_root_winid", s.root_winid)
243-
require("peekstack.core.stack")._register_stack_view_win(s.winid)
244-
245-
local fs = require("peekstack.util.fs")
246-
fs.configure_buffer(s.bufnr)
247-
vim.bo[s.bufnr].modifiable = false
248-
vim.bo[s.bufnr].filetype = "peekstack-stack"
249-
250-
local group_name = string.format("PeekstackStackViewAutoClose:%d", s.bufnr)
251-
local au_group = vim.api.nvim_create_augroup(group_name, { clear = true })
252-
s.autoclose_group = au_group
253-
254-
vim.api.nvim_create_autocmd("WinLeave", {
255-
group = au_group,
256-
buffer = s.bufnr,
257-
callback = function()
258-
vim.schedule(function()
259-
if not should_autoclose(s) then
260-
return
261-
end
262-
keymaps.close_help(s, nil, keymap_deps())
263-
reset_open_state(s)
264-
end)
52+
window.open(s, {
53+
before_close = function(opts)
54+
keymaps.close_help(s, opts, keymap_deps())
26555
end,
266-
})
267-
268-
vim.api.nvim_create_autocmd("CursorMoved", {
269-
group = au_group,
270-
buffer = s.bufnr,
271-
callback = function()
56+
ensure_non_header_cursor = function()
27257
keymaps.ensure_non_header_cursor(s)
27358
end,
27459
})
@@ -279,54 +64,31 @@ end
27964

28065
---Toggle the stack view (open if closed, close if open).
28166
function M.toggle()
282-
local s = get_state()
283-
if is_open(s) then
284-
keymaps.close_help(s, nil, keymap_deps())
285-
reset_open_state(s)
67+
local s = state.current()
68+
if window.is_open(s) then
69+
close_stack_view(s)
28670
return
28771
end
28872
M.open()
28973
end
29074

29175
---Re-render all open stack views (called on push/close events).
29276
function M.refresh_all()
293-
for _, s in pairs(states) do
294-
if is_open(s) and s.bufnr and vim.api.nvim_buf_is_valid(s.bufnr) then
77+
for _, s in pairs(state.all()) do
78+
if window.is_ready(s) then
29579
render_state(s)
29680
end
29781
end
29882
end
29983

30084
---Resize and re-render all open stack views (called on VimResized/WinResized).
30185
function M.resize_all()
302-
for _, s in pairs(states) do
303-
if is_open(s) and s.winid and vim.api.nvim_win_is_valid(s.winid) then
304-
pcall(vim.api.nvim_win_set_config, s.winid, stack_view_win_config())
86+
for _, s in pairs(state.all()) do
87+
if window.is_open(s) then
88+
window.resize(s)
30589
render_state(s)
30690
end
30791
end
30892
end
30993

310-
---Get stack view state (for testing).
311-
---@return PeekstackStackViewState
312-
function M._get_state()
313-
return get_state()
314-
end
315-
316-
---Get stack view state count (for testing).
317-
---@return integer
318-
function M._state_count()
319-
local count = 0
320-
for _ in pairs(states) do
321-
count = count + 1
322-
end
323-
return count
324-
end
325-
326-
---Render stack view state (for testing).
327-
---@param s PeekstackStackViewState
328-
function M._render(s)
329-
render_state(s)
330-
end
331-
33294
return M

0 commit comments

Comments
 (0)