use crate::model::{IssueType, Priority, Status};
use rich_rust::{Color, Style};
fn color(name: &str) -> Color {
Color::parse(name).unwrap_or_else(|_| {
debug_assert!(false, "Invalid color name: {name}");
Color::default_color()
})
}
#[derive(Debug, Clone)]
pub struct Theme {
pub status_open: Style,
pub status_in_progress: Style,
pub status_blocked: Style,
pub status_deferred: Style,
pub status_closed: Style,
pub status_pinned: Style,
pub priority_critical: Style,
pub priority_high: Style,
pub priority_medium: Style,
pub priority_low: Style,
pub type_bug: Style,
pub type_feature: Style,
pub type_task: Style,
pub type_epic: Style,
pub type_docs: Style,
pub type_chore: Style,
pub header: Style,
pub border: Style,
pub muted: Style,
pub emphasis: Style,
pub success: Style,
pub warning: Style,
pub error: Style,
pub issue_id: Style,
pub label: Style,
pub dependency: Style,
}
impl Default for Theme {
fn default() -> Self {
Self::new()
}
}
impl Theme {
#[must_use]
pub fn new() -> Self {
Self {
status_open: Style::new().color(color("green")),
status_in_progress: Style::new().color(color("yellow")),
status_blocked: Style::new().color(color("red")),
status_deferred: Style::new().color(color("blue")),
status_closed: Style::new().color(color("bright_black")),
status_pinned: Style::new().color(color("magenta")).bold(),
priority_critical: Style::new().color(color("red")).bold(),
priority_high: Style::new().color(color("red")),
priority_medium: Style::new().color(color("yellow")),
priority_low: Style::new().color(color("bright_black")),
type_bug: Style::new().color(color("red")),
type_feature: Style::new().color(color("cyan")),
type_task: Style::new(),
type_epic: Style::new().color(color("magenta")).bold(),
type_docs: Style::new().color(color("blue")),
type_chore: Style::new().color(color("bright_black")),
header: Style::new().bold(),
border: Style::new().color(color("bright_black")),
muted: Style::new().color(color("bright_black")),
emphasis: Style::new().bold(),
success: Style::new().color(color("green")),
warning: Style::new().color(color("yellow")),
error: Style::new().color(color("red")).bold(),
issue_id: Style::new().color(color("cyan")),
label: Style::new().color(color("blue")),
dependency: Style::new().color(color("magenta")),
}
}
#[must_use]
pub fn status_style(&self, status: &Status) -> &Style {
match status {
Status::Open => &self.status_open,
Status::InProgress => &self.status_in_progress,
Status::Blocked => &self.status_blocked,
Status::Deferred | Status::Draft => &self.status_deferred,
Status::Closed | Status::Tombstone => &self.status_closed,
Status::Pinned => &self.status_pinned,
Status::Custom(_) => &self.muted,
}
}
#[must_use]
pub fn priority_style(&self, priority: &Priority) -> &Style {
match priority.0 {
0 => &self.priority_critical,
1 => &self.priority_high,
2 => &self.priority_medium,
3 | 4 => &self.priority_low,
_ => &self.muted,
}
}
#[must_use]
pub fn type_style(&self, issue_type: &IssueType) -> &Style {
match issue_type {
IssueType::Bug => &self.type_bug,
IssueType::Feature => &self.type_feature,
IssueType::Task | IssueType::Custom(_) => &self.type_task,
IssueType::Epic => &self.type_epic,
IssueType::Docs | IssueType::Question => &self.type_docs,
IssueType::Chore => &self.type_chore,
}
}
#[must_use]
pub fn dark() -> Self {
Self {
status_open: Style::new().color(color("bright_green")),
status_in_progress: Style::new().color(color("bright_yellow")),
status_blocked: Style::new().color(color("bright_red")),
status_deferred: Style::new().color(color("bright_blue")),
status_closed: Style::new().color(color("bright_black")),
status_pinned: Style::new().color(color("bright_magenta")).bold(),
priority_critical: Style::new().color(color("bright_red")).bold(),
priority_high: Style::new().color(color("bright_red")),
priority_medium: Style::new().color(color("bright_yellow")),
priority_low: Style::new().color(color("bright_black")),
border: Style::new().color(color("white")),
muted: Style::new().color(color("bright_black")),
..Self::new()
}
}
#[must_use]
pub fn minimal() -> Self {
let dim = Style::new().dim();
let bold = Style::new().bold();
let normal = Style::new();
Self {
status_open: normal.clone(),
status_in_progress: bold.clone(),
status_blocked: bold.clone(),
status_deferred: dim.clone(),
status_closed: dim.clone(),
status_pinned: bold.clone(),
priority_critical: bold.clone(),
priority_high: bold.clone(),
priority_medium: normal.clone(),
priority_low: dim.clone(),
type_bug: bold.clone(),
type_feature: normal.clone(),
type_task: normal.clone(),
type_epic: bold.clone(),
type_docs: normal.clone(),
type_chore: dim.clone(),
header: bold.clone(),
border: dim.clone(),
muted: dim,
emphasis: bold,
success: normal.clone(),
warning: normal.clone(),
error: normal.clone(),
issue_id: normal.clone(),
label: normal.clone(),
dependency: normal,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_theme() {
let theme = Theme::default();
assert_eq!(theme.status_style(&Status::Open), &theme.status_open);
assert_eq!(
theme.status_style(&Status::InProgress),
&theme.status_in_progress
);
assert_eq!(theme.status_style(&Status::Blocked), &theme.status_blocked);
assert_eq!(
theme.status_style(&Status::Deferred),
&theme.status_deferred
);
assert_eq!(theme.status_style(&Status::Closed), &theme.status_closed);
assert_eq!(
theme.status_style(&Status::Custom("waiting".to_string())),
&theme.muted
);
}
#[test]
fn test_priority_styles() {
let theme = Theme::default();
assert_eq!(
theme.priority_style(&Priority::CRITICAL),
&theme.priority_critical
);
assert_eq!(theme.priority_style(&Priority::HIGH), &theme.priority_high);
assert_eq!(
theme.priority_style(&Priority::MEDIUM),
&theme.priority_medium
);
assert_eq!(theme.priority_style(&Priority::LOW), &theme.priority_low);
assert_eq!(theme.priority_style(&Priority(99)), &theme.muted);
}
#[test]
fn test_type_styles() {
let theme = Theme::default();
assert_eq!(theme.type_style(&IssueType::Bug), &theme.type_bug);
assert_eq!(theme.type_style(&IssueType::Feature), &theme.type_feature);
assert_eq!(theme.type_style(&IssueType::Task), &theme.type_task);
assert_eq!(
theme.type_style(&IssueType::Custom("ops".to_string())),
&theme.type_task
);
assert_eq!(theme.type_style(&IssueType::Epic), &theme.type_epic);
assert_eq!(theme.type_style(&IssueType::Docs), &theme.type_docs);
assert_eq!(theme.type_style(&IssueType::Chore), &theme.type_chore);
}
#[test]
fn test_dark_theme() {
let theme = Theme::dark();
let default_theme = Theme::default();
assert_eq!(theme.status_style(&Status::Open), &theme.status_open);
assert_ne!(
theme.status_style(&Status::Open),
default_theme.status_style(&Status::Open)
);
assert_eq!(
theme.priority_style(&Priority::CRITICAL),
&theme.priority_critical
);
}
#[test]
fn test_minimal_theme() {
let theme = Theme::minimal();
assert_eq!(theme.status_style(&Status::Open), &Style::new());
assert_eq!(theme.status_style(&Status::Blocked), &Style::new().bold());
assert_eq!(theme.status_style(&Status::Closed), &Style::new().dim());
assert_eq!(theme.type_style(&IssueType::Bug), &Style::new().bold());
assert_eq!(theme.type_style(&IssueType::Chore), &Style::new().dim());
}
}