Skip to content

Commit 2a35364

Browse files
authored
Merge pull request #19 from mhiro2/feat/add-zoom-to-temporarily-maximize-popup
feat(ui): Add zoom to temporarily maximize the top popup
2 parents 246762b + 412a361 commit 2a35364

15 files changed

Lines changed: 410 additions & 54 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ Built-in provider names:
113113
- `:PeekstackRestoreAllPopups` — restore all closed popups
114114
- `:PeekstackCloseAll` — close all popups in the current stack
115115
- `:PeekstackToggle` — temporarily hide/show all popups in the current stack
116+
- `:PeekstackZoom` — toggle zoom (maximize the top popup to fill the editor)
116117
- `:PeekstackHistory` — show popup history and select to restore
117118
- `:PeekstackQuickPeek [provider]` — quick peek without stacking (default: `lsp.definition`, accepts any registered provider)
118119

@@ -127,6 +128,7 @@ Defaults inside popup windows:
127128
- `<C-v>` — promote to vertical split
128129
- `<C-t>` — promote to new tab
129130
- `<leader>os` — open stack view
131+
- `<C-z>` — toggle zoom (maximize top popup)
130132

131133
Defaults in stack view:
132134

@@ -140,6 +142,7 @@ Defaults in stack view:
140142
- `/` — filter
141143
- `gg/G` — jump to first/last stack item
142144
- `j/k` — move cursor by stack item (skip header/preview lines)
145+
- `z` — toggle zoom (maximize top popup)
143146
- `?` — help
144147
- `q` — close
145148

@@ -228,6 +231,7 @@ Configure via `require("peekstack").setup({ ... })`.
228231
promote_vsplit = "<C-v>",
229232
promote_tab = "<C-t>",
230233
toggle_stack_view = "<leader>os",
234+
zoom = "<C-z>",
231235
},
232236
},
233237
picker = {

doc/peekstack.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ With options:
9898
promote_vsplit = "<C-v>",
9999
promote_tab = "<C-t>",
100100
toggle_stack_view = "<leader>os",
101+
zoom = "<C-z>",
101102
},
102103
},
103104
picker = {
@@ -297,6 +298,13 @@ Commands are registered after `setup()` is called.
297298
again recreates the windows in their original layout. Pushing a new
298299
popup while hidden automatically restores visibility.
299300

301+
:PeekstackZoom *:PeekstackZoom*
302+
Toggle zoom on the top popup. When zoomed, the popup fills the entire
303+
editor. Call again to restore the normal layout. The border highlight
304+
changes to `PeekstackPopupBorderZoomed` while zoomed. Zoom is
305+
automatically cleared when the zoomed popup is closed, a new popup
306+
is pushed, or the stack is hidden via |:PeekstackToggle|.
307+
300308
:PeekstackHistory *:PeekstackHistory*
301309
Show popup history and select an entry to restore.
302310

@@ -496,6 +504,7 @@ Default keymaps inside popup windows:
496504
`<C-v>` Promote to vertical split
497505
`<C-t>` Promote to new tab
498506
`<leader>os` Toggle the stack view panel
507+
`<C-z>` Toggle zoom (maximize top popup)
499508

500509
Stack view keymaps:
501510

@@ -509,6 +518,7 @@ Stack view keymaps:
509518
`/` Filter the list
510519
`gg/G` Jump to first/last stack item
511520
`j/k` Move cursor by stack item (skip header/preview lines)
521+
`z` Toggle zoom (maximize top popup)
512522
`?` Toggle help
513523
`q` Close the stack view
514524

@@ -534,6 +544,7 @@ Title highlights:
534544
Popup border highlights:
535545
PeekstackPopupBorder Unfocused popup border (links to FloatBorder)
536546
PeekstackPopupBorderFocused Focused popup border (links to Function)
547+
PeekstackPopupBorderZoomed Zoomed popup border (links to WarningMsg)
537548

538549
Stack view highlights:
539550
PeekstackStackViewIndex Entry index number (links to LineNr)

lua/peekstack/commands.lua

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ local COMMAND_NAMES = {
1414
"PeekstackCloseAll",
1515
"PeekstackQuickPeek",
1616
"PeekstackToggle",
17+
"PeekstackZoom",
1718
}
1819

1920
---@param session PeekstackSession|table
@@ -181,6 +182,13 @@ function M.setup()
181182
end
182183
end, {})
183184

