copilot-powerline 0.3.1

A fast, customizable powerline status line for GitHub Copilot CLI
use crate::config::TokensConfig;
use crate::input::ContextWindow;
use crate::theme::Palette;

pub fn format_tokens(n: Option<u64>) -> String {
    let count = match n {
        Some(v) if v > 0 => v,
        _ => return "0".to_string(),
    };

    if count >= 1_000_000 {
        format!("{:.1}M", count as f64 / 1_000_000.0)
    } else if count >= 10_000 {
        format!("{:.0}k", count as f64 / 1_000.0)
    } else if count >= 1_000 {
        format!("{:.1}k", count as f64 / 1_000.0)
    } else {
        count.to_string()
    }
}

pub fn render_tokens_segment(
    ctx: &ContextWindow,
    config: &TokensConfig,
    icon_set: crate::config::IconSet,
    palette: &Palette,
) -> Option<String> {
    if !config.enabled {
        return None;
    }

    let r = palette.reset;
    let d = palette.dim;
    let lbl = palette.label;

    let curr_tokens = ctx.current_context_tokens.unwrap_or(0);
    let max_tokens = ctx.displayed_context_limit.unwrap_or(0);
    let pct = ctx.current_context_used_percentage;

    let tok_curr_str = format_tokens(Some(curr_tokens));
    let tok_max_str = format_tokens(Some(max_tokens));

    let is_alert = curr_tokens > config.alert_threshold;
    let (alert_icon, curr_styled) = if is_alert {
        (
            config.alert_icon.as_str(),
            format!("{}{}{}", palette.tokens_alert, tok_curr_str, r),
        )
    } else {
        (
            "",
            format!("{}{}{}", palette.tokens_normal, tok_curr_str, r),
        )
    };

    let max_styled = format!("{}{}{}", d, tok_max_str, r);
    let pct_styled = if config.show_percentage {
        pct.map(|p| format!(" ({}{}%{})", d, p, r))
            .unwrap_or_default()
    } else {
        String::new()
    };

    let icon = crate::icons::tokens_icon(icon_set, config.prefix.as_deref());

    Some(format!(
        "{}{}{} {}{}/{}{}",
        lbl, icon, r, alert_icon, curr_styled, max_styled, pct_styled
    ))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::config::IconSet;

    #[test]
    fn test_format_tokens() {
        assert_eq!(format_tokens(None), "0");
        assert_eq!(format_tokens(Some(0)), "0");
        assert_eq!(format_tokens(Some(450)), "450");
        assert_eq!(format_tokens(Some(1_200)), "1.2k");
        assert_eq!(format_tokens(Some(10_000)), "10k");
        assert_eq!(format_tokens(Some(200_000)), "200k");
        assert_eq!(format_tokens(Some(1_500_000)), "1.5M");
    }

    #[test]
    fn test_render_tokens_normal() {
        let ctx = ContextWindow {
            current_context_tokens: Some(0),
            displayed_context_limit: Some(200_000),
            current_context_used_percentage: Some(0.0),
            ..Default::default()
        };
        let cfg = TokensConfig::default();
        let p = Palette::for_theme("plain");

        let rendered = render_tokens_segment(&ctx, &cfg, IconSet::Plain, &p).unwrap();
        assert_eq!(rendered, "Tokens: 0/200k (0%)");
    }

    #[test]
    fn test_render_tokens_emoji() {
        let ctx = ContextWindow {
            current_context_tokens: Some(0),
            displayed_context_limit: Some(200_000),
            current_context_used_percentage: Some(0.0),
            ..Default::default()
        };
        let cfg = TokensConfig::default();
        let p = Palette::for_theme("plain");

        let rendered = render_tokens_segment(&ctx, &cfg, IconSet::Emoji, &p).unwrap();
        assert_eq!(rendered, "🪙 0/200k (0%)");
    }

    #[test]
    fn test_render_tokens_alert() {
        let ctx = ContextWindow {
            current_context_tokens: Some(150_000),
            displayed_context_limit: Some(200_000),
            current_context_used_percentage: Some(75.0),
            ..Default::default()
        };
        let cfg = TokensConfig::default();
        let p = Palette::for_theme("plain");

        let rendered = render_tokens_segment(&ctx, &cfg, IconSet::Plain, &p).unwrap();
        assert_eq!(rendered, "Tokens: ⚠️ 150k/200k (75%)");
    }
}