@@ -28,6 +28,8 @@ local stacks = {}
2828local ephemerals = {}
2929--- @type table<integer , boolean>
3030local stack_view_wins = {}
31+ --- Guard flag: when true, WinClosed/BufWipeout handlers skip popup removal.
32+ local suppress_win_events = false
3133--- @class PeekstackPopupLookupEntry
3234--- @field popup PeekstackPopupModel
3335--- @field root_winid integer ?
@@ -277,6 +279,10 @@ function M.push(location, opts)
277279 end
278280
279281 local stack = ensure_stack ()
282+ -- Auto-show hidden stack before pushing a new popup
283+ if stack .hidden then
284+ M .toggle_visibility (stack .root_winid )
285+ end
280286 local model = popup .create (location , create_opts )
281287 if not model then
282288 return nil
@@ -531,6 +537,30 @@ function M.focus_by_id(id, winid)
531537 return false
532538end
533539
540+ --- Re-create a popup window for an existing stack item.
541+ --- @param item PeekstackPopupModel
542+ --- @param stack PeekstackStackModel
543+ --- @return PeekstackPopupModel ?
544+ local function reopen_popup (item , stack )
545+ local reopen_opts = {
546+ buffer_mode = item .buffer_mode or " copy" ,
547+ origin_winid = stack .root_winid ,
548+ parent_popup_id = item .parent_popup_id ,
549+ }
550+ if not item .title_chunks then
551+ reopen_opts .title = item .title
552+ end
553+ local model = popup .create (item .location , reopen_opts )
554+ if not model then
555+ return nil
556+ end
557+ model .id = item .id
558+ model .pinned = item .pinned or false
559+ vim .b [model .bufnr ].peekstack_popup_id = model .id
560+ vim .w [model .winid ].peekstack_popup_id = model .id
561+ return model
562+ end
563+
534564--- Re-open a popup by id when its window is gone.
535565--- @param id integer
536566--- @param winid ? integer
@@ -540,22 +570,10 @@ function M.reopen_by_id(id, winid)
540570 local stack = ensure_stack (winid )
541571 for idx , item in ipairs (stack .popups ) do
542572 if item .id == id then
543- local reopen_opts = {
544- buffer_mode = item .buffer_mode or " copy" ,
545- origin_winid = stack .root_winid ,
546- parent_popup_id = item .parent_popup_id ,
547- }
548- if not item .title_chunks then
549- reopen_opts .title = item .title
550- end
551- local model = popup .create (item .location , reopen_opts )
573+ local model = reopen_popup (item , stack )
552574 if not model then
553575 return nil
554576 end
555- model .id = item .id
556- model .pinned = item .pinned or false
557- vim .b [model .bufnr ].peekstack_popup_id = model .id
558- vim .w [model .winid ].peekstack_popup_id = model .id
559577 unindex_popup (item )
560578 stack .popups [idx ] = model
561579 index_popup (model , stack .root_winid )
601619--- @param winid integer
602620function M .handle_win_closed (winid )
603621 deps ()
622+ if suppress_win_events then
623+ return
624+ end
604625 if stack_view_wins [winid ] then
605626 stack_view_wins [winid ] = nil
606627 return
688709--- @param bufnr integer
689710function M .handle_buf_wipeout (bufnr )
690711 deps ()
712+ if suppress_win_events then
713+ return
714+ end
691715 for id , item in pairs (ephemerals ) do
692716 if item .bufnr == bufnr then
693717 unregister_ephemeral (id )
@@ -820,11 +844,80 @@ function M.reflow_all()
820844 end
821845end
822846
847+ --- Toggle visibility of all popups in the current stack.
848+ --- When hidden, popup windows are closed but models remain in the stack.
849+ --- When shown, popup windows are recreated from the stored models.
850+ --- @param winid ? integer
851+ --- @return boolean
852+ function M .toggle_visibility (winid )
853+ deps ()
854+ local stack = ensure_stack (winid )
855+ if # stack .popups == 0 then
856+ return false
857+ end
858+
859+ if not stack .hidden then
860+ -- Move focus back to root window before hiding
861+ if vim .api .nvim_win_is_valid (stack .root_winid ) then
862+ vim .api .nvim_set_current_win (stack .root_winid )
863+ end
864+ -- Close all popup windows but keep models in the stack.
865+ -- Suppress WinClosed/BufWipeout handlers to prevent them from
866+ -- removing popups that we intend to keep in the stack.
867+ suppress_win_events = true
868+ for _ , item in ipairs (stack .popups ) do
869+ popup .close (item )
870+ unindex_popup (item )
871+ item .winid = nil
872+ end
873+ suppress_win_events = false
874+ stack .hidden = true
875+ else
876+ -- Recreate all popup windows
877+ for idx , item in ipairs (stack .popups ) do
878+ local model = reopen_popup (item , stack )
879+ if model then
880+ stack .popups [idx ] = model
881+ index_popup (model , stack .root_winid )
882+ end
883+ end
884+ layout .reflow (stack )
885+ stack .hidden = false
886+ -- Restore focus to the previously focused popup
887+ if stack .focused_id then
888+ M .focus_by_id (stack .focused_id , stack .root_winid )
889+ end
890+ end
891+ return true
892+ end
893+
894+ --- Check whether the current stack is hidden.
895+ --- @param winid ? integer
896+ --- @return boolean
897+ function M .is_hidden (winid )
898+ local stack = ensure_stack (winid )
899+ return stack .hidden == true
900+ end
901+
823902--- Close all popups in the current (or given) window's stack.
824903--- @param winid ? integer
825904function M .close_all (winid )
826905 deps ()
827906 local stack = ensure_stack (winid )
907+ -- When hidden, windows are already closed; just clear hidden state
908+ -- and record history for each popup.
909+ if stack .hidden then
910+ for idx = # stack .popups , 1 , - 1 do
911+ local item = stack .popups [idx ]
912+ emit_popup_event (" PeekstackClose" , item , stack .root_winid )
913+ history .push_entry (stack , history .build_entry (item , idx ))
914+ unindex_popup (item )
915+ table.remove (stack .popups , idx )
916+ end
917+ stack .hidden = false
918+ stack .focused_id = nil
919+ return
920+ end
828921 for idx = # stack .popups , 1 , - 1 do
829922 local item = stack .popups [idx ]
830923 feedback .highlight_origin (item .origin )
@@ -855,6 +948,7 @@ function M._reset()
855948 stack_view_wins = {}
856949 popup_by_id = {}
857950 popup_by_winid = {}
951+ suppress_win_events = false
858952end
859953
860954--- Get ephemeral popups (for testing).
0 commit comments