@@ -237,7 +237,8 @@ def get_initial_tmux_info():
237237 """Get all needed tmux info in one optimized call"""
238238 format_str = '#{pane_id},#{window_zoomed_flag},#{pane_active},' + \
239239 '#{pane_top},#{pane_height},#{pane_left},#{pane_width},' + \
240- '#{pane_in_mode},#{scroll_position}'
240+ '#{pane_in_mode},#{scroll_position},' + \
241+ '#{cursor_y},#{cursor_x},#{copy_cursor_y},#{copy_cursor_x}'
241242
242243 cmd = ['tmux' , 'list-panes' , '-F' , format_str ]
243244 output = sh (cmd ).strip ()
@@ -248,19 +249,18 @@ def get_initial_tmux_info():
248249 continue
249250
250251 fields = line .split (',' )
251- if len (fields ) != 9 :
252- continue
253-
254252 # Use destructuring assignment for better readability and performance
255253 (pane_id , zoomed , active , top , height ,
256- left , width , in_mode , scroll_pos ) = fields
254+ left , width , in_mode , scroll_pos ,
255+ cursor_y , cursor_x , copy_cursor_y , copy_cursor_x ) = fields
257256
258257 # Only show all panes in non-zoomed state, or only active pane in zoomed state
259258 if zoomed == "1" and active != "1" :
260259 continue
261260
262261 pane = PaneInfo (
263262 pane_id = pane_id ,
263+ active = active == "1" ,
264264 start_y = int (top ),
265265 height = int (height ),
266266 start_x = int (left ),
@@ -271,16 +271,26 @@ def get_initial_tmux_info():
271271 pane .copy_mode = (in_mode == "1" )
272272 pane .scroll_position = int (scroll_pos or 0 )
273273
274+ # Set cursor position
275+ if in_mode == "1" : # If in copy mode
276+ pane .cursor_y = int (copy_cursor_y )
277+ pane .cursor_x = int (copy_cursor_x )
278+ else : # If not in copy mode, cursor is at bottom left
279+ pane .cursor_y = int (cursor_y )
280+ pane .cursor_x = int (cursor_x )
281+
274282 panes_info .append (pane )
275283
276284 return panes_info
277285
278286
279287class PaneInfo :
280- __slots__ = ('pane_id' , 'start_y' , 'height' , 'start_x' , 'width' ,
281- 'lines' , 'positions' , 'copy_mode' , 'scroll_position' )
288+ __slots__ = ('pane_id' , 'active' , 'start_y' , 'height' , 'start_x' , 'width' ,
289+ 'lines' , 'positions' , 'copy_mode' , 'scroll_position' ,
290+ 'cursor_y' , 'cursor_x' )
282291
283- def __init__ (self , pane_id , start_y , height , start_x , width ):
292+ def __init__ (self , pane_id , active , start_y , height , start_x , width ):
293+ self .active = active
284294 self .pane_id = pane_id
285295 self .start_y = start_y
286296 self .height = height
@@ -290,12 +300,8 @@ def __init__(self, pane_id, start_y, height, start_x, width):
290300 self .positions = []
291301 self .copy_mode = False
292302 self .scroll_position = 0
293-
294-
295- def tmux_pane_id ():
296- # Get the ID of the pane that launched this script
297- source_pane = os .environ .get ('TMUX_PANE' )
298- return source_pane or '%0'
303+ self .cursor_y = 0
304+ self .cursor_x = 0
299305
300306
301307def get_terminal_size ():
@@ -358,27 +364,21 @@ def tmux_move_cursor(pane, line_num, true_col):
358364 sh (cmd )
359365
360366
361- class HintTree :
362- def __init__ (self ):
363- self .targets = {} # Store single key mappings
364- self .children = {} # Store double key mapping subtrees
367+ def assign_hints_by_distance (matches , cursor_y , cursor_x ):
368+ """Sort matches by distance and assign hints"""
369+ # Calculate distances and sort
370+ matches_with_dist = []
371+ for match in matches :
372+ pane , line_num , col = match
373+ dist = (line_num - cursor_y )** 2 + (col - cursor_x )** 2
374+ matches_with_dist .append ((dist , match ))
365375
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 )
376+ matches_with_dist .sort (key = lambda x : x [0 ]) # Sort by distance
374377
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
378+ # Generate hints and create mapping
379+ hints = generate_hints (KEYS , len (matches_with_dist ))
380+ logging .debug (f'{ hints } ' )
381+ return {hint : match for (_ , match ), hint in zip (matches_with_dist , hints )}
382382
383383
384384def generate_hints (keys : str , needed_count : Optional [int ] = None ) -> List [str ]:
@@ -399,28 +399,29 @@ def generate_hints(keys: str, needed_count: Optional[int] = None) -> List[str]:
399399
400400 # Generate all possible double char combinations
401401 double_char_hints = []
402- for prefix in keys_list : # Including first char as prefix
402+ for prefix in keys_list :
403403 for suffix in keys_list :
404404 double_char_hints .append (prefix + suffix )
405405
406- # If we need maximum possible combinations, return all double-char hints
407- if needed_count == max_hints :
408- return double_char_hints
409-
410406 # Dynamically calculate how many single chars to keep
411407 single_chars = 0
412408 for i in range (key_count , 0 , - 1 ):
413- if needed_count <= (key_count - i + 1 ) * key_count :
409+ if needed_count <= (key_count - i ) * key_count + i :
414410 single_chars = i
415411 break
416412
417413 hints = []
418- # Take needed doubles from the end
419- needed_doubles = needed_count - single_chars
420- hints .extend (double_char_hints [- needed_doubles :])
421-
422414 # Add single chars at the beginning
423- hints [0 :0 ] = keys_list [:single_chars ]
415+ single_char_hints = keys_list [:single_chars ]
416+ hints .extend (single_char_hints )
417+
418+ # Filter out double char hints that start with any single char hint
419+ filtered_doubles = [h for h in double_char_hints
420+ if h [0 ] not in single_char_hints ]
421+
422+ # Take needed doubles
423+ needed_doubles = needed_count - single_chars
424+ hints .extend (filtered_doubles [:needed_doubles ])
424425
425426 return hints [:needed_count ]
426427
@@ -504,49 +505,55 @@ def find_matches(panes, search_ch):
504505
505506
506507@perf_timer ("Drawing hints" )
507- def update_hints_display (screen , panes , hint_tree , current_key ):
508+ def update_hints_display (screen , positions , current_key ):
508509 """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 )
510+ for screen_y , screen_x , pane_right_edge , char , next_char , hint in positions :
511+ logging .debug (f'{ screen_x } { pane_right_edge } { char } { next_char } { hint } ' )
512+ if hint .startswith (current_key ):
513+ next_x = screen_x + get_char_width (char )
514+ if next_x < pane_right_edge :
515+ logging .debug (f"Restoring next char { next_x } { next_char } " )
516+ screen .addstr (screen_y , next_x , next_char )
517+ else :
518+ logging .debug (f"Non-matching hint { screen_x } { screen_y } { char } " )
519+ # Restore original character for non-matching hints
520+ screen .addstr (screen_y , screen_x , char )
521+ # Always restore second character
522+ next_x = screen_x + get_char_width (char )
523+ if next_x < pane_right_edge :
524+ logging .debug (f"Restoring next char { next_x } { next_char } " )
525+ screen .addstr (screen_y , next_x , next_char )
526+ continue
521527
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 )
528+ # For matching hints:
529+ if len (hint ) > len (current_key ):
530+ # Show remaining hint character
531+ screen .addstr (screen_y , screen_x ,
532+ hint [len (current_key )], screen .A_HINT2 )
533+ else :
534+ # If hint is fully entered, restore all original characters
535+ screen .addstr (screen_y , screen_x , char )
536+ next_x = screen_x + get_char_width (char )
537+ if next_x < pane_right_edge :
538+ screen .addstr (screen_y , next_x , next_char )
526539
527540 screen .refresh ()
528541
529542
530- def draw_all_hints (panes , terminal_height , screen ):
543+ def draw_all_hints (positions , terminal_height , screen ):
531544 """Draw all hints across all panes"""
532- for pane in panes :
533- for line_num , col , char , hint in pane .positions :
534- y = pane .start_y + line_num
535- x = pane .start_x + col
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 ):
545+ for screen_y , screen_x , pane_right_edge , char , next_char , hint in positions :
546+ if screen_y >= terminal_height :
547+ continue
540548
541- # Always show first character
542- screen .addstr (y , x , hint [0 ], screen .A_HINT1 )
549+ # Draw first character of hint
550+ screen .addstr (screen_y , screen_x , hint [0 ], screen .A_HINT1 )
543551
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 )
552+ # Draw second character if hint has two chars and space allows
553+ if len (hint ) > 1 :
554+ next_x = screen_x + get_char_width (char )
555+ if next_x < pane_right_edge :
556+ screen .addstr (screen_y , next_x , hint [1 ], screen .A_HINT2 )
550557
551558 screen .refresh ()
552559
@@ -562,16 +569,42 @@ def main(screen: Screen):
562569
563570 search_ch = getch ()
564571 matches = find_matches (panes , search_ch )
565- hints = generate_hints (KEYS , len (matches ))
566572
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 ))
573+ # If only one match, jump directly
574+ if len (matches ) == 1 :
575+ pane , line_num , col = matches [0 ]
576+ true_col = get_true_position (pane .lines [line_num ], col )
577+ tmux_move_cursor (pane , line_num , true_col )
578+ return
579+
580+ # Get cursor position from current pane
581+ current_pane = next (p for p in panes if p .active )
582+ cursor_y = current_pane .cursor_y
583+ cursor_x = current_pane .cursor_x
584+ logging .debug (f"Cursor position: { current_pane .pane_id } , {
585+ cursor_y } , { cursor_x } " )
586+
587+ # Replace HintTree with direct hint assignment
588+ hint_mapping = assign_hints_by_distance (matches , cursor_y , cursor_x )
589+
590+ # Create flat positions list with all needed info
591+ positions = [
592+ (pane .start_y + line_num , # screen_y
593+ pane .start_x + col , # screen_x
594+ pane .start_x + pane .width , # pane_right_edge
595+ char , # original char at hint position
596+ # original char at second hint position (if exists)
597+ next_char ,
598+ hint )
599+ for hint , (pane , line_num , col ) in hint_mapping .items ()
600+ for char , next_char in [(
601+ pane .lines [line_num ][col ],
602+ pane .lines [line_num ][col + 1 ] if col +
603+ 1 < len (pane .lines [line_num ]) else ''
604+ )]
605+ ]
573606
574- draw_all_hints (panes , terminal_height , screen )
607+ draw_all_hints (positions , terminal_height , screen )
575608
576609 # Handle user input
577610 key_sequence = ""
@@ -581,7 +614,7 @@ def main(screen: Screen):
581614 return
582615
583616 key_sequence += ch
584- target = hint_tree .get (key_sequence )
617+ target = hint_mapping .get (key_sequence )
585618
586619 if target :
587620 pane , line_num , col = target
@@ -592,7 +625,7 @@ def main(screen: Screen):
592625 return # Exit program
593626 else :
594627 # Update display to show remaining possible hints
595- update_hints_display (screen , panes , hint_tree , key_sequence )
628+ update_hints_display (screen , positions , key_sequence )
596629
597630
598631if __name__ == '__main__' :
0 commit comments