@@ -751,6 +751,88 @@ def _handle_finished_input_update(self, had_input):
751751 if self .finished and had_input and self .display is not None :
752752 self ._update_display ()
753753
754+ def _get_visible_rows_info (self ):
755+ """Calculate visible rows and stats list for opcode navigation."""
756+ stats_list = self .build_stats_list ()
757+ if self .display :
758+ height , _ = self .display .get_dimensions ()
759+ extra_header = FINISHED_BANNER_EXTRA_LINES if self .finished else 0
760+ max_stats = max (0 , height - HEADER_LINES - extra_header - FOOTER_LINES - SAFETY_MARGIN )
761+ stats_list = stats_list [:max_stats ]
762+ visible_rows = max (1 , height - 8 - 2 - 12 )
763+ else :
764+ visible_rows = self .limit
765+ total_rows = len (stats_list )
766+ return stats_list , visible_rows , total_rows
767+
768+ def _move_selection_down (self ):
769+ """Move selection down in opcode mode with scrolling."""
770+ if not self .show_opcodes :
771+ return
772+
773+ stats_list , visible_rows , total_rows = self ._get_visible_rows_info ()
774+ if total_rows == 0 :
775+ return
776+
777+ # Max scroll is when last item is at bottom
778+ max_scroll = max (0 , total_rows - visible_rows )
779+ # Current absolute position
780+ abs_pos = self .scroll_offset + self .selected_row
781+
782+ # Only move if not at the last item
783+ if abs_pos < total_rows - 1 :
784+ # Try to move selection within visible area first
785+ if self .selected_row < visible_rows - 1 :
786+ self .selected_row += 1
787+ elif self .scroll_offset < max_scroll :
788+ # Scroll down
789+ self .scroll_offset += 1
790+
791+ # Clamp to valid range
792+ self .scroll_offset = min (self .scroll_offset , max_scroll )
793+ max_selected = min (visible_rows - 1 , total_rows - self .scroll_offset - 1 )
794+ self .selected_row = min (self .selected_row , max (0 , max_selected ))
795+
796+ def _move_selection_up (self ):
797+ """Move selection up in opcode mode with scrolling."""
798+ if not self .show_opcodes :
799+ return
800+
801+ if self .selected_row > 0 :
802+ self .selected_row -= 1
803+ elif self .scroll_offset > 0 :
804+ self .scroll_offset -= 1
805+
806+ # Clamp to valid range based on actual stats_list
807+ stats_list , visible_rows , total_rows = self ._get_visible_rows_info ()
808+ if total_rows > 0 :
809+ max_scroll = max (0 , total_rows - visible_rows )
810+ self .scroll_offset = min (self .scroll_offset , max_scroll )
811+ max_selected = min (visible_rows - 1 , total_rows - self .scroll_offset - 1 )
812+ self .selected_row = min (self .selected_row , max (0 , max_selected ))
813+
814+ def _navigate_to_previous_thread (self ):
815+ """Navigate to previous thread in PER_THREAD mode, or switch from ALL to PER_THREAD."""
816+ if len (self .thread_ids ) > 0 :
817+ if self .view_mode == "ALL" :
818+ self .view_mode = "PER_THREAD"
819+ self .current_thread_index = len (self .thread_ids ) - 1
820+ else :
821+ self .current_thread_index = (
822+ self .current_thread_index - 1
823+ ) % len (self .thread_ids )
824+
825+ def _navigate_to_next_thread (self ):
826+ """Navigate to next thread in PER_THREAD mode, or switch from ALL to PER_THREAD."""
827+ if len (self .thread_ids ) > 0 :
828+ if self .view_mode == "ALL" :
829+ self .view_mode = "PER_THREAD"
830+ self .current_thread_index = 0
831+ else :
832+ self .current_thread_index = (
833+ self .current_thread_index + 1
834+ ) % len (self .thread_ids )
835+
754836 def _show_terminal_too_small (self , height , width ):
755837 """Display a message when terminal is too small."""
756838 A_BOLD = self .display .get_attr ("A_BOLD" )
@@ -930,154 +1012,35 @@ def _handle_input(self):
9301012
9311013 elif ch == ord ("j" ) or ch == ord ("J" ):
9321014 # Move selection down in opcode mode (with scrolling)
933- if self .show_opcodes :
934- # Use the actual displayed stats_list count, not raw result_source
935- # This matches what _prepare_display_data() produces
936- stats_list = self .build_stats_list ()
937- if self .display :
938- height , _ = self .display .get_dimensions ()
939- # Same calculation as _prepare_display_data
940- extra_header = FINISHED_BANNER_EXTRA_LINES if self .finished else 0
941- max_stats = max (0 , height - HEADER_LINES - extra_header - FOOTER_LINES - SAFETY_MARGIN )
942- stats_list = stats_list [:max_stats ]
943- visible_rows = max (1 , height - 8 - 2 - 12 )
944- else :
945- visible_rows = self .limit
946- total_rows = len (stats_list )
947- if total_rows == 0 :
948- return
949- # Max scroll is when last item is at bottom
950- max_scroll = max (0 , total_rows - visible_rows )
951- # Current absolute position
952- abs_pos = self .scroll_offset + self .selected_row
953- # Only move if not at the last item
954- if abs_pos < total_rows - 1 :
955- # Try to move selection within visible area first
956- if self .selected_row < visible_rows - 1 :
957- self .selected_row += 1
958- elif self .scroll_offset < max_scroll :
959- # Scroll down
960- self .scroll_offset += 1
961- # Clamp to valid range
962- self .scroll_offset = min (self .scroll_offset , max_scroll )
963- max_selected = min (visible_rows - 1 , total_rows - self .scroll_offset - 1 )
964- self .selected_row = min (self .selected_row , max (0 , max_selected ))
1015+ self ._move_selection_down ()
9651016
9661017 elif ch == ord ("k" ) or ch == ord ("K" ):
9671018 # Move selection up in opcode mode (with scrolling)
968- if self .show_opcodes :
969- if self .selected_row > 0 :
970- self .selected_row -= 1
971- elif self .scroll_offset > 0 :
972- self .scroll_offset -= 1
973- # Clamp to valid range based on actual stats_list
974- stats_list = self .build_stats_list ()
975- if self .display :
976- height , _ = self .display .get_dimensions ()
977- extra_header = FINISHED_BANNER_EXTRA_LINES if self .finished else 0
978- max_stats = max (0 , height - HEADER_LINES - extra_header - FOOTER_LINES - SAFETY_MARGIN )
979- stats_list = stats_list [:max_stats ]
980- visible_rows = max (1 , height - 8 - 2 - 12 )
981- else :
982- visible_rows = self .limit
983- total_rows = len (stats_list )
984- if total_rows > 0 :
985- max_scroll = max (0 , total_rows - visible_rows )
986- self .scroll_offset = min (self .scroll_offset , max_scroll )
987- max_selected = min (visible_rows - 1 , total_rows - self .scroll_offset - 1 )
988- self .selected_row = min (self .selected_row , max (0 , max_selected ))
1019+ self ._move_selection_up ()
9891020
9901021 elif ch == curses .KEY_UP :
9911022 # Move selection up (same as 'k') when in opcode mode
9921023 if self .show_opcodes :
993- if self .selected_row > 0 :
994- self .selected_row -= 1
995- elif self .scroll_offset > 0 :
996- self .scroll_offset -= 1
997- # Clamp to valid range based on actual stats_list
998- stats_list = self .build_stats_list ()
999- if self .display :
1000- height , _ = self .display .get_dimensions ()
1001- extra_header = FINISHED_BANNER_EXTRA_LINES if self .finished else 0
1002- max_stats = max (0 , height - HEADER_LINES - extra_header - FOOTER_LINES - SAFETY_MARGIN )
1003- stats_list = stats_list [:max_stats ]
1004- visible_rows = max (1 , height - 8 - 2 - 12 )
1005- else :
1006- visible_rows = self .limit
1007- total_rows = len (stats_list )
1008- if total_rows > 0 :
1009- max_scroll = max (0 , total_rows - visible_rows )
1010- self .scroll_offset = min (self .scroll_offset , max_scroll )
1011- max_selected = min (visible_rows - 1 , total_rows - self .scroll_offset - 1 )
1012- self .selected_row = min (self .selected_row , max (0 , max_selected ))
1024+ self ._move_selection_up ()
10131025 else :
10141026 # Navigate to previous thread (same as KEY_LEFT)
1015- if len (self .thread_ids ) > 0 :
1016- if self .view_mode == "ALL" :
1017- self .view_mode = "PER_THREAD"
1018- self .current_thread_index = len (self .thread_ids ) - 1
1019- else :
1020- self .current_thread_index = (
1021- self .current_thread_index - 1
1022- ) % len (self .thread_ids )
1027+ self ._navigate_to_previous_thread ()
10231028
10241029 elif ch == curses .KEY_DOWN :
10251030 # Move selection down (same as 'j') when in opcode mode
10261031 if self .show_opcodes :
1027- stats_list = self .build_stats_list ()
1028- if self .display :
1029- height , _ = self .display .get_dimensions ()
1030- extra_header = FINISHED_BANNER_EXTRA_LINES if self .finished else 0
1031- max_stats = max (0 , height - HEADER_LINES - extra_header - FOOTER_LINES - SAFETY_MARGIN )
1032- stats_list = stats_list [:max_stats ]
1033- visible_rows = max (1 , height - 8 - 2 - 12 )
1034- else :
1035- visible_rows = self .limit
1036- total_rows = len (stats_list )
1037- if total_rows == 0 :
1038- return
1039- max_scroll = max (0 , total_rows - visible_rows )
1040- abs_pos = self .scroll_offset + self .selected_row
1041- if abs_pos < total_rows - 1 :
1042- if self .selected_row < visible_rows - 1 :
1043- self .selected_row += 1
1044- elif self .scroll_offset < max_scroll :
1045- self .scroll_offset += 1
1046- self .scroll_offset = min (self .scroll_offset , max_scroll )
1047- max_selected = min (visible_rows - 1 , total_rows - self .scroll_offset - 1 )
1048- self .selected_row = min (self .selected_row , max (0 , max_selected ))
1032+ self ._move_selection_down ()
10491033 else :
10501034 # Navigate to next thread (same as KEY_RIGHT)
1051- if len (self .thread_ids ) > 0 :
1052- if self .view_mode == "ALL" :
1053- self .view_mode = "PER_THREAD"
1054- self .current_thread_index = 0
1055- else :
1056- self .current_thread_index = (
1057- self .current_thread_index + 1
1058- ) % len (self .thread_ids )
1035+ self ._navigate_to_next_thread ()
10591036
10601037 elif ch == curses .KEY_LEFT :
10611038 # Navigate to previous thread
1062- if len (self .thread_ids ) > 0 :
1063- if self .view_mode == "ALL" :
1064- self .view_mode = "PER_THREAD"
1065- self .current_thread_index = len (self .thread_ids ) - 1
1066- else :
1067- self .current_thread_index = (
1068- self .current_thread_index - 1
1069- ) % len (self .thread_ids )
1039+ self ._navigate_to_previous_thread ()
10701040
10711041 elif ch == curses .KEY_RIGHT :
10721042 # Navigate to next thread
1073- if len (self .thread_ids ) > 0 :
1074- if self .view_mode == "ALL" :
1075- self .view_mode = "PER_THREAD"
1076- self .current_thread_index = 0
1077- else :
1078- self .current_thread_index = (
1079- self .current_thread_index + 1
1080- ) % len (self .thread_ids )
1043+ self ._navigate_to_next_thread ()
10811044
10821045 # Update display if input was processed while finished
10831046 self ._handle_finished_input_update (ch != - 1 )
0 commit comments