1use comfy_table::{Attribute, Cell, Color};
2use std::io::IsTerminal;
3
4use crate::model::{Effort, Priority, Status};
5
6pub struct Theme {
7 pub red: &'static str,
8 pub green: &'static str,
9 pub yellow: &'static str,
10 pub blue: &'static str,
11 pub cyan: &'static str,
12 pub bold: &'static str,
13 pub bold_red: &'static str,
14 pub bold_yellow: &'static str,
15 pub reset: &'static str,
16}
17
18const ON: Theme = Theme {
19 red: "\x1b[31m",
20 green: "\x1b[32m",
21 yellow: "\x1b[33m",
22 blue: "\x1b[34m",
23 cyan: "\x1b[36m",
24 bold: "\x1b[1m",
25 bold_red: "\x1b[1;31m",
26 bold_yellow: "\x1b[1;33m",
27 reset: "\x1b[0m",
28};
29
30const OFF: Theme = Theme {
31 red: "",
32 green: "",
33 yellow: "",
34 blue: "",
35 cyan: "",
36 bold: "",
37 bold_red: "",
38 bold_yellow: "",
39 reset: "",
40};
41
42fn use_color(is_tty: bool) -> bool {
43 std::env::var_os("NO_COLOR").is_none() && is_tty
44}
45
46/// Colour theme for stdout.
47pub fn stdout_theme() -> &'static Theme {
48 if use_color(std::io::stdout().is_terminal()) {
49 &ON
50 } else {
51 &OFF
52 }
53}
54
55/// Colour theme for stderr.
56pub fn stderr_theme() -> &'static Theme {
57 if use_color(std::io::stderr().is_terminal()) {
58 &ON
59 } else {
60 &OFF
61 }
62}
63
64/// Whether stdout should use colour.
65pub fn stdout_use_color() -> bool {
66 use_color(std::io::stdout().is_terminal())
67}
68
69/// A table cell with bold text.
70pub fn cell_bold(text: impl ToString, use_color: bool) -> Cell {
71 let cell = Cell::new(text);
72 if use_color {
73 cell.add_attribute(Attribute::Bold)
74 } else {
75 cell
76 }
77}
78
79/// A table cell with a coloured foreground.
80pub fn cell_fg(text: impl ToString, color: Color, use_color: bool) -> Cell {
81 let cell = Cell::new(text);
82 if use_color {
83 cell.fg(color)
84 } else {
85 cell
86 }
87}
88
89impl Theme {
90 /// ANSI escape for a task status.
91 pub fn status(&self, s: Status) -> &str {
92 match s {
93 Status::Open => self.green,
94 Status::InProgress => self.bold_yellow,
95 Status::Closed => "",
96 }
97 }
98
99 /// ANSI escape for a priority level.
100 pub fn priority(&self, p: Priority) -> &str {
101 match p {
102 Priority::High => self.bold_red,
103 Priority::Medium => "",
104 Priority::Low => self.cyan,
105 }
106 }
107
108 /// ANSI escape for an effort level.
109 pub fn effort(&self, e: Effort) -> &str {
110 match e {
111 Effort::High => self.bold_red,
112 Effort::Medium => "",
113 Effort::Low => self.cyan,
114 }
115 }
116}
117
118/// A table cell styled for a task status.
119pub fn cell_status(text: impl ToString, s: Status, use_color: bool) -> Cell {
120 let cell = Cell::new(text);
121 if !use_color {
122 return cell;
123 }
124 match s {
125 Status::Open => cell.fg(Color::Green),
126 Status::InProgress => cell.fg(Color::Yellow).add_attribute(Attribute::Bold),
127 Status::Closed => cell,
128 }
129}
130
131/// A table cell styled for a priority level.
132pub fn cell_priority(text: impl ToString, p: Priority, use_color: bool) -> Cell {
133 let cell = Cell::new(text);
134 if !use_color {
135 return cell;
136 }
137 match p {
138 Priority::High => cell.fg(Color::Red).add_attribute(Attribute::Bold),
139 Priority::Medium => cell,
140 Priority::Low => cell.fg(Color::Cyan),
141 }
142}
143
144/// A table cell styled for an effort level.
145pub fn cell_effort(text: impl ToString, e: Effort, use_color: bool) -> Cell {
146 let cell = Cell::new(text);
147 if !use_color {
148 return cell;
149 }
150 match e {
151 Effort::High => cell.fg(Color::Red).add_attribute(Attribute::Bold),
152 Effort::Medium => cell,
153 Effort::Low => cell.fg(Color::Cyan),
154 }
155}