185+
vim.api.nvim_create_user_command("PeekstackZoom", function()
186+
local toggled = require("peekstack.core.stack").toggle_zoom()
187+
if not toggled then
188+
notify.info("No popups in the current stack")
189+
end
190+
end, {})
191+
184192
vim.api.nvim_create_user_command("PeekstackQuickPeek", function(opts)
185193
local provider = opts.args and opts.args ~= "" and opts.args or "lsp.definition"
186194
require("peekstack").peek(provider, { mode = "quick" })

lua/peekstack/config.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ M.defaults = {
156156
promote_vsplit = "<C-v>",
157157
promote_tab = "<C-t>",
158158
toggle_stack_view = "<leader>os",
159+
zoom = "<C-z>",
159160
},
160161
},
161162
picker = {

lua/peekstack/core/events.lua

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,11 @@ function M.setup()
125125
local close_events = cfg.ui.quick_peek and cfg.ui.quick_peek.close_events
126126
or { "CursorMoved", "InsertEnter", "BufLeave", "WinLeave" }
127127

128-
for _, event in ipairs(close_events) do
129-
vim.api.nvim_create_autocmd(event, {
130-
group = group,
131-
callback = close_ephemeral_popups,
132-
})
133-
end
128+
-- Merge all close events into a single autocmd for efficiency
129+
vim.api.nvim_create_autocmd(close_events, {
130+
group = group,
131+
callback = close_ephemeral_popups,
132+
})
134133

135134
vim.api.nvim_create_autocmd("User", {
136135
group = group,

lua/peekstack/core/layout.lua

Lines changed: 67 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,22 @@ function M.compute(index)
6767
}
6868
end
6969

70+
---Compute fullscreen layout for zoomed popup.
71+
---@param popup_count integer number of popups in the stack
72+
---@return PeekstackLayoutResult
73+
function M.compute_zoom(popup_count)
74+
local columns = vim.o.columns
75+
local lines = vim.o.lines - vim.o.cmdheight
76+
local base = config.get().ui.layout.zindex_base
77+
return {
78+
width = columns,
79+
height = lines,
80+
row = 0,
81+
col = 0,
82+
zindex = base + popup_count + 1,
83+
}
84+
end
85+
7086
---@param winid integer
7187
---@param is_focused boolean
7288
local function set_popup_winhighlight(winid, is_focused)
@@ -89,28 +105,52 @@ local function focused_popup_winid(stack)
89105
return nil
90106
end
91107

108+
---@param winid integer
109+
---@param is_zoomed boolean
110+
local function set_popup_zoom_winhighlight(winid, is_zoomed)
111+
if not vim.api.nvim_win_is_valid(winid) then
112+
return
113+
end
114+
if is_zoomed then
115+
vim.wo[winid].winhighlight = "FloatBorder:PeekstackPopupBorderZoomed"
116+
end
117+
end
118+
92119
---@param stack PeekstackStackModel
93120
function M.reflow(stack)
94121
local focused_winid = focused_popup_winid(stack)
95122
local base = config.get().ui.layout.zindex_base
96123
local top = base + #stack.popups
124+
local zoomed_id = stack.zoomed_id
97125
for idx, popup in ipairs(stack.popups) do
98126
if popup.winid and vim.api.nvim_win_is_valid(popup.winid) then
99127
local is_focused = focused_winid ~= nil and popup.winid == focused_winid
100-
local layout = M.compute(idx)
101-
local z = layout.zindex
102-
if is_focused then
128+
local is_zoomed = zoomed_id ~= nil and popup.id == zoomed_id
129+
130+
local lo
131+
if is_zoomed then
132+
lo = M.compute_zoom(#stack.popups)
133+
else
134+
lo = M.compute(idx)
135+
end
136+
137+
local z = lo.zindex
138+
if not is_zoomed and is_focused then
103139
z = top
104140
end
105141
local win_opts = vim.tbl_extend("force", popup.win_opts or {}, {
106-
row = layout.row,
107-
col = layout.col,
108-
width = layout.width,
109-
height = layout.height,
142+
row = lo.row,
143+
col = lo.col,
144+
width = lo.width,
145+
height = lo.height,
110146
zindex = z,
111147
})
112148
vim.api.nvim_win_set_config(popup.winid, win_opts)
113-
set_popup_winhighlight(popup.winid, is_focused)
149+
if is_zoomed then
150+
set_popup_zoom_winhighlight(popup.winid, true)
151+
else
152+
set_popup_winhighlight(popup.winid, is_focused)
153+
end
114154
end
115155
end
116156
end
@@ -126,12 +166,24 @@ function M.update_focus_zindex(stack, focused_winid)
126166
local ui = config.get().ui
127167
local base = ui.layout.zindex_base
128168
local top = base + #stack.popups
169+
local zoomed_id = stack.zoomed_id
129170

130171
for idx, popup in ipairs(stack.popups) do
131172
if popup.winid and vim.api.nvim_win_is_valid(popup.winid) then
132173
local is_focused = popup.winid == focused_winid
133-
local z = is_focused and top or (base + idx - 1)
134-
local lo = M.compute(idx)
174+
local is_zoomed = zoomed_id ~= nil and popup.id == zoomed_id
175+
176+
local lo
177+
if is_zoomed then
178+
lo = M.compute_zoom(#stack.popups)
179+
else
180+
lo = M.compute(idx)
181+
end
182+
183+
local z = lo.zindex
184+
if not is_zoomed and is_focused then
185+
z = top
186+
end
135187
local win_opts = vim.tbl_extend("force", popup.win_opts or {}, {
136188
row = lo.row,
137189
col = lo.col,
@@ -140,7 +192,11 @@ function M.update_focus_zindex(stack, focused_winid)
140192
zindex = z,
141193
})
142194
pcall(vim.api.nvim_win_set_config, popup.winid, win_opts)
143-
set_popup_winhighlight(popup.winid, is_focused)
195+
if is_zoomed then
196+
set_popup_zoom_winhighlight(popup.winid, true)
197+
else
198+
set_popup_winhighlight(popup.winid, is_focused)
199+
end
144200
end
145201
end
146202
end

lua/peekstack/core/stack.lua

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,10 @@ function M.push(location, opts)
283283
if stack.hidden then
284284
M.toggle(stack.root_winid)
285285
end
286+
-- Clear zoom before pushing so the new top popup gets normal layout
287+
if stack.zoomed_id then
288+
stack.zoomed_id = nil
289+
end
286290
local model = popup.create(location, create_opts)
287291
if not model then
288292
return nil
@@ -325,6 +329,10 @@ end
325329
local function close_stack_item(stack, idx, item)
326330
local current_win = vim.api.nvim_get_current_win()
327331
local should_restore_focus = item.winid == current_win and vim.w[current_win].peekstack_popup_id ~= nil
332+
-- Clear zoom if the zoomed popup is being closed
333+
if stack.zoomed_id == item.id then
334+
stack.zoomed_id = nil
335+
end
328336
-- Remove from popups BEFORE closing the window to prevent
329337
-- WinClosed autocmd from re-entering and processing the same popup.
330338
table.remove(stack.popups, idx)
@@ -650,6 +658,9 @@ function M.handle_win_closed(winid)
650658
if stack.focused_id == item.id then
651659
focused_removed = true
652660
end
661+
if stack.zoomed_id == item.id then
662+
stack.zoomed_id = nil
663+
end
653664
emit_popup_event("PeekstackClose", item, root_winid)
654665
feedback.highlight_origin(item.origin)
655666
table.remove(stack.popups, idx)
@@ -719,6 +730,9 @@ function M.handle_buf_wipeout(bufnr)
719730
for idx = #stack.popups, 1, -1 do
720731
local item = stack.popups[idx]
721732
if item.bufnr == bufnr then
733+
if stack.zoomed_id == item.id then
734+
stack.zoomed_id = nil
735+
end
722736
unindex_popup(item)
723737
table.remove(stack.popups, idx)
724738
end
@@ -795,6 +809,9 @@ function M.handle_origin_wipeout(bufnr)
795809
for idx = #stack.popups, 1, -1 do
796810
local item = stack.popups[idx]
797811
if should_close_for_origin(item) then
812+
if stack.zoomed_id == item.id then
813+
stack.zoomed_id = nil
814+
end
798815
popup.close(item)
799816
unindex_popup(item)
800817
table.remove(stack.popups, idx)
@@ -855,6 +872,8 @@ function M.toggle(winid)
855872
end
856873

857874
if not stack.hidden then
875+
-- Clear zoom state before hiding
876+
stack.zoomed_id = nil
858877
-- Move focus back to root window before hiding
859878
if vim.api.nvim_win_is_valid(stack.root_winid) then
860879
vim.api.nvim_set_current_win(stack.root_winid)
@@ -897,11 +916,41 @@ function M.is_hidden(winid)
897916
return stack.hidden == true
898917
end
899918

919+
---Toggle zoom on the top popup. When zoomed, the popup fills the
920+
---entire editor. Calling again restores the normal layout.
921+
---@param winid? integer
922+
---@return boolean
923+
function M.toggle_zoom(winid)
924+
deps()
925+
local stack = ensure_stack(winid)
926+
if #stack.popups == 0 or stack.hidden then
927+
return false
928+
end
929+
930+
local top = stack.popups[#stack.popups]
931+
if stack.zoomed_id == top.id then
932+
stack.zoomed_id = nil
933+
else
934+
stack.zoomed_id = top.id
935+
end
936+
layout.reflow(stack)
937+
return true
938+
end
939+
940+
---Check whether the current stack has a zoomed popup.
941+
---@param winid? integer
942+
---@return boolean
943+
function M.is_zoomed(winid)
944+
local stack = ensure_stack(winid)
945+
return stack.zoomed_id ~= nil
946+
end
947+
900948
--- Close all popups in the current (or given) window's stack.
901949
---@param winid? integer
902950
function M.close_all(winid)
903951
deps()
904952
local stack = ensure_stack(winid)
953+
stack.zoomed_id = nil
905954
-- When hidden, windows are already closed; just clear hidden state
906955
-- and record history for each popup.
907956
if stack.hidden then

lua/peekstack/init.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ local function set_hl()
3232
vim.api.nvim_set_hl(0, "PeekstackStackViewLine", { default = true, link = "LineNr" })
3333
vim.api.nvim_set_hl(0, "PeekstackPopupBorder", { default = true, link = "FloatBorder" })
3434
vim.api.nvim_set_hl(0, "PeekstackPopupBorderFocused", { default = true, link = "Function" })
35+
vim.api.nvim_set_hl(0, "PeekstackPopupBorderZoomed", { default = true, link = "WarningMsg" })
3536
vim.api.nvim_set_hl(0, "PeekstackTitleKindError", { default = true, link = "DiagnosticError" })
3637
vim.api.nvim_set_hl(0, "PeekstackTitleKindWarn", { default = true, link = "DiagnosticWarn" })
3738
vim.api.nvim_set_hl(0, "PeekstackTitleKindInfo", { default = true, link = "DiagnosticInfo" })

lua/peekstack/types.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
---@field layout_state any
6464
---@field focused_id integer?
6565
---@field hidden boolean?
66+
---@field zoomed_id integer?
6667

6768
---@class PeekstackUserEventData
6869
---@field event string
@@ -250,6 +251,7 @@
250251
---@field promote_vsplit string
251252
---@field promote_tab string
252253
---@field toggle_stack_view string
254+
---@field zoom string
253255

254256
---@class PeekstackConfigUI
255257
---@field layout PeekstackConfigLayout

0 commit comments

Comments
 (0)