1use anstyle::{AnsiColor, Style};
6
7use crate::providers::ReviewState;
8
9pub const BRANCH: Style = AnsiColor::Cyan.on_default();
11pub const CURRENT: Style = AnsiColor::Green.on_default().bold();
13pub const DIM: Style = Style::new().dimmed();
15pub const HINT: Style = AnsiColor::Cyan.on_default();
17pub const WARN: Style = AnsiColor::Yellow.on_default();
19pub const ERROR: Style = AnsiColor::Red.on_default().bold();
21
22pub const OPEN: Style = AnsiColor::Green.on_default();
25pub const MERGED: Style = AnsiColor::Magenta.on_default();
26pub const CLOSED: Style = AnsiColor::Red.on_default();
27
28pub fn paint(style: Style, text: &str) -> String {
29 format!("{style}{text}{style:#}")
30}
31
32pub fn branch(name: &str) -> String {
34 paint(BRANCH, name)
35}
36
37pub fn dim(text: &str) -> String {
39 paint(DIM, text)
40}
41
42pub fn success(text: &str) -> String {
44 paint(OPEN, text)
45}
46
47pub fn warn(text: &str) -> String {
49 paint(WARN, text)
50}
51
52pub fn hint_prefix() -> String {
54 paint(HINT, "hint:")
55}
56
57pub fn error_prefix() -> String {
59 paint(ERROR, "error:")
60}
61
62pub fn state(state: &ReviewState) -> String {
64 let style = match state {
65 ReviewState::Open => OPEN,
66 ReviewState::Merged => MERGED,
67 ReviewState::Closed => CLOSED,
68 ReviewState::Unknown(_) => DIM,
69 };
70 paint(style, &state.to_string())
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn paint_wraps_text_in_escape_codes() {
79 let painted = paint(BRANCH, "feature/a");
80 assert!(painted.contains("feature/a"));
81 assert!(painted.starts_with('\u{1b}'));
82 assert!(painted.ends_with('m'));
83 }
84
85 #[test]
86 fn state_uses_the_ledger_palette() {
87 assert!(state(&ReviewState::Open).contains("open"));
88 assert!(state(&ReviewState::Merged).contains("merged"));
89 assert!(state(&ReviewState::Closed).contains("closed"));
90 assert_ne!(
91 state(&ReviewState::Open),
92 state(&ReviewState::Merged),
93 "states should not share a style"
94 );
95 }
96}