soffit 0.1.1

Customizable statusline manager for Claude Code with desktop editor and custom widget support
use regex::Regex;
use std::sync::LazyLock;

static ANSI_RE: LazyLock<Regex> =
    LazyLock::new(|| Regex::new(r"\x1b\[[^m]*m|\x1b\]8;;[^\x07]*\x07").unwrap());

pub fn visible_len(s: &str) -> usize {
    ANSI_RE.replace_all(s, "").chars().count()
}

#[cfg(test)]
pub fn dot_bar(pct: u32, width: usize) -> (String, String) {
    use crate::theme::{ansi, ThemePalette, RESET};
    let p = ThemePalette::default();
    let pct = pct.min(100);
    let filled = ((pct as usize * width + 50) / 100).min(width);
    let empty = width - filled;
    let col = if pct >= 80 {
        ansi(p.danger)
    } else if pct >= 50 {
        ansi(p.warning)
    } else {
        ansi(p.success)
    };
    let muted = ansi(p.muted);
    let bar = format!(
        "{col}{filled}{muted}{empty}{reset}",
        filled = "".repeat(filled),
        empty = "".repeat(empty),
        reset = RESET,
    );
    (bar, col)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::theme::{ansi, ThemePalette};

    #[test]
    fn dot_bar_zero() {
        let p = ThemePalette::default();
        let (bar, col) = dot_bar(0, 10);
        assert_eq!(col, ansi(p.success));
        assert!(bar.contains(&"".repeat(10)));
        assert!(!bar.contains(''));
    }

    #[test]
    fn dot_bar_50() {
        let p = ThemePalette::default();
        let (bar, col) = dot_bar(50, 10);
        assert_eq!(col, ansi(p.warning));
        assert!(bar.contains(&"".repeat(5)));
        assert!(bar.contains(&"".repeat(5)));
    }

    #[test]
    fn dot_bar_80() {
        let p = ThemePalette::default();
        let (bar, col) = dot_bar(80, 10);
        assert_eq!(col, ansi(p.danger));
        assert!(bar.contains(&"".repeat(8)));
        assert!(bar.contains(&"".repeat(2)));
    }

    #[test]
    fn dot_bar_100() {
        let p = ThemePalette::default();
        let (bar, col) = dot_bar(100, 10);
        assert_eq!(col, ansi(p.danger));
        assert!(bar.contains(&"".repeat(10)));
        assert!(!bar.contains(''));
    }

    #[test]
    fn test_visible_len_plain() {
        assert_eq!(visible_len("hello"), 5);
    }

    #[test]
    fn test_visible_len_ansi() {
        use crate::theme::{ansi, ThemePalette, RESET};
        let p = ThemePalette::default();
        let s = format!("{}hello{}", ansi(p.success), RESET);
        assert_eq!(visible_len(&s), 5);
    }

    #[test]
    fn test_visible_len_osc8() {
        let s = "\x1b]8;;https://example.com\x07click\x1b]8;;\x07";
        assert_eq!(visible_len(s), 5);
    }
}