@@ -79,19 +79,52 @@ impl Terminal {
7979 tokio:: task:: spawn_blocking ( move || {
8080 tracing:: info!( "PTY reader started" ) ;
8181 let mut buf = [ 0u8 ; 1024 ] ;
82+ let mut unprocessed = Vec :: new ( ) ;
8283 loop {
8384 match reader. read ( & mut buf) {
8485 Ok ( 0 ) => break ,
8586 Ok ( n) => {
86- let s = String :: from_utf8_lossy ( & buf[ ..n] ) . to_string ( ) ;
87- let _ = pty_output_tx. blocking_send ( s) ;
87+ unprocessed. extend_from_slice ( & buf[ ..n] ) ;
88+ let mut check_idx = 0 ;
89+ while check_idx < unprocessed. len ( ) {
90+ match std:: str:: from_utf8 ( & unprocessed[ check_idx..] ) {
91+ Ok ( s) => {
92+ let _ = pty_output_tx. blocking_send ( s. to_string ( ) ) ;
93+ unprocessed. clear ( ) ;
94+ break ;
95+ }
96+ Err ( e) => {
97+ let valid_up_to = e. valid_up_to ( ) ;
98+ if valid_up_to > 0 {
99+ if let Ok ( s) = std:: str:: from_utf8 ( & unprocessed[ check_idx..check_idx + valid_up_to] ) {
100+ let _ = pty_output_tx. blocking_send ( s. to_string ( ) ) ;
101+ }
102+ check_idx += valid_up_to;
103+ }
104+ match e. error_len ( ) {
105+ Some ( err_len) => {
106+ let _ = pty_output_tx. blocking_send ( String :: from ( "\u{FFFD} " ) ) ;
107+ check_idx += err_len;
108+ }
109+ None => {
110+ unprocessed. drain ( ..check_idx) ;
111+ break ;
112+ }
113+ }
114+ }
115+ }
116+ }
88117 }
89118 Err ( e) => {
90119 tracing:: warn!( "PTY read error: {:?}" , e) ;
91120 break ;
92121 }
93122 }
94123 }
124+ if !unprocessed. is_empty ( ) {
125+ let s = String :: from_utf8_lossy ( & unprocessed) . to_string ( ) ;
126+ let _ = pty_output_tx. blocking_send ( s) ;
127+ }
95128 tracing:: info!( "PTY reader stopped" ) ;
96129 } ) ;
97130 }
@@ -209,4 +242,50 @@ mod tests {
209242
210243 Ok ( ( ) )
211244 }
245+
246+ #[ tokio:: test]
247+ async fn test_terminal_unicode ( ) -> Result < ( ) > {
248+ let ( tx, mut rx) = mpsc:: channel :: < String > ( 10 ) ;
249+
250+ let shell = if cfg ! ( target_os = "windows" ) {
251+ "cmd.exe" . to_string ( )
252+ } else {
253+ "cat" . to_string ( )
254+ } ;
255+
256+ let terminal = Terminal :: new (
257+ "test_unicode" . to_string ( ) ,
258+ "session2" . to_string ( ) ,
259+ 30 ,
260+ 80 ,
261+ Some ( shell) ,
262+ None ,
263+ tx,
264+ )
265+ . await ?;
266+
267+ let unicode_str = "привет, как дела? 🚀" ;
268+ terminal. send_input ( format ! ( "{}\n " , unicode_str) ) . await ?;
269+
270+ let mut output = String :: new ( ) ;
271+ let _ = timeout ( Duration :: from_secs ( 2 ) , async {
272+ while let Some ( chunk) = rx. recv ( ) . await {
273+ output. push_str ( & chunk) ;
274+ if output. contains ( unicode_str) {
275+ break ;
276+ }
277+ }
278+ } )
279+ . await ;
280+
281+ println ! ( "Output: {}" , output) ;
282+
283+ assert ! (
284+ output. contains( unicode_str) ,
285+ "terminal did not echo expected unicode output, got: {}" ,
286+ output
287+ ) ;
288+
289+ Ok ( ( ) )
290+ }
212291}
0 commit comments