use std::io::IsTerminal;
use crate::cli::AskArgs;
#[derive(Debug, Clone, Copy)]
pub struct Style {
pub render_markdown: bool,
pub color: bool,
pub spinner: bool,
}
impl Style {
pub fn detect(args: &AskArgs) -> Self {
let stdout_tty = std::io::stdout().is_terminal();
let stderr_tty = std::io::stderr().is_terminal();
let no_color = std::env::var_os("NO_COLOR").is_some();
let plain = args.plain;
let render_markdown =
!plain && stdout_tty && !args.json && !args.stream && args.code.is_none();
let color = !plain && stdout_tty && !no_color;
let spinner = !plain && stderr_tty && !args.stream;
Self {
render_markdown,
color,
spinner,
}
}
pub fn plain() -> Self {
Self {
render_markdown: false,
color: false,
spinner: false,
}
}
pub fn detect_for_error() -> Self {
let stderr_tty = std::io::stderr().is_terminal();
let no_color = std::env::var_os("NO_COLOR").is_some();
Self {
render_markdown: false,
color: stderr_tty && !no_color,
spinner: false,
}
}
pub fn detect_for_subcommand() -> Self {
let stdout_tty = std::io::stdout().is_terminal();
let no_color = std::env::var_os("NO_COLOR").is_some();
Self {
render_markdown: stdout_tty && !no_color,
color: stdout_tty && !no_color,
spinner: false,
}
}
}
pub fn print_body(text: &str, style: &Style) {
if !style.render_markdown {
println!("{text}");
return;
}
let skin = build_skin(style.color);
let width = terminal_width().saturating_sub(3).max(20);
let rendered = skin.text(text, Some(width));
let rendered_string = format!("{rendered}");
let trimmed = rendered_string.trim_end_matches('\n');
for line in trimmed.split('\n') {
if line.is_empty() {
println!();
} else {
println!(" {line}");
}
}
}
fn terminal_width() -> usize {
termimad::crossterm::terminal::size()
.map(|(w, _)| w as usize)
.unwrap_or(80)
}
fn build_skin(color: bool) -> termimad::MadSkin {
if color {
termimad::MadSkin::default()
} else {
termimad::MadSkin::no_style()
}
}
pub fn print_meta(line: &str, style: &Style) {
if style.color {
eprintln!("\x1b[2m{line}\x1b[0m");
} else {
eprintln!("{line}");
}
}
pub fn print_meta_blank() {
eprintln!();
}
pub fn print_warning(message: &str, style: &Style) {
if style.color {
eprintln!("\x1b[1;33m◆ warning:\x1b[0m {message}");
} else {
eprintln!("warning: {message}");
}
}
pub fn print_error(message: &str, style: &Style) {
if style.color {
eprintln!("\x1b[1;31m◆ error:\x1b[0m {message}");
} else {
eprintln!("error: {message}");
}
}
pub fn print_thinking_delta(text: &str, style: &Style) {
use std::io::Write;
let stderr = std::io::stderr();
let mut lock = stderr.lock();
if style.color {
let _ = write!(lock, "\x1b[2m{text}\x1b[0m");
} else {
let _ = write!(lock, "{text}");
}
let _ = lock.flush();
}
pub fn print_tool_call(line: &str, style: &Style) {
if style.color {
eprintln!(" \x1b[2m▸ {line}\x1b[0m");
} else {
eprintln!(" > {line}");
}
}
pub fn spinner() -> indicatif::ProgressBar {
let pb = indicatif::ProgressBar::new_spinner();
pb.enable_steady_tick(std::time::Duration::from_millis(80));
pb.set_style(
indicatif::ProgressStyle::with_template("{spinner} {elapsed}").expect("static template"),
);
pb
}