use ratatui::{
buffer::Buffer,
layout::Rect,
style::Style,
text::{Line, Span},
widgets::Widget,
};
use super::spinner::SpinnerState;
use crate::formatters::style_tokens;
#[derive(Debug, Clone)]
pub struct TaskProgress {
pub description: String,
pub elapsed_secs: u64,
pub token_display: Option<String>,
pub interrupted: bool,
pub started_at: std::time::Instant,
}
pub struct TaskProgressWidget<'a> {
progress: &'a TaskProgress,
spinner_char: char,
}
impl<'a> TaskProgressWidget<'a> {
pub fn new(progress: &'a TaskProgress, spinner_state: &SpinnerState) -> Self {
Self {
progress,
spinner_char: spinner_state.current(),
}
}
}
impl Widget for TaskProgressWidget<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
if area.height == 0 || area.width == 0 {
return;
}
let mut spans: Vec<Span> = Vec::new();
spans.push(Span::styled(
format!("{} ", self.spinner_char),
Style::default().fg(style_tokens::BLUE_BRIGHT),
));
spans.push(Span::styled(
format!("{}... ", self.progress.description),
Style::default().fg(style_tokens::SUBTLE),
));
let mut info_parts = Vec::new();
info_parts.push("esc to interrupt".to_string());
info_parts.push(format!("{}s", self.progress.elapsed_secs));
if let Some(ref token_display) = self.progress.token_display {
info_parts.push(token_display.clone());
}
let info_str = info_parts.join(" \u{00b7} "); spans.push(Span::styled(
info_str,
Style::default().fg(style_tokens::SUBTLE),
));
let line = Line::from(spans);
buf.set_line(area.left(), area.top(), &line, area.width);
}
}
pub fn format_final_status(progress: &TaskProgress) -> String {
let symbol = if progress.interrupted {
"\u{23f9}" } else {
"\u{23fa}" };
let status = if progress.interrupted {
"interrupted"
} else {
"completed"
};
let mut parts = vec![format!("{status} in {}s", progress.elapsed_secs)];
if let Some(ref token_display) = progress.token_display {
parts.push(token_display.clone());
}
format!("{symbol} {}", parts.join(", "))
}
#[cfg(test)]
#[path = "progress_tests.rs"]
mod tests;