use crossterm::{
cursor,
terminal::{self, ClearType},
ExecutableCommand, QueueableCommand,
};
use mixtape_core::Agent;
use std::io::{stdout, Write};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StatusColors {
pub fg: &'static str,
pub bg: &'static str,
}
impl StatusColors {
pub const CRITICAL: Self = Self {
fg: "\x1b[31m",
bg: "\x1b[48;5;52m",
};
pub const WARNING: Self = Self {
fg: "\x1b[33m",
bg: "\x1b[48;5;58m",
};
pub const NORMAL: Self = Self {
fg: "\x1b[37m",
bg: "\x1b[48;5;236m",
};
}
pub fn select_status_colors(usage_percentage: f32) -> StatusColors {
if usage_percentage >= 0.9 {
StatusColors::CRITICAL
} else if usage_percentage >= 0.75 {
StatusColors::WARNING
} else {
StatusColors::NORMAL
}
}
pub fn update_status_line(agent: &Agent) {
let Ok((width, height)) = terminal::size() else {
return; };
let mut stdout = stdout();
let usage = agent.get_context_usage();
let tokens_k = usage.context_tokens as f32 / 1000.0;
let percentage = (usage.usage_percentage * 100.0) as u32;
let colors = select_status_colors(usage.usage_percentage);
let status_text = format!(
" Context: {:.1}k / {}k ({:>3}%) ยท {} messages",
tokens_k,
usage.max_context_tokens / 1000,
percentage,
usage.total_messages
);
let _ = stdout.queue(cursor::SavePosition);
let _ = stdout.queue(cursor::MoveTo(0, height - 1));
let _ = write!(stdout, "{}{}", colors.bg, colors.fg);
let _ = write!(stdout, "{}", status_text);
let padding = (width as usize).saturating_sub(status_text.len());
if padding > 0 {
let _ = write!(stdout, "{}", " ".repeat(padding));
}
let _ = write!(stdout, "\x1b[0m");
let _ = stdout.queue(cursor::RestorePosition);
let _ = stdout.flush();
}
pub fn clear_status_line() {
if let Ok((_, height)) = terminal::size() {
let mut stdout = stdout();
let _ = stdout.queue(cursor::SavePosition);
let _ = stdout.queue(cursor::MoveTo(0, height - 1));
let _ = stdout.execute(terminal::Clear(ClearType::CurrentLine));
let _ = stdout.queue(cursor::RestorePosition);
let _ = stdout.flush();
}
}
#[cfg(test)]
mod tests {
use super::*;
mod status_colors_tests {
use super::*;
#[test]
fn critical_colors_are_red() {
assert_eq!(StatusColors::CRITICAL.fg, "\x1b[31m");
assert_eq!(StatusColors::CRITICAL.bg, "\x1b[48;5;52m");
}
#[test]
fn warning_colors_are_yellow() {
assert_eq!(StatusColors::WARNING.fg, "\x1b[33m");
assert_eq!(StatusColors::WARNING.bg, "\x1b[48;5;58m");
}
#[test]
fn normal_colors_are_white_gray() {
assert_eq!(StatusColors::NORMAL.fg, "\x1b[37m");
assert_eq!(StatusColors::NORMAL.bg, "\x1b[48;5;236m");
}
}
mod select_status_colors_tests {
use super::*;
#[test]
fn critical_at_90_percent() {
assert_eq!(select_status_colors(0.9), StatusColors::CRITICAL);
assert_eq!(select_status_colors(0.95), StatusColors::CRITICAL);
assert_eq!(select_status_colors(1.0), StatusColors::CRITICAL);
}
#[test]
fn warning_at_75_percent() {
assert_eq!(select_status_colors(0.75), StatusColors::WARNING);
assert_eq!(select_status_colors(0.80), StatusColors::WARNING);
assert_eq!(select_status_colors(0.89), StatusColors::WARNING);
}
#[test]
fn normal_below_75_percent() {
assert_eq!(select_status_colors(0.0), StatusColors::NORMAL);
assert_eq!(select_status_colors(0.5), StatusColors::NORMAL);
assert_eq!(select_status_colors(0.74), StatusColors::NORMAL);
}
#[test]
fn boundary_at_exactly_75() {
assert_eq!(select_status_colors(0.75), StatusColors::WARNING);
}
#[test]
fn boundary_just_below_75() {
assert_eq!(select_status_colors(0.749999), StatusColors::NORMAL);
}
#[test]
fn boundary_at_exactly_90() {
assert_eq!(select_status_colors(0.9), StatusColors::CRITICAL);
}
#[test]
fn boundary_just_below_90() {
assert_eq!(select_status_colors(0.899999), StatusColors::WARNING);
}
}
}