@@ -32,6 +32,78 @@ use std::collections::{BTreeSet, HashMap};
3232use std:: ops:: RangeInclusive ;
3333
3434use unicode_width:: UnicodeWidthChar ;
35+ use crate :: ime:: Preedit ;
36+
37+ #[ derive( Clone , Copy , Debug , PartialEq , Eq ) ]
38+ enum PreeditCell {
39+ Char ( char ) ,
40+ Spacer ,
41+ }
42+
43+ struct PreeditOverlay {
44+ columns : usize ,
45+ cells : Vec < Option < PreeditCell > > ,
46+ }
47+
48+ impl PreeditOverlay {
49+ fn new ( preedit : & Preedit , start_row : usize , start_col : usize , columns : usize , rows : usize ) -> Option < Self > {
50+ if preedit. text . is_empty ( ) || columns == 0 || rows == 0 {
51+ return None ;
52+ }
53+
54+ let mut cells = vec ! [ None ; rows. saturating_mul( columns) ] ;
55+ let mut row = start_row;
56+ let mut col = start_col;
57+
58+ for ch in preedit. text . chars ( ) {
59+ if row >= rows {
60+ break ;
61+ }
62+
63+ if col >= columns {
64+ row += 1 ;
65+ col = 0 ;
66+ }
67+ if row >= rows {
68+ break ;
69+ }
70+
71+ let width = ch. width ( ) . unwrap_or ( 1 ) . max ( 1 ) ;
72+ if width > 1 && col + 1 >= columns {
73+ row += 1 ;
74+ col = 0 ;
75+ if row >= rows {
76+ break ;
77+ }
78+ }
79+
80+ let idx = row * columns + col;
81+ if let Some ( cell) = cells. get_mut ( idx) {
82+ * cell = Some ( PreeditCell :: Char ( ch) ) ;
83+ }
84+
85+ if width > 1 && col + 1 < columns {
86+ let spacer_idx = idx + 1 ;
87+ if let Some ( cell) = cells. get_mut ( spacer_idx) {
88+ * cell = Some ( PreeditCell :: Spacer ) ;
89+ }
90+ }
91+
92+ col = col. saturating_add ( width) ;
93+ if col >= columns {
94+ row += 1 ;
95+ col = 0 ;
96+ }
97+ }
98+
99+ Some ( Self { columns, cells } )
100+ }
101+
102+ fn get ( & self , row : usize , col : usize ) -> Option < PreeditCell > {
103+ let idx = row. checked_mul ( self . columns ) ?. saturating_add ( col) ;
104+ self . cells . get ( idx) . copied ( ) . flatten ( )
105+ }
106+ }
35107
36108#[ derive( Default ) ]
37109pub struct Search {
@@ -256,9 +328,11 @@ impl Renderer {
256328 builder : & mut Content ,
257329 row : & Row < Square > ,
258330 has_cursor : bool ,
331+ visible_row_index : usize ,
259332 line_opt : Option < usize > ,
260333 line : Line ,
261334 renderable_content : & RenderableContent ,
335+ ime_preedit : Option < & PreeditOverlay > ,
262336 hint_matches : Option < & [ rio_backend:: crosswords:: search:: Match ] > ,
263337 focused_match : & Option < RangeInclusive < Pos > > ,
264338 term_colors : & TermColors ,
@@ -279,8 +353,10 @@ impl Renderer {
279353 // First pass: collect all styles and identify font cache misses
280354 for column in 0 ..columns {
281355 let square = & row. inner [ column] ;
356+ let preedit_cell =
357+ ime_preedit. and_then ( |preedit| preedit. get ( visible_row_index, column) ) ;
282358
283- if square. flags . contains ( Flags :: WIDE_CHAR_SPACER ) {
359+ if square. flags . contains ( Flags :: WIDE_CHAR_SPACER ) && preedit_cell . is_none ( ) {
284360 continue ;
285361 }
286362
@@ -374,10 +450,28 @@ impl Renderer {
374450 ) ;
375451 }
376452
453+ if let Some ( cell) = preedit_cell {
454+ match cell {
455+ PreeditCell :: Char ( ch) => {
456+ square_content = ch;
457+ }
458+ PreeditCell :: Spacer => {
459+ square_content = ' ' ;
460+ }
461+ }
462+ if !( has_cursor && column == cursor. state . pos . col ) {
463+ style. color =
464+ self . color ( NamedColor :: DimForeground as usize , term_colors) ;
465+ style. decoration = None ;
466+ style. decoration_color = None ;
467+ }
468+ }
469+
377470 if !is_active {
378471 style. color [ 3 ] = self . unfocused_split_opacity ;
379472 if let Some ( mut background_color) = style. background_color {
380473 background_color[ 3 ] = self . unfocused_split_opacity ;
474+ style. background_color = Some ( background_color) ;
381475 }
382476 }
383477
@@ -940,6 +1034,15 @@ impl Renderer {
9401034
9411035 // Update cursor state from snapshot
9421036 context. renderable_content . cursor . state = terminal_snapshot. cursor ;
1037+ let preedit_overlay = context. ime . preedit ( ) . and_then ( |preedit| {
1038+ PreeditOverlay :: new (
1039+ preedit,
1040+ context. renderable_content . cursor . state . pos . row . 0 . max ( 0 ) as usize ,
1041+ context. renderable_content . cursor . state . pos . col . 0 ,
1042+ terminal_snapshot. columns ,
1043+ terminal_snapshot. visible_rows . len ( ) ,
1044+ )
1045+ } ) ;
9431046
9441047 let mut specific_lines: Option < BTreeSet < LineDamage > > = None ;
9451048
@@ -1033,18 +1136,20 @@ impl Renderer {
10331136 for ( i, row) in terminal_snapshot. visible_rows . iter ( ) . enumerate ( ) {
10341137 let has_cursor = is_cursor_visible
10351138 && context. renderable_content . cursor . state . pos . row == i;
1036- self . create_line (
1037- content,
1038- row,
1039- has_cursor,
1040- None ,
1041- Line ( ( i as i32 ) - terminal_snapshot. display_offset as i32 ) ,
1042- & context. renderable_content ,
1043- hint_matches,
1044- focused_match,
1045- & terminal_snapshot. colors ,
1046- is_active,
1047- ) ;
1139+ self . create_line (
1140+ content,
1141+ row,
1142+ has_cursor,
1143+ i,
1144+ None ,
1145+ Line ( ( i as i32 ) - terminal_snapshot. display_offset as i32 ) ,
1146+ & context. renderable_content ,
1147+ preedit_overlay. as_ref ( ) ,
1148+ hint_matches,
1149+ focused_match,
1150+ & terminal_snapshot. colors ,
1151+ is_active,
1152+ ) ;
10481153 }
10491154 content. build ( ) ;
10501155 // let _duration = start.elapsed();
@@ -1063,12 +1168,14 @@ impl Renderer {
10631168 content,
10641169 visible_row,
10651170 has_cursor,
1171+ line,
10661172 Some ( line) ,
10671173 Line (
10681174 ( line as i32 )
10691175 - terminal_snapshot. display_offset as i32 ,
10701176 ) ,
10711177 & context. renderable_content ,
1178+ preedit_overlay. as_ref ( ) ,
10721179 hint_matches,
10731180 focused_match,
10741181 & terminal_snapshot. colors ,
@@ -1314,4 +1421,25 @@ mod tests {
13141421 Pos :: new( Line ( 2 ) , Column ( 12 ) )
13151422 ) ) ;
13161423 }
1424+
1425+ #[ test]
1426+ fn preedit_overlay_places_wide_chars_and_spacers ( ) {
1427+ let preedit = Preedit :: new ( "好a" . to_string ( ) , None ) ;
1428+ let overlay = PreeditOverlay :: new ( & preedit, 0 , 0 , 4 , 1 ) . unwrap ( ) ;
1429+
1430+ assert_eq ! ( overlay. get( 0 , 0 ) , Some ( PreeditCell :: Char ( '啊' ) ) ) ;
1431+ assert_eq ! ( overlay. get( 0 , 1 ) , Some ( PreeditCell :: Spacer ) ) ;
1432+ assert_eq ! ( overlay. get( 0 , 2 ) , Some ( PreeditCell :: Char ( 'a' ) ) ) ;
1433+ assert_eq ! ( overlay. get( 0 , 3 ) , None ) ;
1434+ }
1435+
1436+ #[ test]
1437+ fn preedit_overlay_wraps_wide_chars ( ) {
1438+ let preedit = Preedit :: new ( "好" . to_string ( ) , None ) ;
1439+ let overlay = PreeditOverlay :: new ( & preedit, 0 , 2 , 3 , 2 ) . unwrap ( ) ;
1440+
1441+ assert_eq ! ( overlay. get( 0 , 2 ) , None ) ;
1442+ assert_eq ! ( overlay. get( 1 , 0 ) , Some ( PreeditCell :: Char ( '啊' ) ) ) ;
1443+ assert_eq ! ( overlay. get( 1 , 1 ) , Some ( PreeditCell :: Spacer ) ) ;
1444+ }
13171445}
0 commit comments