@@ -10,6 +10,15 @@ use std::sync::Arc;
1010use std:: thread;
1111use std:: time:: Duration ;
1212
13+ use pulldown_cmark:: { Options , Parser } ;
14+ use pulldown_cmark_mdcat:: {
15+ push_tty,
16+ resources:: NoopResourceHandler ,
17+ terminal:: { TerminalProgram , TerminalSize } ,
18+ Environment , Settings , Theme ,
19+ } ;
20+ use syntect:: parsing:: SyntaxSet ;
21+
1322/// Message severity levels for consistent UI feedback
1423#[ derive( Debug , Clone , Copy , PartialEq ) ]
1524pub enum MessageSeverity {
@@ -130,6 +139,7 @@ impl Drop for RawModeGuard {
130139}
131140
132141/// UI utilities for displaying messages, animations, and formatting
142+ #[ allow( dead_code) ]
133143pub struct UI {
134144 highlighter : SyntaxHighlighter ,
135145}
@@ -258,14 +268,14 @@ impl UI {
258268 println ! ( ) ;
259269 }
260270
261- pub fn display_session ( & self , session : & crate :: history:: Session ) {
271+ pub fn display_session ( & self , session : & crate :: history:: Session ) -> io :: Result < ( ) > {
262272 if session. display_messages . is_empty ( ) {
263273 println ! (
264274 "{}" ,
265275 "Note: No display history available for this session." . dimmed( )
266276 ) ;
267277 println ! ( ) ;
268- return ;
278+ return Ok ( ( ) ) ;
269279 }
270280
271281 println ! ( "{}" , "═" . repeat( 80 ) . bright_cyan( ) ) ;
@@ -281,9 +291,7 @@ impl UI {
281291 }
282292 DisplayMessage :: AssistantMessage { content } => {
283293 println ! ( "{}" , "Assistant:" . bright_blue( ) . bold( ) ) ;
284- let highlighted = self . highlighter . highlight_text ( content) ;
285- println ! ( "{}" , highlighted) ;
286- println ! ( ) ;
294+ self . print_assistant_text ( content) ?;
287295 }
288296 DisplayMessage :: ToolExecution {
289297 tool_name,
@@ -296,33 +304,25 @@ impl UI {
296304 ) {
297305 if let Some ( command) = input_val. get ( "command" ) . and_then ( |v| v. as_str ( ) )
298306 {
299- println ! (
300- "{} {}" ,
301- "Executing:" . bright_green( ) . bold( ) ,
302- command. bright_cyan( )
303- ) ;
307+ self . print_tool_header ( tool_name, Some ( command) ) ;
304308 }
305309 }
306310 } else {
307- println ! (
308- "{} {}" ,
309- "Using tool:" . bright_yellow( ) . bold( ) ,
310- tool_name. bright_yellow( )
311- ) ;
311+ self . print_tool_header ( tool_name, None ) ;
312312 }
313- println ! ( "{}" , tool_output. dimmed( ) ) ;
314- println ! ( ) ;
313+ self . print_tool_output ( tool_output) ;
315314 }
316315 }
317316 }
318317
319318 println ! ( "{}" , "═" . repeat( 80 ) . bright_cyan( ) ) ;
320319 println ! ( ) ;
320+ Ok ( ( ) )
321321 }
322322
323- pub fn print_assistant_text ( & self , text : & str ) {
324- let highlighted = self . highlighter . highlight_text ( text) ;
325- println ! ( "{}" , highlighted ) ;
323+ pub fn print_assistant_text ( & self , text : & str ) -> io :: Result < ( ) > {
324+ self . print_markdown_highlighted ( text) ? ;
325+ Ok ( ( ) )
326326 }
327327
328328 pub fn print_thinking ( & self , thinking : & str ) {
@@ -331,8 +331,7 @@ impl UI {
331331 "\n {}" ,
332332 "Thinking:" . truecolor( 0x77 , 0x00 , 0xFF ) . bold( ) . dimmed( )
333333 ) ;
334- println ! ( "{}" , thinking. dimmed( ) . italic( ) ) ;
335- println ! ( ) ;
334+ println ! ( "{}\n " , thinking. dimmed( ) . italic( ) ) ;
336335 }
337336 }
338337
@@ -344,7 +343,7 @@ impl UI {
344343 "Executing:" . bright_green( ) . bold( ) ,
345344 cmd. bright_cyan( )
346345 ) ;
347- let _ = io :: stdout ( ) . flush ( ) ;
346+ let _ = stdout ( ) . flush ( ) ;
348347 }
349348 } else {
350349 println ! (
@@ -355,6 +354,35 @@ impl UI {
355354 }
356355 }
357356
357+ pub fn print_tool_output ( & self , tool_output : & str ) {
358+ println ! ( "{}\n " , tool_output. dimmed( ) ) ;
359+ }
360+
361+ /// Render Markdown to the current terminal with ANSI styling + syntax-highlighted fenced code blocks.
362+ pub fn print_markdown_highlighted ( & self , md : & str ) -> io:: Result < ( ) > {
363+ let parser = Parser :: new_ext ( md, Options :: all ( ) ) ;
364+
365+ // Required by the API; current_dir is just a convenient absolute base.
366+ let environment = Environment :: for_local_directory ( & std:: env:: current_dir ( ) ?) ?;
367+
368+ let settings = Settings {
369+ terminal_capabilities : TerminalProgram :: detect ( ) . capabilities ( ) ,
370+ terminal_size : TerminalSize :: detect ( ) . unwrap_or_default ( ) ,
371+ syntax_set : & SyntaxSet :: load_defaults_newlines ( ) ,
372+ theme : Theme :: default ( ) ,
373+ } ;
374+
375+ let mut out = stdout ( ) . lock ( ) ;
376+ push_tty (
377+ & settings,
378+ & environment,
379+ & NoopResourceHandler ,
380+ & mut out,
381+ parser,
382+ ) ?;
383+ out. flush ( )
384+ }
385+
358386 pub fn run_animation_with_interrupt (
359387 action_message : String ,
360388 interrupt_message : String ,
@@ -371,7 +399,7 @@ impl UI {
371399 let mut frame_idx = 0 ;
372400
373401 print ! ( "\n \x1B [?25l" ) ;
374- let _ = io :: stdout ( ) . flush ( ) ;
402+ let _ = stdout ( ) . flush ( ) ;
375403
376404 while running_anim. load ( Ordering :: SeqCst ) {
377405 print ! (
@@ -380,15 +408,15 @@ impl UI {
380408 action_message. truecolor( 0xFF , 0x99 , 0x33 ) ,
381409 interrupt_message. dimmed( ) ,
382410 ) ;
383- let _ = io :: stdout ( ) . flush ( ) ;
411+ let _ = stdout ( ) . flush ( ) ;
384412 frame_idx = ( frame_idx + 1 ) % frames. len ( ) ;
385413 thread:: sleep ( Duration :: from_millis ( 80 ) ) ;
386414 }
387415
388416 print ! ( "\r {}\r " , " " . repeat( 70 ) ) ;
389417 print ! ( "\x1B [?25h" ) ;
390418 println ! ( ) ; // Move to new line so next output doesn't conflict
391- let _ = io :: stdout ( ) . flush ( ) ;
419+ let _ = stdout ( ) . flush ( ) ;
392420 } ) ;
393421
394422 let key_handle = thread:: spawn ( move || {
@@ -419,7 +447,7 @@ impl UI {
419447 // Final cleanup - ensure terminal is in a known good state
420448 let _ = crossterm:: terminal:: disable_raw_mode ( ) ;
421449 print ! ( "\x1B [?25h" ) ;
422- let _ = io :: stdout ( ) . flush ( ) ;
450+ let _ = stdout ( ) . flush ( ) ;
423451 }
424452
425453 pub fn create_tool_display_message (
0 commit comments