Skip to main content

git_stk/
style.rs

1//! Semantic terminal styles. Styled lines must be printed through
2//! `anstream::println!`/`eprintln!`, which strip color for pipes, NO_COLOR,
3//! and consoles that cannot render it.
4
5use anstyle::{AnsiColor, Style};
6
7use crate::providers::ReviewState;
8
9/// Branch names.
10pub const BRANCH: Style = AnsiColor::Cyan.on_default();
11/// The branch you are standing on.
12pub const CURRENT: Style = AnsiColor::Green.on_default().bold();
13/// Secondary detail: URLs, the trunk tag, counts.
14pub const DIM: Style = Style::new().dimmed();
15/// The `hint:` prefix.
16pub const HINT: Style = AnsiColor::Cyan.on_default();
17/// The `warning:` prefix.
18pub const WARN: Style = AnsiColor::Yellow.on_default();
19/// The `error:` prefix.
20pub const ERROR: Style = AnsiColor::Red.on_default().bold();
21
22/// Review states, matching the ledger emoji: green open, purple merged,
23/// red closed.
24pub const OPEN: Style = AnsiColor::Green.on_default();
25pub const MERGED: Style = AnsiColor::Magenta.on_default();
26pub const CLOSED: Style = AnsiColor::Red.on_default();
27
28/// Faded green/red for a branch's added/removed line counts, like a diff.
29pub const ADDED: Style = AnsiColor::Green.on_default().dimmed();
30pub const REMOVED: Style = AnsiColor::Red.on_default().dimmed();
31
32pub fn paint(style: Style, text: &str) -> String {
33    format!("{style}{text}{style:#}")
34}
35
36/// A branch name in the branch color.
37pub fn branch(name: &str) -> String {
38    paint(BRANCH, name)
39}
40
41/// Secondary detail: ids, urls, skip lines, previews.
42pub fn dim(text: &str) -> String {
43    paint(DIM, text)
44}
45
46/// Completion lines ("... complete", "merged ...").
47pub fn success(text: &str) -> String {
48    paint(OPEN, text)
49}
50
51/// Notable-but-not-fatal lines.
52pub fn warn(text: &str) -> String {
53    paint(WARN, text)
54}
55
56/// The shared `hint:` prefix.
57pub fn hint_prefix() -> String {
58    paint(HINT, "hint:")
59}
60
61/// The shared `error:` prefix.
62pub fn error_prefix() -> String {
63    paint(ERROR, "error:")
64}
65
66/// A review state in its color.
67pub fn state(state: &ReviewState) -> String {
68    let style = match state {
69        ReviewState::Open => OPEN,
70        ReviewState::Merged => MERGED,
71        ReviewState::Closed => CLOSED,
72        ReviewState::Unknown(_) => DIM,
73    };
74    paint(style, &state.to_string())
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn paint_wraps_text_in_escape_codes() {
83        let painted = paint(BRANCH, "feature/a");
84        assert!(painted.contains("feature/a"));
85        assert!(painted.starts_with('\u{1b}'));
86        assert!(painted.ends_with('m'));
87    }
88
89    #[test]
90    fn state_uses_the_ledger_palette() {
91        assert!(state(&ReviewState::Open).contains("open"));
92        assert!(state(&ReviewState::Merged).contains("merged"));
93        assert!(state(&ReviewState::Closed).contains("closed"));
94        assert_ne!(
95            state(&ReviewState::Open),
96            state(&ReviewState::Merged),
97            "states should not share a style"
98        );
99    }
100}