Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ Built-in provider names:
- `:PeekstackRestoreAllPopups` — restore all closed popups
- `:PeekstackCloseAll` — close all popups in the current stack
- `:PeekstackToggle` — temporarily hide/show all popups in the current stack
- `:PeekstackZoom` — toggle zoom (maximize the top popup to fill the editor)
- `:PeekstackHistory` — show popup history and select to restore
- `:PeekstackQuickPeek [provider]` — quick peek without stacking (default: `lsp.definition`, accepts any registered provider)

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

Defaults in stack view:

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

Expand Down Expand Up @@ -228,6 +231,7 @@ Configure via `require("peekstack").setup({ ... })`.
promote_vsplit = "<C-v>",
promote_tab = "<C-t>",
toggle_stack_view = "<leader>os",
zoom = "<C-z>",
},
},
picker = {
Expand Down
11 changes: 11 additions & 0 deletions doc/peekstack.txt
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ With options:
promote_vsplit = "<C-v>",
promote_tab = "<C-t>",
toggle_stack_view = "<leader>os",
zoom = "<C-z>",
},
},
picker = {
Expand Down Expand Up @@ -297,6 +298,13 @@ Commands are registered after `setup()` is called.
again recreates the windows in their original layout. Pushing a new
popup while hidden automatically restores visibility.

:PeekstackZoom *:PeekstackZoom*
Toggle zoom on the top popup. When zoomed, the popup fills the entire
editor. Call again to restore the normal layout. The border highlight
changes to `PeekstackPopupBorderZoomed` while zoomed. Zoom is
automatically cleared when the zoomed popup is closed, a new popup
is pushed, or the stack is hidden via |:PeekstackToggle|.

:PeekstackHistory *:PeekstackHistory*
Show popup history and select an entry to restore.

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

Stack view keymaps:

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

Expand All @@ -534,6 +544,7 @@ Title highlights:
Popup border highlights:
PeekstackPopupBorder Unfocused popup border (links to FloatBorder)
PeekstackPopupBorderFocused Focused popup border (links to Function)
PeekstackPopupBorderZoomed Zoomed popup border (links to WarningMsg)

Stack view highlights:
PeekstackStackViewIndex Entry index number (links to LineNr)
Expand Down
8 changes: 8 additions & 0 deletions lua/peekstack/commands.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ local COMMAND_NAMES = {
"PeekstackCloseAll",
"PeekstackQuickPeek",
"PeekstackToggle",
"PeekstackZoom",
}

---@param session PeekstackSession|table
Expand Down Expand Up @@ -181,6 +182,13 @@ function M.setup()
end
end, {})

vim.api.nvim_create_user_command("PeekstackZoom", function()
local toggled = require("peekstack.core.stack").toggle_zoom()
if not toggled then
notify.info("No popups in the current stack")
end
end, {})

