@@ -31,8 +31,86 @@ use rio_backend::sugarloaf::{
3131use std:: collections:: { BTreeSet , HashMap } ;
3232use std:: ops:: RangeInclusive ;
3333
34+ use crate :: ime:: Preedit ;
3435use unicode_width:: UnicodeWidthChar ;
3536
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 (
50+ preedit : & Preedit ,
51+ start_row : usize ,
52+ start_col : usize ,
53+ columns : usize ,
54+ rows : usize ,
55+ ) -> Option < Self > {
56+ if preedit. text . is_empty ( ) || columns == 0 || rows == 0 {
57+ return None ;
58+ }
59+
60+ let mut cells = vec ! [ None ; rows. saturating_mul( columns) ] ;
61+ let mut row = start_row;
62+ let mut col = start_col;
63+
64+ for ch in preedit. text . chars ( ) {
65+ if row >= rows {
66+ break ;
67+ }
68+
69+ if col >= columns {
70+ row += 1 ;
71+ col = 0 ;
72+ }
73+ if row >= rows {
74+ break ;
75+ }
76+
77+ let width = ch. width ( ) . unwrap_or ( 1 ) . max ( 1 ) ;
78+ if width > 1 && col + 1 >= columns {
79+ row += 1 ;
80+ col = 0 ;
81+ if row >= rows {
82+ break ;
83+ }
84+ }
85+
86+ let idx = row * columns + col;
87+ if let Some ( cell) = cells. get_mut ( idx) {
88+ * cell = Some ( PreeditCell :: Char ( ch) ) ;
89+ }
90+
91+ if width > 1 && col + 1 < columns {
92+ let spacer_idx = idx + 1 ;
93+ if let Some ( cell) = cells. get_mut ( spacer_idx) {
94+ * cell = Some ( PreeditCell :: Spacer ) ;
95+ }
96+ }
97+
98+ col = col. saturating_add ( width) ;
99+ if col >= columns {
100+ row += 1 ;
101+ col = 0 ;
102+ }
103+ }
104+
105+ Some ( Self { columns, cells } )
106+ }
107+
108+ fn get ( & self , row : usize , col : usize ) -> Option < PreeditCell > {
109+ let idx = row. checked_mul ( self . columns ) ?. saturating_add ( col) ;
110+ self . cells . get ( idx) . copied ( ) . flatten ( )
111+ }
112+ }
113+
36114#[ derive( Default ) ]
37115pub struct Search {
38116 rich_text_id : Option < usize > ,
@@ -256,9 +334,11 @@ impl Renderer {
256334 builder : & mut Content ,
257335 row : & Row < Square > ,
258336 has_cursor : bool ,
337+ visible_row_index : usize ,
259338 line_opt : Option < usize > ,
260339 line : Line ,
261340 renderable_content : & RenderableContent ,
341+ ime_preedit : Option < & PreeditOverlay > ,
262342 hint_matches : Option < & [ rio_backend:: crosswords:: search:: Match ] > ,
263343 focused_match : & Option < RangeInclusive < Pos > > ,
264344 term_colors : & TermColors ,
@@ -279,8 +359,10 @@ impl Renderer {
279359 // First pass: collect all styles and identify font cache misses
280360 for column in 0 ..columns {
281361 let square = & row. inner [ column] ;
362+ let preedit_cell =
363+ ime_preedit. and_then ( |preedit| preedit. get ( visible_row_index, column) ) ;
282364
283- if square. flags . contains ( Flags :: WIDE_CHAR_SPACER ) {
365+ if square. flags . contains ( Flags :: WIDE_CHAR_SPACER ) && preedit_cell . is_none ( ) {
284366 continue ;
285367 }
286368
@@ -374,10 +456,28 @@ impl Renderer {
374456 ) ;
375457 }
376458
459+ if let Some ( cell) = preedit_cell {
460+ match cell {
461+ PreeditCell :: Char ( ch) => {
462+ square_content = ch;
463+ }
464+ PreeditCell :: Spacer => {
465+ square_content = ' ' ;
466+ }
467+ }
468+ if !( has_cursor && column == cursor. state . pos . col ) {
469+ style. color =
470+ self . color ( NamedColor :: DimForeground as usize , term_colors) ;
471+ style. decoration = None ;
472+ style. decoration_color = None ;
473+ }
474+ }
475+
377476 if !is_active {
378477 style. color [ 3 ] = self . unfocused_split_opacity ;
379478 if let Some ( mut background_color) = style. background_color {
380479 background_color[ 3 ] = self . unfocused_split_opacity ;
480+ style. background_color = Some ( background_color) ;
381481 }
382482 }
383483
@@ -940,6 +1040,15 @@ impl Renderer {
9401040
9411041 // Update cursor state from snapshot
9421042 context. renderable_content . cursor . state = terminal_snapshot. cursor ;
1043+ let preedit_overlay = context. ime . preedit ( ) . and_then ( |preedit| {
1044+ PreeditOverlay :: new (
1045+ preedit,
1046+ context. renderable_content . cursor . state . pos . row . 0 . max ( 0 ) as usize ,
1047+ context. renderable_content . cursor . state . pos . col . 0 ,
1048+ terminal_snapshot. columns ,
1049+ terminal_snapshot. visible_rows . len ( ) ,
1050+ )
1051+ } ) ;
9431052
9441053 let mut specific_lines: Option < BTreeSet < LineDamage > > = None ;
9451054
@@ -1037,9 +1146,11 @@ impl Renderer {
10371146 content,
10381147 row,
10391148 has_cursor,
1149+ i,
10401150 None ,
10411151 Line ( ( i as i32 ) - terminal_snapshot. display_offset as i32 ) ,
10421152 & context. renderable_content ,
1153+ preedit_overlay. as_ref ( ) ,
10431154 hint_matches,
10441155 focused_match,
10451156 & terminal_snapshot. colors ,
@@ -1063,12 +1174,14 @@ impl Renderer {
10631174 content,
10641175 visible_row,
10651176 has_cursor,
1177+ line,
10661178 Some ( line) ,
10671179 Line (
10681180 ( line as i32 )
10691181 - terminal_snapshot. display_offset as i32 ,
10701182 ) ,
10711183 & context. renderable_content ,
1184+ preedit_overlay. as_ref ( ) ,
10721185 hint_matches,
10731186 focused_match,
10741187 & terminal_snapshot. colors ,
@@ -1314,4 +1427,25 @@ mod tests {
13141427 Pos :: new( Line ( 2 ) , Column ( 12 ) )
13151428 ) ) ;
13161429 }
1430+
1431+ #[ test]
1432+ fn preedit_overlay_places_wide_chars_and_spacers ( ) {
1433+ let preedit = Preedit :: new ( "啊a" . to_string ( ) , None ) ;
1434+ let overlay = PreeditOverlay :: new ( & preedit, 0 , 0 , 4 , 1 ) . unwrap ( ) ;
1435+
1436+ assert_eq ! ( overlay. get( 0 , 0 ) , Some ( PreeditCell :: Char ( '啊' ) ) ) ;
1437+ assert_eq ! ( overlay. get( 0 , 1 ) , Some ( PreeditCell :: Spacer ) ) ;
1438+ assert_eq ! ( overlay. get( 0 , 2 ) , Some ( PreeditCell :: Char ( 'a' ) ) ) ;
1439+ assert_eq ! ( overlay. get( 0 , 3 ) , None ) ;
1440+ }
1441+
1442+ #[ test]
1443+ fn preedit_overlay_wraps_wide_chars ( ) {
1444+ let preedit = Preedit :: new ( "啊" . to_string ( ) , None ) ;
1445+ let overlay = PreeditOverlay :: new ( & preedit, 0 , 2 , 3 , 2 ) . unwrap ( ) ;
1446+
1447+ assert_eq ! ( overlay. get( 0 , 2 ) , None ) ;
1448+ assert_eq ! ( overlay. get( 1 , 0 ) , Some ( PreeditCell :: Char ( '啊' ) ) ) ;
1449+ assert_eq ! ( overlay. get( 1 , 1 ) , Some ( PreeditCell :: Spacer ) ) ;
1450+ }
13171451}
0 commit comments