1use crate::ui::iocraft::{
2 IocraftHandle, IocraftSegment, convert_style as convert_to_iocraft_style, theme_from_styles,
3};
4use crate::ui::theme;
5use crate::utils::transcript;
6use anstream::{AutoStream, ColorChoice};
7use anstyle::{Reset, Style};
8use anstyle_query::{clicolor, clicolor_force, no_color, term_supports_color};
9use anyhow::Result;
10use std::io::{self, Write};
11
12#[derive(Clone, Copy)]
14pub enum MessageStyle {
15 Info,
16 Error,
17 Output,
18 Response,
19 Tool,
20 User,
21 Reasoning,
22}
23
24impl MessageStyle {
25 fn style(self) -> Style {
26 let styles = theme::active_styles();
27 match self {
28 Self::Info => styles.info,
29 Self::Error => styles.error,
30 Self::Output => styles.output,
31 Self::Response => styles.response,
32 Self::Tool => styles.tool,
33 Self::User => styles.user,
34 Self::Reasoning => styles.reasoning,
35 }
36 }
37
38 fn indent(self) -> &'static str {
39 match self {
40 Self::Response | Self::Tool | Self::Reasoning => " ",
41 _ => "",
42 }
43 }
44}
45
46pub struct AnsiRenderer {
48 writer: AutoStream<io::Stdout>,
49 buffer: String,
50 color: bool,
51 sink: Option<IocraftSink>,
52}
53
54impl AnsiRenderer {
55 pub fn stdout() -> Self {
57 let color =
58 clicolor_force() || (!no_color() && clicolor().unwrap_or_else(term_supports_color));
59 let choice = if color {
60 ColorChoice::Auto
61 } else {
62 ColorChoice::Never
63 };
64 Self {
65 writer: AutoStream::new(std::io::stdout(), choice),
66 buffer: String::new(),
67 color,
68 sink: None,
69 }
70 }
71
72 pub fn with_iocraft(handle: IocraftHandle) -> Self {
74 let mut renderer = Self::stdout();
75 renderer.sink = Some(IocraftSink::new(handle));
76 renderer
77 }
78
79 pub fn push(&mut self, text: &str) {
81 self.buffer.push_str(text);
82 }
83
84 pub fn flush(&mut self, style: MessageStyle) -> Result<()> {
86 if let Some(sink) = &mut self.sink {
87 let indent = style.indent();
88 let line = self.buffer.clone();
89 sink.write_line(style.style(), indent, &line)?;
90 self.buffer.clear();
91 return Ok(());
92 }
93 let style = style.style();
94 if self.color {
95 writeln!(self.writer, "{style}{}{Reset}", self.buffer)?;
96 } else {
97 writeln!(self.writer, "{}", self.buffer)?;
98 }
99 self.writer.flush()?;
100 transcript::append(&self.buffer);
101 self.buffer.clear();
102 Ok(())
103 }
104
105 pub fn line(&mut self, style: MessageStyle, text: &str) -> Result<()> {
107 let indent = style.indent();
108
109 if let Some(sink) = &mut self.sink {
110 sink.write_multiline(style.style(), indent, text)?;
111 return Ok(());
112 }
113
114 if text.contains('\n') {
115 let trailing_newline = text.ends_with('\n');
116 for line in text.lines() {
117 self.buffer.clear();
118 if !indent.is_empty() && !line.is_empty() {
119 self.buffer.push_str(indent);
120 }
121 self.buffer.push_str(line);
122 self.flush(style)?;
123 }
124 if trailing_newline {
125 self.buffer.clear();
126 if !indent.is_empty() {
127 self.buffer.push_str(indent);
128 }
129 self.flush(style)?;
130 }
131 Ok(())
132 } else {
133 self.buffer.clear();
134 if !indent.is_empty() && !text.is_empty() {
135 self.buffer.push_str(indent);
136 }
137 self.buffer.push_str(text);
138 self.flush(style)
139 }
140 }
141
142 pub fn inline_with_style(&mut self, style: Style, text: &str) -> Result<()> {
144 if let Some(sink) = &mut self.sink {
145 sink.write_inline(style, text);
146 return Ok(());
147 }
148 if self.color {
149 write!(self.writer, "{style}{}{Reset}", text)?;
150 } else {
151 write!(self.writer, "{}", text)?;
152 }
153 self.writer.flush()?;
154 Ok(())
155 }
156
157 pub fn line_with_style(&mut self, style: Style, text: &str) -> Result<()> {
159 if let Some(sink) = &mut self.sink {
160 sink.write_multiline(style, "", text)?;
161 return Ok(());
162 }
163 if self.color {
164 writeln!(self.writer, "{style}{}{Reset}", text)?;
165 } else {
166 writeln!(self.writer, "{}", text)?;
167 }
168 self.writer.flush()?;
169 transcript::append(text);
170 Ok(())
171 }
172
173 pub fn raw_line(&mut self, text: &str) -> Result<()> {
175 writeln!(self.writer, "{}", text)?;
176 self.writer.flush()?;
177 transcript::append(text);
178 Ok(())
179 }
180}
181
182struct IocraftSink {
183 handle: IocraftHandle,
184}
185
186impl IocraftSink {
187 fn new(handle: IocraftHandle) -> Self {
188 Self { handle }
189 }
190
191 fn style_to_segment(&self, style: Style, text: &str) -> IocraftSegment {
192 let mut text_style = convert_to_iocraft_style(style);
193 if text_style.color.is_none() {
194 let theme = theme_from_styles(&theme::active_styles());
195 text_style = text_style.merge_color(theme.foreground);
196 }
197 IocraftSegment {
198 text: text.to_string(),
199 style: text_style,
200 }
201 }
202
203 fn write_multiline(&mut self, style: Style, indent: &str, text: &str) -> Result<()> {
204 if text.is_empty() {
205 self.handle.append_line(Vec::new());
206 crate::utils::transcript::append("");
207 return Ok(());
208 }
209
210 let mut lines = text.split('\n').peekable();
211 let ends_with_newline = text.ends_with('\n');
212
213 while let Some(line) = lines.next() {
214 let mut content = String::new();
215 if !indent.is_empty() && !line.is_empty() {
216 content.push_str(indent);
217 }
218 content.push_str(line);
219 if content.is_empty() {
220 self.handle.append_line(Vec::new());
221 crate::utils::transcript::append("");
222 } else {
223 let segment = self.style_to_segment(style, &content);
224 self.handle.append_line(vec![segment]);
225 crate::utils::transcript::append(&content);
226 }
227 }
228
229 if ends_with_newline {
230 self.handle.append_line(Vec::new());
231 crate::utils::transcript::append("");
232 }
233
234 Ok(())
235 }
236
237 fn write_line(&mut self, style: Style, indent: &str, text: &str) -> Result<()> {
238 if text.is_empty() {
239 self.handle.append_line(Vec::new());
240 crate::utils::transcript::append("");
241 return Ok(());
242 }
243 let mut content = String::new();
244 if !indent.is_empty() {
245 content.push_str(indent);
246 }
247 content.push_str(text);
248 let segment = self.style_to_segment(style, &content);
249 self.handle.append_line(vec![segment]);
250 crate::utils::transcript::append(&content);
251 Ok(())
252 }
253
254 fn write_inline(&mut self, style: Style, text: &str) {
255 if text.is_empty() {
256 return;
257 }
258 let segment = self.style_to_segment(style, text);
259 self.handle.inline(segment);
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
268 fn test_styles_construct() {
269 let info = MessageStyle::Info.style();
270 assert_eq!(info, MessageStyle::Info.style());
271 let resp = MessageStyle::Response.style();
272 assert_eq!(resp, MessageStyle::Response.style());
273 let tool = MessageStyle::Tool.style();
274 assert_eq!(tool, MessageStyle::Tool.style());
275 let reasoning = MessageStyle::Reasoning.style();
276 assert_eq!(reasoning, MessageStyle::Reasoning.style());
277 }
278
279 #[test]
280 fn test_renderer_buffer() {
281 let mut r = AnsiRenderer::stdout();
282 r.push("hello");
283 assert_eq!(r.buffer, "hello");
284 }
285}