vim.api.nvim_create_user_command("PeekstackQuickPeek", function(opts)
local provider = opts.args and opts.args ~= "" and opts.args or "lsp.definition"
require("peekstack").peek(provider, { mode = "quick" })
Expand Down
1 change: 1 addition & 0 deletions lua/peekstack/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ M.defaults = {
promote_vsplit = "<C-v>",
promote_tab = "<C-t>",
toggle_stack_view = "<leader>os",
zoom = "<C-z>",
},
},
picker = {
Expand Down
11 changes: 5 additions & 6 deletions lua/peekstack/core/events.lua
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,11 @@ function M.setup()
local close_events = cfg.ui.quick_peek and cfg.ui.quick_peek.close_events
or { "CursorMoved", "InsertEnter", "BufLeave", "WinLeave" }

for _, event in ipairs(close_events) do
vim.api.nvim_create_autocmd(event, {
group = group,
callback = close_ephemeral_popups,
})
end
-- Merge all close events into a single autocmd for efficiency
vim.api.nvim_create_autocmd(close_events, {
group = group,
callback = close_ephemeral_popups,
})

vim.api.nvim_create_autocmd("User", {
group = group,
Expand Down
78 changes: 67 additions & 11 deletions lua/peekstack/core/layout.lua
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,22 @@ function M.compute(index)
}
end

---Compute fullscreen layout for zoomed popup.
---@param popup_count integer number of popups in the stack
---@return PeekstackLayoutResult
function M.compute_zoom(popup_count)
local columns = vim.o.columns
local lines = vim.o.lines - vim.o.cmdheight
local base = config.get().ui.layout.zindex_base
return {
width = columns,
height = lines,
row = 0,
col = 0,
zindex = base + popup_count + 1,
}
end

---@param winid integer
---@param is_focused boolean
local function set_popup_winhighlight(winid, is_focused)
Expand All @@ -89,28 +105,52 @@ local function focused_popup_winid(stack)
return nil
end

---@param winid integer
---@param is_zoomed boolean
local function set_popup_zoom_winhighlight(winid, is_zoomed)
if not vim.api.nvim_win_is_valid(winid) then
return
end
if is_zoomed then
vim.wo[winid].winhighlight = "FloatBorder:PeekstackPopupBorderZoomed"
end
end

---@param stack PeekstackStackModel
function M.reflow(stack)
local focused_winid = focused_popup_winid(stack)
local base = config.get().ui.layout.zindex_base
local top = base + #stack.popups
local zoomed_id = stack.zoomed_id
for idx, popup in ipairs(stack.popups) do
if popup.winid and vim.api.nvim_win_is_valid(popup.winid) then
local is_focused = focused_winid ~= nil and popup.winid == focused_winid
local layout = M.compute(idx)
local z = layout.zindex
if is_focused then
local is_zoomed = zoomed_id ~= nil and popup.id == zoomed_id

local lo
if is_zoomed then
lo = M.compute_zoom(#stack.popups)
else
lo = M.compute(idx)
end

local z = lo.zindex
if not is_zoomed and is_focused then
z = top
end
local win_opts = vim.tbl_extend("force", popup.win_opts or {}, {
row = layout.row,
col = layout.col,
width = layout.width,
height = layout.height,
row = lo.row,
col = lo.col,
width = lo.width,
height = lo.height,
zindex = z,
})
vim.api.nvim_win_set_config(popup.winid, win_opts)
set_popup_winhighlight(popup.winid, is_focused)
if is_zoomed then
set_popup_zoom_winhighlight(popup.winid, true)
else
set_popup_winhighlight(popup.winid, is_focused)
end
end
end
end
Expand All @@ -126,12 +166,24 @@ function M.update_focus_zindex(stack, focused_winid)
local ui = config.get().ui
local base = ui.layout.zindex_base
local top = base + #stack.popups
local zoomed_id = stack.zoomed_id

for idx, popup in ipairs(stack.popups) do
if popup.winid and vim.api.nvim_win_is_valid(popup.winid) then
local is_focused = popup.winid == focused_winid
local z = is_focused and top or (base + idx - 1)
local lo = M.compute(idx)
local is_zoomed = zoomed_id ~= nil and popup.id == zoomed_id

local lo
if is_zoomed then
lo = M.compute_zoom(#stack.popups)
else
lo = M.compute(idx)
end

local z = lo.zindex
if not is_zoomed and is_focused then
z = top
end
local win_opts = vim.tbl_extend("force", popup.win_opts or {}, {
row = lo.row,
col = lo.col,
Expand All @@ -140,7 +192,11 @@ function M.update_focus_zindex(stack, focused_winid)
zindex = z,
})
pcall(vim.api.nvim_win_set_config, popup.winid, win_opts)
set_popup_winhighlight(popup.winid, is_focused)
if is_zoomed then
set_popup_zoom_winhighlight(popup.winid, true)
else
set_popup_winhighlight(popup.winid, is_focused)
end
end
end
end
Expand Down
49 changes: 49 additions & 0 deletions lua/peekstack/core/stack.lua
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,10 @@ function M.push(location, opts)
if stack.hidden then
M.toggle(stack.root_winid)
end
-- Clear zoom before pushing so the new top popup gets normal layout
if stack.zoomed_id then
stack.zoomed_id = nil
end
local model = popup.create(location, create_opts)
if not model then
return nil
Expand Down Expand Up @@ -325,6 +329,10 @@ end
local function close_stack_item(stack, idx, item)
local current_win = vim.api.nvim_get_current_win()
local should_restore_focus = item.winid == current_win and vim.w[current_win].peekstack_popup_id ~= nil
-- Clear zoom if the zoomed popup is being closed
if stack.zoomed_id == item.id then
stack.zoomed_id = nil
end
-- Remove from popups BEFORE closing the window to prevent
-- WinClosed autocmd from re-entering and processing the same popup.
table.remove(stack.popups, idx)
Expand Down Expand Up @@ -650,6 +658,9 @@ function M.handle_win_closed(winid)
if stack.focused_id == item.id then
focused_removed = true
end
if stack.zoomed_id == item.id then
stack.zoomed_id = nil
end
emit_popup_event("PeekstackClose", item, root_winid)
feedback.highlight_origin(item.origin)
table.remove(stack.popups, idx)
Expand Down Expand Up @@ -719,6 +730,9 @@ function M.handle_buf_wipeout(bufnr)
for idx = #stack.popups, 1, -1 do
local item = stack.popups[idx]
if item.bufnr == bufnr then
if stack.zoomed_id == item.id then
stack.zoomed_id = nil
end
unindex_popup(item)
table.remove(stack.popups, idx)
end
Expand Down Expand Up @@ -795,6 +809,9 @@ function M.handle_origin_wipeout(bufnr)
for idx = #stack.popups, 1, -1 do
local item = stack.popups[idx]
if should_close_for_origin(item) then
if stack.zoomed_id == item.id then
stack.zoomed_id = nil
end
popup.close(item)
unindex_popup(item)
table.remove(stack.popups, idx)
Expand Down Expand Up @@ -855,6 +872,8 @@ function M.toggle(winid)
end

if not stack.hidden then
-- Clear zoom state before hiding
stack.zoomed_id = nil
-- Move focus back to root window before hiding
if vim.api.nvim_win_is_valid(stack.root_winid) then
vim.api.nvim_set_current_win(stack.root_winid)
Expand Down Expand Up @@ -897,11 +916,41 @@ function M.is_hidden(winid)
return stack.hidden == true
end

---Toggle zoom on the top popup. When zoomed, the popup fills the
---entire editor. Calling again restores the normal layout.
---@param winid? integer
---@return boolean
function M.toggle_zoom(winid)
deps()
local stack = ensure_stack(winid)
if #stack.popups == 0 or stack.hidden then
return false
end

local top = stack.popups[#stack.popups]
if stack.zoomed_id == top.id then
stack.zoomed_id = nil
else
stack.zoomed_id = top.id
end
layout.reflow(stack)
return true
end

---Check whether the current stack has a zoomed popup.
---@param winid? integer
---@return boolean
function M.is_zoomed(winid)
local stack = ensure_stack(winid)
return stack.zoomed_id ~= nil
end

--- Close all popups in the current (or given) window's stack.
---@param winid? integer
function M.close_all(winid)
deps()
local stack = ensure_stack(winid)
stack.zoomed_id = nil
-- When hidden, windows are already closed; just clear hidden state
-- and record history for each popup.
if stack.hidden then
Expand Down
1 change: 1 addition & 0 deletions lua/peekstack/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ local function set_hl()
vim.api.nvim_set_hl(0, "PeekstackStackViewLine", { default = true, link = "LineNr" })
vim.api.nvim_set_hl(0, "PeekstackPopupBorder", { default = true, link = "FloatBorder" })
vim.api.nvim_set_hl(0, "PeekstackPopupBorderFocused", { default = true, link = "Function" })
vim.api.nvim_set_hl(0, "PeekstackPopupBorderZoomed", { default = true, link = "WarningMsg" })
vim.api.nvim_set_hl(0, "PeekstackTitleKindError", { default = true, link = "DiagnosticError" })
vim.api.nvim_set_hl(0, "PeekstackTitleKindWarn", { default = true, link = "DiagnosticWarn" })
vim.api.nvim_set_hl(0, "PeekstackTitleKindInfo", { default = true, link = "DiagnosticInfo" })
Expand Down
2 changes: 2 additions & 0 deletions lua/peekstack/types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
---@field layout_state any
---@field focused_id integer?
---@field hidden boolean?
---@field zoomed_id integer?

---@class PeekstackUserEventData
---@field event string
Expand Down Expand Up @@ -250,6 +251,7 @@
---@field promote_vsplit string
---@field promote_tab string
---@field toggle_stack_view string
---@field zoom string

---@class PeekstackConfigUI
---@field layout PeekstackConfigLayout
Expand Down
Loading