@@ -598,10 +598,14 @@ local function render(s)
598598 end
599599 apply_preview_treesitter_highlights (s .bufnr , preview_lines )
600600
601- if s .winid and vim .api .nvim_win_is_valid (s .winid ) and # visible > 0 then
602- local cursor = vim .api .nvim_win_get_cursor (s .winid )[1 ]
603- if cursor <= s .header_lines then
604- vim .api .nvim_win_set_cursor (s .winid , { s .header_lines + 1 , 0 })
601+ if s .winid and vim .api .nvim_win_is_valid (s .winid ) and s .bufnr and vim .api .nvim_buf_is_valid (s .bufnr ) then
602+ local line_count = vim .api .nvim_buf_line_count (s .bufnr )
603+ if line_count > s .header_lines then
604+ local min_line = s .header_lines + 1
605+ local cursor = vim .api .nvim_win_get_cursor (s .winid )[1 ]
606+ if cursor < min_line then
607+ vim .api .nvim_win_set_cursor (s .winid , { min_line , 0 })
608+ end
605609 end
606610 end
607611end
@@ -623,6 +627,88 @@ local function move_cursor_to_id(s, id)
623627 end
624628end
625629
630+ --- @param s table
631+ --- @return integer[]
632+ local function entry_lines (s )
633+ local id_to_line = {}
634+ for line , id in pairs (s .line_to_id or {}) do
635+ if line > (s .header_lines or 0 ) and (not id_to_line [id ] or line < id_to_line [id ]) then
636+ id_to_line [id ] = line
637+ end
638+ end
639+ local lines = {}
640+ for _ , line in pairs (id_to_line ) do
641+ table.insert (lines , line )
642+ end
643+ table.sort (lines )
644+ return lines
645+ end
646+
647+ --- @param s table
648+ local function ensure_non_header_cursor (s )
649+ if not (s .winid and vim .api .nvim_win_is_valid (s .winid ) and s .bufnr and vim .api .nvim_buf_is_valid (s .bufnr )) then
650+ return
651+ end
652+ local line_count = vim .api .nvim_buf_line_count (s .bufnr )
653+ if line_count <= 0 then
654+ return
655+ end
656+ local min_line = math.min ((s .header_lines or 0 ) + 1 , line_count )
657+ local cursor = vim .api .nvim_win_get_cursor (s .winid )[1 ]
658+ if cursor < min_line then
659+ vim .api .nvim_win_set_cursor (s .winid , { min_line , 0 })
660+ end
661+ end
662+
663+ --- @param s table
664+ --- @param step integer
665+ local function move_cursor_by_stack_item (s , step )
666+ if not (s .winid and vim .api .nvim_win_is_valid (s .winid )) then
667+ return
668+ end
669+ local lines = entry_lines (s )
670+ if # lines == 0 then
671+ ensure_non_header_cursor (s )
672+ return
673+ end
674+
675+ local cursor_line = vim .api .nvim_win_get_cursor (s .winid )[1 ]
676+ if cursor_line <= (s .header_lines or 0 ) then
677+ vim .api .nvim_win_set_cursor (s .winid , { lines [1 ], 0 })
678+ return
679+ end
680+
681+ local current_id = s .line_to_id [cursor_line ]
682+ local base_line = cursor_line
683+ if current_id then
684+ for line , id in pairs (s .line_to_id ) do
685+ if id == current_id and line < base_line then
686+ base_line = line
687+ end
688+ end
689+ end
690+
691+ local target_line = base_line
692+ if step > 0 then
693+ for _ , line in ipairs (lines ) do
694+ if line > base_line then
695+ target_line = line
696+ break
697+ end
698+ end
699+ else
700+ for idx = # lines , 1 , - 1 do
701+ local line = lines [idx ]
702+ if line < base_line then
703+ target_line = line
704+ break
705+ end
706+ end
707+ end
708+
709+ vim .api .nvim_win_set_cursor (s .winid , { target_line , 0 })
710+ end
711+
626712--- @param s table
627713local function toggle_help (s )
628714 if s .help_winid and vim .api .nvim_win_is_valid (s .help_winid ) then
@@ -641,6 +727,8 @@ local function toggle_help(s)
641727 " r Rename selected popup" ,
642728 " p Toggle pin (skip auto-close)" ,
643729 " / Filter list" ,
730+ " gg/G Jump to first/last stack item" ,
731+ " j/k Move cursor by stack item" ,
644732 " J/K Move item down/up" ,
645733 " q Close stack view" ,
646734 " ? Toggle this help" ,
@@ -798,6 +886,38 @@ local function apply_keymaps(s)
798886 end )
799887 end , { buffer = s .bufnr , nowait = true , silent = true })
800888
889+ vim .keymap .set (" n" , " gg" , function ()
890+ local lines = entry_lines (s )
891+ if # lines == 0 then
892+ ensure_non_header_cursor (s )
893+ return
894+ end
895+ vim .api .nvim_win_set_cursor (s .winid , { lines [1 ], 0 })
896+ end , { buffer = s .bufnr , nowait = true , silent = true })
897+
898+ vim .keymap .set (" n" , " G" , function ()
899+ local lines = entry_lines (s )
900+ if # lines == 0 then
901+ ensure_non_header_cursor (s )
902+ return
903+ end
904+ vim .api .nvim_win_set_cursor (s .winid , { lines [# lines ], 0 })
905+ end , { buffer = s .bufnr , nowait = true , silent = true })
906+
907+ vim .keymap .set (" n" , " j" , function ()
908+ local count = vim .v .count1
909+ for _ = 1 , count do
910+ move_cursor_by_stack_item (s , 1 )
911+ end
912+ end , { buffer = s .bufnr , nowait = true , silent = true })
913+
914+ vim .keymap .set (" n" , " k" , function ()
915+ local count = vim .v .count1
916+ for _ = 1 , count do
917+ move_cursor_by_stack_item (s , - 1 )
918+ end
919+ end , { buffer = s .bufnr , nowait = true , silent = true })
920+
801921 vim .keymap .set (" n" , " J" , function ()
802922 local line = vim .api .nvim_win_get_cursor (s .winid )[1 ]
803923 local id = s .line_to_id [line ]
@@ -994,6 +1114,13 @@ function M.open()
9941114 end )
9951115 end ,
9961116 })
1117+ vim .api .nvim_create_autocmd (" CursorMoved" , {
1118+ group = au_group ,
1119+ buffer = s .bufnr ,
1120+ callback = function ()
1121+ ensure_non_header_cursor (s )
1122+ end ,
1123+ })
9971124
9981125 apply_keymaps (s )
9991126 render (s )
0 commit comments