@@ -2472,6 +2472,14 @@ def move(self, delta: int) -> None:
24722472 if n :
24732473 self .day_index = max (0 , min (self .day_index + delta , n - 1 ))
24742474
2475+ def _page_step (self , stdscr : curses .window | None ) -> int :
2476+ # Half the visible pager height (the window minus chrome, mirroring
2477+ # Renderer.max_scroll) -- the PgDn/PgUp and Ctrl-D/Ctrl-U stride.
2478+ if stdscr is None :
2479+ return 10
2480+ height , _width = stdscr .getmaxyx ()
2481+ return max (1 , (height - 9 ) // 2 )
2482+
24752483 def jump (self , to_end : bool , stdscr : curses .window | None = None ) -> None :
24762484 if self .view == "browse" :
24772485 if self .browse_mode == "projects" :
@@ -2741,12 +2749,16 @@ def handle_key(self, stdscr: curses.window, key: int) -> bool:
27412749 if self .price_prompt :
27422750 return self .handle_price_prompt_key (key )
27432751 if self .help :
2744- # A pager like the price overlay: j/k/arrows and g/G scroll;
2752+ # A pager like the price overlay: j/k/arrows, page keys and g/G scroll;
27452753 # any other key closes it.
27462754 if key in (ord ("j" ), curses .KEY_DOWN ):
27472755 self .help_scroll += 1
27482756 elif key in (ord ("k" ), curses .KEY_UP ):
27492757 self .help_scroll = max (0 , self .help_scroll - 1 )
2758+ elif key in (curses .KEY_NPAGE , 4 ): # PgDn / Ctrl-D
2759+ self .help_scroll += self ._page_step (stdscr ) # clamped on draw
2760+ elif key in (curses .KEY_PPAGE , 21 ): # PgUp / Ctrl-U
2761+ self .help_scroll = max (0 , self .help_scroll - self ._page_step (stdscr ))
27502762 elif key == ord ("g" ):
27512763 self .help_scroll = 0
27522764 elif key == ord ("G" ):
@@ -2760,8 +2772,8 @@ def handle_key(self, stdscr: curses.window, key: int) -> bool:
27602772 if self .filter_active :
27612773 return self .handle_filter_key (key )
27622774 if self .prices_model is not None :
2763- return self ._handle_price_sessions_key (key )
2764- return self ._handle_price_models_key (key )
2775+ return self ._handle_price_sessions_key (key , stdscr )
2776+ return self ._handle_price_models_key (key , stdscr )
27652777 if self .trends :
27662778 current = self .trend_tabs [self .trend_tab % len (self .trend_tabs )]
27672779 if current == "Calendar" :
@@ -2890,7 +2902,7 @@ def handle_key(self, stdscr: curses.window, key: int) -> bool:
28902902 if key == ord ("B" ):
28912903 self .toggle_bookmarks_view ()
28922904 return True
2893- if key == ord ("f" ):
2905+ if key in ( ord ("f" ), ord ( "/" ) ):
28942906 if not self .can_filter_current_view ():
28952907 self .notify (
28962908 "nothing to filter here — open a sessions, projects, or models list" , "error"
@@ -2955,6 +2967,12 @@ def handle_key(self, stdscr: curses.window, key: int) -> bool:
29552967 if key in (ord ("k" ), curses .KEY_UP ):
29562968 self .move (- 1 )
29572969 return True
2970+ if key in (curses .KEY_NPAGE , 4 ): # PgDn / Ctrl-D
2971+ self .move (self ._page_step (stdscr ))
2972+ return True
2973+ if key in (curses .KEY_PPAGE , 21 ): # PgUp / Ctrl-U
2974+ self .move (- self ._page_step (stdscr ))
2975+ return True
29582976 return True
29592977
29602978 def prices_view_label (self , view : str | None = None ) -> str :
@@ -2971,11 +2989,11 @@ def cycle_prices_view(self) -> None:
29712989 self .prices_scroll = 0
29722990 self .notice = f"view: { self .prices_view_label ()} "
29732991
2974- def _handle_price_models_key (self , key : int ) -> bool :
2975- # The P overlay's model list: j/k/arrows move a cursor, g/G jump to ends ,
2976- # Enter drills into the selected model's sessions, s sorts by a column, p
2977- # cycles the layout (by vendor / by provider / flat), f filters, r refreshes,
2978- # e exports the table; any other key closes the overlay.
2992+ def _handle_price_models_key (self , key : int , stdscr : curses . window | None = None ) -> bool :
2993+ # The P overlay's model list: j/k/arrows move a cursor, page keys stride ,
2994+ # g/G jump to ends, Enter drills into the selected model's sessions, s sorts
2995+ # by a column, p cycles the layout (by vendor / by provider / flat), f
2996+ # filters, r refreshes, e exports the table; any other key closes the overlay.
29792997 n = len (self .priced_model_names ())
29802998 if key in (ord ("s" ), ord ("S" )):
29812999 self .open_sort_menu ()
@@ -2987,6 +3005,10 @@ def _handle_price_models_key(self, key: int) -> bool:
29873005 self .prices_index = min (self .prices_index + 1 , max (0 , n - 1 ))
29883006 elif key in (ord ("k" ), curses .KEY_UP ):
29893007 self .prices_index = max (0 , self .prices_index - 1 )
3008+ elif key in (curses .KEY_NPAGE , 4 ): # PgDn / Ctrl-D
3009+ self .prices_index = min (self .prices_index + self ._page_step (stdscr ), max (0 , n - 1 ))
3010+ elif key in (curses .KEY_PPAGE , 21 ): # PgUp / Ctrl-U
3011+ self .prices_index = max (0 , self .prices_index - self ._page_step (stdscr ))
29903012 elif key == ord ("g" ):
29913013 self .prices_index = 0
29923014 elif key == ord ("G" ):
@@ -2998,7 +3020,7 @@ def _handle_price_models_key(self, key: int) -> bool:
29983020 self .prices_scroll = 0
29993021 elif key in (ord ("r" ), ord ("R" )):
30003022 self .refresh_prices_action () # keeps the overlay open
3001- elif key == ord ("f" ):
3023+ elif key in ( ord ("f" ), ord ( "/" ) ):
30023024 self .filter_active = True
30033025 self ._filter_before = self .query
30043026 self .prices_scroll = 0
@@ -3008,13 +3030,18 @@ def _handle_price_models_key(self, key: int) -> bool:
30083030 self .show_prices = False
30093031 return True
30103032
3011- def _handle_price_sessions_key (self , key : int ) -> bool :
3012- # The P overlay's per-model drill-in: j/k/arrows and g/G scroll the session
3013- # list; Esc/left/backspace backs out to the model list; any other key closes.
3033+ def _handle_price_sessions_key (self , key : int , stdscr : curses .window | None = None ) -> bool :
3034+ # The P overlay's per-model drill-in: j/k/arrows, page keys and g/G scroll the
3035+ # session list; Esc/left/backspace backs out to the model list; any other key
3036+ # closes.
30143037 if key in (ord ("j" ), curses .KEY_DOWN ):
30153038 self .prices_scroll += 1
30163039 elif key in (ord ("k" ), curses .KEY_UP ):
30173040 self .prices_scroll = max (0 , self .prices_scroll - 1 )
3041+ elif key in (curses .KEY_NPAGE , 4 ): # PgDn / Ctrl-D
3042+ self .prices_scroll += self ._page_step (stdscr ) # clamped on draw
3043+ elif key in (curses .KEY_PPAGE , 21 ): # PgUp / Ctrl-U
3044+ self .prices_scroll = max (0 , self .prices_scroll - self ._page_step (stdscr ))
30183045 elif key == ord ("g" ):
30193046 self .prices_scroll = 0
30203047 elif key == ord ("G" ):
@@ -3110,7 +3137,11 @@ def handle_mouse(self) -> bool:
31103137 self .launch_menu = None # click cancels the launch picker
31113138 return True
31123139 if self .help :
3113- if up or down or click or double :
3140+ if up :
3141+ self .help_scroll = max (0 , self .help_scroll - 3 )
3142+ elif down :
3143+ self .help_scroll += 3 # clamped to the last page on draw
3144+ elif click or double :
31143145 self .help = False
31153146 return True
31163147 if self .show_prices :
0 commit comments