1use std::io::Write;
7
8use crate::render::TokenRenderer;
9use crate::viz::RenderSink;
10
11const ANSI_RESET: &str = "\x1b[0m";
13
14#[derive(Debug)]
16pub struct TerminalSink<W: Write> {
17 renderer: TokenRenderer<W>,
18}
19
20impl<W: Write> TerminalSink<W> {
21 pub fn new(renderer: TokenRenderer<W>) -> Self {
23 Self { renderer }
24 }
25
26 pub fn renderer_mut(&mut self) -> &mut TokenRenderer<W> {
29 &mut self.renderer
30 }
31
32 pub fn into_renderer(self) -> TokenRenderer<W> {
34 self.renderer
35 }
36}
37
38impl<W: Write> RenderSink for TerminalSink<W> {
39 fn visible_text(&mut self, text: &str) {
40 self.renderer.set_in_think(false);
41 self.renderer.write(text);
42 }
43 fn think_text(&mut self, text: &str) {
44 self.renderer.set_in_think(true);
45 self.renderer.write(text);
46 }
47 fn tool_text(&mut self, text: &str) {
48 self.renderer.set_in_think(false);
52 self.renderer.plain(text);
53 }
54 fn error_text(&mut self, text: &str) {
55 self.renderer.set_in_think(false);
56 self.renderer.color("\x1b[1;31m");
57 self.renderer.plain(text);
58 self.renderer.color(ANSI_RESET);
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65 use crate::render::RenderOptions;
66
67 #[test]
68 fn tool_text_is_not_markdown_processed() {
69 let opts = RenderOptions {
70 use_color: false,
71 format_thinking: true,
72 format_markdown: true,
73 };
74 let mut sink = TerminalSink::new(TokenRenderer::new(Vec::new(), opts));
75 sink.tool_text("pattern=**/mod.rs");
76 let out = String::from_utf8(sink.into_renderer().into_sink()).unwrap();
77 assert!(
78 out.contains("**/mod.rs"),
79 "tool banners must render verbatim, got {out:?}"
80 );
81 }
82}