use std::time::{Duration, Instant};
use super::{Row, State};
use crate::ui::{fit_cell, identity_style, paint, AnsiColor, Style};
pub const SPINNER: [&str; 10] = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
const DONE_MARK: &str = "✔";
const PENDING_MARK: &str = "⠿";
const KIND_WIDTH: usize = 9;
const TIME_WIDTH: usize = 6;
fn marker(row: &Row, frame: usize) -> (&'static str, Style) {
match row.state {
State::Done(_) => (
DONE_MARK,
Style::new().fg_color(Some(AnsiColor::Green.into())),
),
State::Working(_) => (SPINNER[frame % SPINNER.len()], Style::new()),
State::Pending => (PENDING_MARK, Style::new().dimmed()),
}
}
fn verb(row: &Row) -> &str {
match &row.state {
State::Done(v) | State::Working(v) => v,
State::Pending => "Pending",
}
}
pub fn format_elapsed(d: Duration) -> String {
let secs = d.as_secs_f64();
if secs < 60.0 {
format!("{secs:.1}s")
} else {
format!("{}m{:02}s", d.as_secs() / 60, d.as_secs() % 60)
}
}
pub fn render(row: &Row, name_width: usize, frame: usize, now: Instant, width: usize) -> String {
let (mark, mark_style) = marker(row, frame);
let time = row.duration(now).map(format_elapsed).unwrap_or_default();
let plain = format!(
" {mark} {:<KIND_WIDTH$} {} {:<12} {time:>TIME_WIDTH$}",
row.kind.noun(),
fit_cell(&row.name, name_width),
verb(row),
);
let plain = fit_cell(plain.trim_end(), 0);
let plain = if width > 0 && plain.chars().count() > width {
fit_cell(&plain, width)
} else {
plain
};
colourise(&plain, row, mark, mark_style)
}
fn colourise(plain: &str, row: &Row, mark: &str, mark_style: Style) -> String {
let mut out = plain.to_string();
if let Some(pos) = out.find(mark) {
let styled = paint(mark_style, mark, true);
out.replace_range(pos..pos + mark.len(), &styled);
}
if let Some(pos) = out.find(&row.name) {
let styled = paint(identity_style(&row.name), &row.name, true);
out.replace_range(pos..pos + row.name.len(), &styled);
}
out
}
pub fn summary(done: usize, total: usize) -> String {
format!("[+] Running {done}/{total}")
}
#[cfg(test)]
#[path = "row_tests.rs"]
mod tests;