@@ -28,6 +28,7 @@ use tokio::sync::mpsc::UnboundedReceiver;
2828use tokio:: sync:: mpsc:: error:: TryRecvError ;
2929use tokio:: task:: JoinHandle ;
3030use tui_textarea:: TextArea ;
31+ use unicode_width:: UnicodeWidthStr ;
3132
3233use crate :: chat:: { ChatRuntime , handle_user_input} ;
3334use crate :: kernel:: MessageEvent ;
@@ -609,23 +610,38 @@ fn cursor_visual_position(textarea: &TextArea, content_width: u16) -> (usize, us
609610 let mut visual_row = 0_usize ;
610611
611612 for line in lines. iter ( ) . take ( cursor_row) {
612- let line_len = line. chars ( ) . count ( ) ;
613- let wrapped_rows = if line_len == 0 {
613+ let display_width = UnicodeWidthStr :: width ( line. as_str ( ) ) ;
614+ let wrapped_rows = if display_width == 0 {
614615 1
615616 } else {
616- line_len . saturating_sub ( 1 ) / width + 1
617+ display_width . saturating_sub ( 1 ) / width + 1
617618 } ;
618619 visual_row = visual_row. saturating_add ( wrapped_rows) ;
619620 }
620621
621- let current_line_len = lines[ cursor_row] . chars ( ) . count ( ) ;
622+ let current_line = lines[ cursor_row] . as_str ( ) ;
623+ let current_line_len = current_line. chars ( ) . count ( ) ;
622624 let cursor_col = cursor_col. min ( current_line_len) ;
623- visual_row = visual_row. saturating_add ( cursor_col / width) ;
624- let visual_col = cursor_col % width;
625+ let cursor_display_col = display_width_for_char_prefix ( current_line, cursor_col) ;
626+ visual_row = visual_row. saturating_add ( cursor_display_col / width) ;
627+ let visual_col = cursor_display_col % width;
625628
626629 ( visual_row, visual_col)
627630}
628631
632+ fn display_width_for_char_prefix ( line : & str , char_count : usize ) -> usize {
633+ if char_count == 0 {
634+ return 0 ;
635+ }
636+
637+ let split_at = line
638+ . char_indices ( )
639+ . nth ( char_count)
640+ . map ( |( byte_idx, _) | byte_idx)
641+ . unwrap_or ( line. len ( ) ) ;
642+ UnicodeWidthStr :: width ( & line[ ..split_at] )
643+ }
644+
629645fn input_rendered_line_count ( textarea : & TextArea , content_width : u16 ) -> usize {
630646 Paragraph :: new ( input_text ( textarea) )
631647 . wrap ( Wrap { trim : false } )
@@ -973,4 +989,22 @@ mod tests {
973989 let ( row, col) = cursor_visual_position ( & textarea, 5 ) ;
974990 assert_eq ! ( ( row, col) , ( 1 , 1 ) ) ;
975991 }
992+
993+ #[ test]
994+ fn cursor_visual_position_accounts_for_wide_char_width ( ) {
995+ let mut textarea = TextArea :: default ( ) ;
996+ textarea. insert_str ( "你好a" ) ;
997+
998+ let ( row, col) = cursor_visual_position ( & textarea, 4 ) ;
999+ assert_eq ! ( ( row, col) , ( 1 , 1 ) ) ;
1000+ }
1001+
1002+ #[ test]
1003+ fn cursor_visual_position_counts_wrapped_wide_lines_before_cursor_row ( ) {
1004+ let mut textarea = TextArea :: default ( ) ;
1005+ textarea. insert_str ( "你好你好\n x" ) ;
1006+
1007+ let ( row, col) = cursor_visual_position ( & textarea, 4 ) ;
1008+ assert_eq ! ( ( row, col) , ( 2 , 1 ) ) ;
1009+ }
9761010}
0 commit comments