@@ -199,7 +199,12 @@ def __init__(
199
199
self ._status_color = 0
200
200
self ._screen : Window = curses .initscr ()
201
201
self ._screen .timeout (refresh )
202
+ self ._screen .keypad (True ) # Enable keypad mode for proper key handling
202
203
self ._one_line_input = FormHandlerText (screen = self ._screen , ui_config = self ._ui_config )
204
+ # When cursor navigation is enabled, this tracks which visible row to highlight
205
+ self ._highlight_line_offset : int | None = None
206
+ # When cursor navigation is enabled, this tracks the cursor position in menus
207
+ self ._menu_cursor_pos : int = 0
203
208
204
209
def clear (self ) -> None :
205
210
"""Clear the screen."""
@@ -464,7 +469,7 @@ def _display(
464
469
index_width = len (str (count ))
465
470
466
471
keypad = {str (x ) for x in range (10 )}
467
- other_valid_keys = ["+" , "-" , "_" , "KEY_F(5)" , "^[" , "\x1b " ]
472
+ other_valid_keys = ["+" , "-" , "_" , "KEY_F(5)" , "^[" , "\x1b " , "CURSOR_ENTER" ]
468
473
469
474
while True :
470
475
self ._screen .erase ()
@@ -479,10 +484,26 @@ def _display(
479
484
line_index = line_numbers [idx ]
480
485
line_index_str = str (line_index ).rjust (index_width )
481
486
prefix = f"{ line_index_str } \u2502 "
487
+ # Apply highlight decoration when enabled and this is the selected row
488
+ if (indent_heading and self ._ui_config .cursor_navigation and
489
+ self ._highlight_line_offset is not None and idx == self ._highlight_line_offset ):
490
+ # Rebuild the line with reverse-video decoration added
491
+ highlighted_parts = tuple (
492
+ CursesLinePart (
493
+ column = lp .column ,
494
+ string = lp .string ,
495
+ color = lp .color ,
496
+ decoration = lp .decoration | curses .A_REVERSE ,
497
+ )
498
+ for lp in line
499
+ )
500
+ line_to_draw = CursesLine (highlighted_parts )
501
+ else :
502
+ line_to_draw = line
482
503
self ._add_line (
483
504
window = self ._screen ,
484
505
lineno = idx + len (heading ),
485
- line = line ,
506
+ line = line_to_draw ,
486
507
prefix = prefix ,
487
508
)
488
509
@@ -504,6 +525,16 @@ def _display(
504
525
if await_input :
505
526
char = self ._screen .getch ()
506
527
key = "KEY_F(5)" if char == - 1 else curses .keyname (char ).decode ()
528
+ # Debug: log the raw char and converted key for troubleshooting
529
+ if self ._ui_config .cursor_navigation :
530
+ self ._logger .debug ("Raw char: %s, Converted key: '%s'" , char , key )
531
+ # Check for Enter key codes and return a special value for cursor navigation
532
+ if (self ._ui_config .cursor_navigation and
533
+ char in [10 , 13 ]): # Enter key codes: 10=LF, 13=CR
534
+ self ._logger .debug (
535
+ "Enter key detected! Raw char: %s, setting key to CURSOR_ENTER" , char
536
+ )
537
+ key = "CURSOR_ENTER"
507
538
else :
508
539
key = "KEY_F(5)"
509
540
@@ -915,6 +946,17 @@ def _show_menu(
915
946
showing_indices ,
916
947
)
917
948
949
+ # Determine which row to highlight, if enabled
950
+ self ._highlight_line_offset = None
951
+ if self ._ui_config .cursor_navigation and self ._menu_indices :
952
+ self ._menu_cursor_pos = max (0 , min (self ._menu_cursor_pos ,
953
+ len (self ._menu_indices ) - 1 ))
954
+ selected_global_index = self ._menu_indices [self ._menu_cursor_pos ]
955
+ try :
956
+ self ._highlight_line_offset = showing_indices .index (selected_global_index )
957
+ except ValueError :
958
+ self ._highlight_line_offset = None
959
+
918
960
entry = self ._display (
919
961
lines = menu_lines ,
920
962
line_numbers = line_numbers ,
@@ -925,9 +967,45 @@ def _show_menu(
925
967
await_input = await_input ,
926
968
)
927
969
970
+ # Debug: log what entry we received
971
+ if self ._ui_config .cursor_navigation :
972
+ self ._logger .debug ("Received entry: '%s'" , entry )
973
+
974
+ # Handle arrow navigation for menus when enabled
928
975
if entry in ["KEY_RESIZE" , "KEY_DOWN" , "KEY_UP" , "KEY_NPAGE" , "KEY_PPAGE" , "^F" , "^B" ]:
976
+ if (entry in ["KEY_DOWN" , "KEY_UP" ] and self ._ui_config .cursor_navigation and
977
+ self ._menu_indices ):
978
+ # Move the cursor position
979
+ if entry == "KEY_DOWN" and self ._menu_cursor_pos < len (self ._menu_indices ) - 1 :
980
+ self ._menu_cursor_pos += 1
981
+ # If moved past the last visible item, scroll down one
982
+ if (self ._highlight_line_offset is None or
983
+ self ._highlight_line_offset >= len (showing_indices ) - 1 ):
984
+ # Mimic _display scroll down
985
+ viewport_height = self ._screen_height - len (menu_heading ) - 1
986
+ self .scroll (max (min (self .scroll () + 1 , len (self ._menu_indices )),
987
+ viewport_height ))
988
+ elif entry == "KEY_UP" and self ._menu_cursor_pos > 0 :
989
+ self ._menu_cursor_pos -= 1
990
+ # If moved before the first visible item, scroll up one
991
+ if self ._highlight_line_offset is None or self ._highlight_line_offset <= 0 :
992
+ viewport_height = self ._screen_height - len (menu_heading ) - 1
993
+ self .scroll (max (self .scroll () - 1 , viewport_height ))
994
+ # Re-render with updated highlight
995
+ continue
996
+ # Otherwise, preserve prior behavior
929
997
continue
930
998
999
+ # Enter key should select the highlighted item when cursor nav is enabled
1000
+ if (self ._ui_config .cursor_navigation and
1001
+ (entry == "CURSOR_ENTER" or entry in ["^J" , "^M" , "KEY_ENTER" , "KEY_RETURN" ]) and
1002
+ self ._menu_indices ):
1003
+ self ._logger .debug ("Enter key selection triggered! Entry: '%s', Selecting index %s" ,
1004
+ entry , self ._menu_cursor_pos )
1005
+ index_to_select = self ._menu_indices [self ._menu_cursor_pos ]
1006
+ entry = str (index_to_select )
1007
+ self ._logger .debug ("Changed entry to: '%s'" , entry )
1008
+
931
1009
name , action = self ._template_match_action (entry , current )
932
1010
if name and action :
933
1011
if name == "select" :
0 commit comments