use crate::ui::theme;
use crate::utils::transcript;
use anstream::{AutoStream, ColorChoice};
use anstyle::{Reset, Style};
use anstyle_query::{clicolor, clicolor_force, no_color, term_supports_color};
use anyhow::Result;
use std::io::{self, Write};
#[derive(Clone, Copy)]
pub enum MessageStyle {
Info,
Error,
Output,
Response,
Tool,
User,
}
impl MessageStyle {
fn style(self) -> Style {
let styles = theme::active_styles();
match self {
Self::Info => styles.info,
Self::Error => styles.error,
Self::Output => styles.output,
Self::Response => styles.response,
Self::Tool => styles.tool,
Self::User => styles.user,
}
}
fn indent(self) -> &'static str {
match self {
Self::Response | Self::Tool => " ",
_ => "",
}
}
}
pub struct AnsiRenderer {
writer: AutoStream<io::Stdout>,
buffer: String,
color: bool,
}
impl AnsiRenderer {
pub fn stdout() -> Self {
let color =
clicolor_force() || (!no_color() && clicolor().unwrap_or_else(term_supports_color));
let choice = if color {
ColorChoice::Auto
} else {
ColorChoice::Never
};
Self {
writer: AutoStream::new(std::io::stdout(), choice),
buffer: String::new(),
color,
}
}
pub fn push(&mut self, text: &str) {
self.buffer.push_str(text);
}
pub fn flush(&mut self, style: MessageStyle) -> Result<()> {
let style = style.style();
if self.color {
writeln!(self.writer, "{style}{}{Reset}", self.buffer)?;
} else {
writeln!(self.writer, "{}", self.buffer)?;
}
self.writer.flush()?;
transcript::append(&self.buffer);
self.buffer.clear();
Ok(())
}
pub fn line(&mut self, style: MessageStyle, text: &str) -> Result<()> {
let indent = style.indent();
if text.contains('\n') {
let trailing_newline = text.ends_with('\n');
for line in text.lines() {
self.buffer.clear();
if !indent.is_empty() && !line.is_empty() {
self.buffer.push_str(indent);
}
self.buffer.push_str(line);
self.flush(style)?;
}
if trailing_newline {
self.buffer.clear();
if !indent.is_empty() {
self.buffer.push_str(indent);
}
self.flush(style)?;
}
Ok(())
} else {
self.buffer.clear();
if !indent.is_empty() && !text.is_empty() {
self.buffer.push_str(indent);
}
self.buffer.push_str(text);
self.flush(style)
}
}
pub fn inline_with_style(&mut self, style: Style, text: &str) -> Result<()> {
if self.color {
write!(self.writer, "{style}{}{Reset}", text)?;
} else {
write!(self.writer, "{}", text)?;
}
self.writer.flush()?;
Ok(())
}
pub fn line_with_style(&mut self, style: Style, text: &str) -> Result<()> {
if self.color {
writeln!(self.writer, "{style}{}{Reset}", text)?;
} else {
writeln!(self.writer, "{}", text)?;
}
self.writer.flush()?;
transcript::append(text);
Ok(())
}
pub fn raw_line(&mut self, text: &str) -> Result<()> {
writeln!(self.writer, "{}", text)?;
self.writer.flush()?;
transcript::append(text);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_styles_construct() {
let info = MessageStyle::Info.style();
assert_eq!(info, MessageStyle::Info.style());
let resp = MessageStyle::Response.style();
assert_eq!(resp, MessageStyle::Response.style());
let tool = MessageStyle::Tool.style();
assert_eq!(tool, MessageStyle::Tool.style());
}
#[test]
fn test_renderer_buffer() {
let mut r = AnsiRenderer::stdout();
r.push("hello");
assert_eq!(r.buffer, "hello");
}
}