@@ -358,17 +358,71 @@ def tmux_move_cursor(pane, line_num, true_col):
358358 sh (cmd )
359359
360360
361+ class HintTree :
362+ def __init__ (self ):
363+ self .targets = {} # Store single key mappings
364+ self .children = {} # Store double key mapping subtrees
365+
366+ def add (self , hint , target ):
367+ if len (hint ) == 1 :
368+ self .targets [hint ] = target
369+ else :
370+ first , rest = hint [0 ], hint [1 ]
371+ if first not in self .children :
372+ self .children [first ] = HintTree ()
373+ self .children [first ].add (rest , target )
374+
375+ def get (self , key_sequence ):
376+ if len (key_sequence ) == 1 :
377+ return self .targets .get (key_sequence )
378+ first , rest = key_sequence [0 ], key_sequence [1 ]
379+ if first in self .children :
380+ return self .children [first ].get (rest )
381+ return None
382+
383+
361384def generate_hints (keys : str , needed_count : Optional [int ] = None ) -> List [str ]:
362- """Generate only as many hints as needed"""
363- if needed_count is None :
364- return [k1 + k2 for k1 in keys for k2 in keys ]
385+ """Generate hints with optimal single/double key distribution"""
386+ if not needed_count :
387+ needed_count = len (keys )** 2
388+
389+ keys_list = list (keys )
390+ key_count = len (keys_list )
391+ max_hints = key_count * key_count # All possible double-char combinations
392+
393+ if needed_count > max_hints :
394+ needed_count = max_hints
395+
396+ # When needed hints count is less than or equal to available keys, use single chars
397+ if needed_count <= key_count :
398+ return keys_list [:needed_count ]
399+
400+ # Generate all possible double char combinations
401+ double_char_hints = []
402+ for prefix in keys_list : # Including first char as prefix
403+ for suffix in keys_list :
404+ double_char_hints .append (prefix + suffix )
405+
406+ # If we need maximum possible combinations, return all double-char hints
407+ if needed_count == max_hints :
408+ return double_char_hints
409+
410+ # Dynamically calculate how many single chars to keep
411+ single_chars = 0
412+ for i in range (key_count , 0 , - 1 ):
413+ if needed_count <= (key_count - i + 1 ) * key_count :
414+ single_chars = i
415+ break
416+
365417 hints = []
366- for k1 in keys :
367- for k2 in keys :
368- hints .append (k1 + k2 )
369- if len (hints ) >= needed_count :
370- return hints
371- return hints
418+ # Take needed doubles from the end
419+ needed_doubles = needed_count - single_chars
420+ hints .extend (double_char_hints [- needed_doubles :])
421+
422+ # Add single chars at the beginning
423+ hints [0 :0 ] = keys_list [:single_chars ]
424+
425+ return hints [:needed_count ]
372426
373427
374428@perf_timer ()
@@ -429,38 +483,71 @@ def draw_all_panes(panes, max_x, padding_cache, terminal_height, screen):
429483
430484
431485@perf_timer ("Finding matches" )
432- def find_matches (panes , search_ch , hints ):
433- """Find all matches for the search character and assign hints"""
434- hint_index = 0
435- hint_positions = {} # Add lookup dictionary
486+ def find_matches (panes , search_ch ):
487+ """Find all matches and return match list"""
488+ matches = []
436489 for pane in panes :
437- for line_num , line in enumerate (pane .lines ): # Use lines directly
438- for match in re .finditer (search_ch , line .lower ()):
439- if hint_index >= len (hints ):
440- continue
441- visual_col = sum (get_char_width (c )
442- for c in line [:match .start ()])
443- position = (pane , line_num , visual_col )
444- hint = hints [hint_index ]
445- pane .positions .append (
446- (line_num , visual_col , line [match .start ()], hint ))
447- hint_positions [hint ] = position # Store for quick lookup
448- hint_index += 1
449- return hint_positions
490+ for line_num , line in enumerate (pane .lines ):
491+ # Search each position in the line
492+ pos = 0
493+ while pos < len (line ):
494+ idx = line .lower ().find (search_ch .lower (), pos )
495+ if idx == - 1 :
496+ break
497+
498+ # Calculate visual column position
499+ visual_col = sum (get_char_width (c ) for c in line [:idx ])
500+ matches .append ((pane , line_num , visual_col ))
501+ pos = idx + 1
502+
503+ return matches
450504
451505
452506@perf_timer ("Drawing hints" )
507+ def update_hints_display (screen , panes , hint_tree , current_key ):
508+ """Update hint display based on current key sequence"""
509+ terminal_width , terminal_height = get_terminal_size ()
510+
511+ for pane in panes :
512+ for line_num , col , char , hint in pane .positions :
513+ y = pane .start_y + line_num
514+ x = pane .start_x + col
515+
516+ # First restore the second character position to original character
517+ if len (hint ) > 1 :
518+ char_width = get_char_width (char )
519+ if x + char_width < pane .start_x + pane .width :
520+ screen .addstr (y , x + char_width , char )
521+
522+ # Then show new hints based on current input
523+ if hint .startswith (current_key ):
524+ if len (hint ) > len (current_key ):
525+ screen .addstr (y , x , hint [len (current_key )], screen .A_HINT2 )
526+
527+ screen .refresh ()
528+
529+
453530def draw_all_hints (panes , terminal_height , screen ):
454531 """Draw all hints across all panes"""
455532 for pane in panes :
456533 for line_num , col , char , hint in pane .positions :
457534 y = pane .start_y + line_num
458535 x = pane .start_x + col
459- if (y < min (pane .start_y + pane .height , terminal_height ) and x + get_char_width (char ) <= pane .start_x + pane .width ):
536+
537+ # Ensure position is within visible range
538+ if (y < min (pane .start_y + pane .height , terminal_height ) and
539+ x + get_char_width (char ) <= pane .start_x + pane .width ):
540+
541+ # Always show first character
460542 screen .addstr (y , x , hint [0 ], screen .A_HINT1 )
461- char_width = get_char_width (char )
462- if x + char_width < pane .start_x + pane .width :
463- screen .addstr (y , x + char_width , hint [1 ], screen .A_HINT2 )
543+
544+ # Only show second character for two-character hints
545+ if len (hint ) > 1 :
546+ char_width = get_char_width (char )
547+ if x + char_width < pane .start_x + pane .width :
548+ screen .addstr (y , x + char_width ,
549+ hint [1 ], screen .A_HINT2 )
550+
464551 screen .refresh ()
465552
466553
@@ -473,39 +560,39 @@ def main(screen: Screen):
473560 draw_all_panes (panes , max_x , padding_cache , terminal_height , screen )
474561 sh (['tmux' , 'select-window' , '-t' , '{end}' ])
475562
476- hints = generate_hints (KEYS )
477563 search_ch = getch ()
478- hint_positions = find_matches (panes , search_ch , hints )
479-
480- draw_all_hints (panes , terminal_height , screen )
564+ matches = find_matches (panes , search_ch )
565+ hints = generate_hints (KEYS , len (matches ))
481566
482- ch1 = getch ()
483- if ch1 not in KEYS :
484- return
567+ # Build hint tree
568+ hint_tree = HintTree ()
569+ for match , hint in zip (matches , hints ):
570+ hint_tree .add (hint , match )
571+ pane , line_num , col = match
572+ pane .positions .append ((line_num , col , pane .lines [line_num ][col ], hint ))
485573
486- # Update hints consistently using screen.addstr
487- for pane in panes :
488- for line_num , col , char , hint in pane .positions :
489- y = pane .start_y + line_num
490- x = pane .start_x + col
491- char_width = get_char_width (char )
492- if (y < min (pane .start_y + pane .height , terminal_height ) and
493- x + char_width <= pane .start_x + pane .width ):
494- screen .addstr (y , x , hint [1 ], screen .A_HINT2 )
495- if x + char_width + 1 < pane .start_x + pane .width :
496- screen .addstr (y , x + char_width , char )
574+ draw_all_hints (panes , terminal_height , screen )
497575
498- screen .refresh ()
576+ # Handle user input
577+ key_sequence = ""
578+ while True :
579+ ch = getch ()
580+ if ch not in KEYS :
581+ return
499582
500- ch2 = getch ()
501- if ch2 not in KEYS :
502- return
583+ key_sequence += ch
584+ target = hint_tree .get (key_sequence )
503585
504- target_hint = ch1 + ch2
505- if target_hint in hint_positions :
506- pane , line_num , col = hint_positions [target_hint ]
507- true_col = get_true_position (pane .lines [line_num ], col )
508- tmux_move_cursor (pane , line_num , true_col )
586+ if target :
587+ pane , line_num , col = target
588+ true_col = get_true_position (pane .lines [line_num ], col )
589+ tmux_move_cursor (pane , line_num , true_col )
590+ return # Exit after finding and moving to target
591+ elif len (key_sequence ) >= 2 : # If no target found after 2 chars
592+ return # Exit program
593+ else :
594+ # Update display to show remaining possible hints
595+ update_hints_display (screen , panes , hint_tree , key_sequence )
509596
510597
511598if __name__ == '__main__' :
0 commit comments