radicle_term/ansi/
paint.rs1use std::io::IsTerminal as _;
2use std::sync::atomic::{AtomicBool, AtomicI32};
3use std::sync::LazyLock;
4use std::{fmt, sync};
5
6use super::color::Color;
7use super::style::{Property, Style};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10#[repr(i32)]
11pub enum TerminalFile {
12 Stdout = 1,
13 Stderr = 2,
14}
15
16impl TryFrom<i32> for TerminalFile {
17 type Error = ();
18
19 fn try_from(value: i32) -> Result<Self, Self::Error> {
20 match value {
21 1 => Ok(TerminalFile::Stdout),
22 2 => Ok(TerminalFile::Stderr),
23 _ => Err(()),
24 }
25 }
26}
27
28impl TerminalFile {
29 fn is_terminal(&self) -> bool {
30 match self {
31 TerminalFile::Stdout => std::io::stdout().is_terminal(),
32 TerminalFile::Stderr => std::io::stderr().is_terminal(),
33 }
34 }
35}
36
37static TERMINAL: AtomicI32 = AtomicI32::new(TerminalFile::Stdout as i32);
39static ENABLED: AtomicBool = AtomicBool::new(true);
41static FORCED: AtomicBool = AtomicBool::new(false);
43
44#[derive(Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Copy, Clone)]
46pub struct Paint<T> {
47 pub item: T,
48 pub style: Style,
49}
50
51impl Paint<&str> {
52 pub fn content(&self) -> &str {
54 self.item
55 }
56}
57
58impl Paint<String> {
59 pub fn content(&self) -> &str {
61 self.item.as_str()
62 }
63}
64
65impl<T> From<T> for Paint<T> {
66 fn from(value: T) -> Self {
67 Self::new(value)
68 }
69}
70
71impl From<&str> for Paint<String> {
72 fn from(item: &str) -> Self {
73 Self::new(item.to_string())
74 }
75}
76
77impl From<Paint<&str>> for Paint<String> {
78 fn from(paint: Paint<&str>) -> Self {
79 Self {
80 item: paint.item.to_owned(),
81 style: paint.style,
82 }
83 }
84}
85
86impl From<Paint<String>> for String {
87 fn from(value: Paint<String>) -> Self {
88 value.item
89 }
90}
91
92impl<T> Paint<T> {
93 #[inline]
96 pub const fn new(item: T) -> Paint<T> {
97 Paint {
98 item,
99 style: Style {
100 foreground: Color::Unset,
101 background: Color::Unset,
102 properties: Property::new(),
103 wrap: false,
104 },
105 }
106 }
107
108 #[inline]
121 pub const fn wrapping(item: T) -> Paint<T> {
122 Paint::new(item).wrap()
123 }
124
125 #[inline]
128 pub const fn rgb(r: u8, g: u8, b: u8, item: T) -> Paint<T> {
129 Paint::new(item).fg(Color::RGB(r, g, b))
130 }
131
132 #[inline]
135 pub const fn fixed(color: u8, item: T) -> Paint<T> {
136 Paint::new(item).fg(Color::Fixed(color))
137 }
138
139 pub const fn red(item: T) -> Paint<T> {
140 Paint::new(item).fg(Color::Red)
141 }
142
143 pub const fn black(item: T) -> Paint<T> {
144 Paint::new(item).fg(Color::Black)
145 }
146
147 pub const fn yellow(item: T) -> Paint<T> {
148 Paint::new(item).fg(Color::Yellow)
149 }
150
151 pub const fn green(item: T) -> Paint<T> {
152 Paint::new(item).fg(Color::Green)
153 }
154
155 pub const fn cyan(item: T) -> Paint<T> {
156 Paint::new(item).fg(Color::Cyan)
157 }
158
159 pub const fn blue(item: T) -> Paint<T> {
160 Paint::new(item).fg(Color::Blue)
161 }
162
163 pub const fn magenta(item: T) -> Paint<T> {
164 Paint::new(item).fg(Color::Magenta)
165 }
166
167 pub const fn white(item: T) -> Paint<T> {
168 Paint::new(item).fg(Color::White)
169 }
170
171 #[inline]
173 pub const fn style(&self) -> Style {
174 self.style
175 }
176
177 #[inline]
179 pub const fn inner(&self) -> &T {
180 &self.item
181 }
182
183 #[inline]
185 pub fn with_style(mut self, style: Style) -> Paint<T> {
186 self.style = style;
187 self
188 }
189
190 #[inline]
202 pub const fn wrap(mut self) -> Paint<T> {
203 self.style.wrap = true;
204 self
205 }
206
207 #[inline]
209 pub const fn fg(mut self, color: Color) -> Paint<T> {
210 self.style.foreground = color;
211 self
212 }
213
214 #[inline]
216 pub const fn bg(mut self, color: Color) -> Paint<T> {
217 self.style.background = color;
218 self
219 }
220
221 pub fn bold(mut self) -> Self {
222 self.style.properties.set(Property::BOLD);
223 self
224 }
225
226 pub fn dim(mut self) -> Self {
227 self.style.properties.set(Property::DIM);
228 self
229 }
230
231 pub fn italic(mut self) -> Self {
232 self.style.properties.set(Property::ITALIC);
233 self
234 }
235
236 pub fn underline(mut self) -> Self {
237 self.style.properties.set(Property::UNDERLINE);
238 self
239 }
240
241 pub fn invert(mut self) -> Self {
242 self.style.properties.set(Property::INVERT);
243 self
244 }
245
246 pub fn strikethrough(mut self) -> Self {
247 self.style.properties.set(Property::STRIKETHROUGH);
248 self
249 }
250
251 pub fn blink(mut self) -> Self {
252 self.style.properties.set(Property::BLINK);
253 self
254 }
255
256 pub fn hidden(mut self) -> Self {
257 self.style.properties.set(Property::HIDDEN);
258 self
259 }
260}
261
262impl<T: fmt::Display> fmt::Display for Paint<T> {
263 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
264 if Paint::is_enabled() && self.style.wrap {
265 let mut prefix = String::new();
266 prefix.push_str("\x1B[0m");
267 self.style.fmt_prefix(&mut prefix)?;
268 self.style.fmt_prefix(f)?;
269
270 let item = format!("{}", self.item).replace("\x1B[0m", &prefix);
271 fmt::Display::fmt(&item, f)?;
272 self.style.fmt_suffix(f)
273 } else if Paint::is_enabled() {
274 self.style.fmt_prefix(f)?;
275 fmt::Display::fmt(&self.item, f)?;
276 self.style.fmt_suffix(f)
277 } else {
278 fmt::Display::fmt(&self.item, f)
279 }
280 }
281}
282
283impl Paint<()> {
284 pub fn is_enabled() -> bool {
286 if FORCED.load(sync::atomic::Ordering::SeqCst) {
287 return true;
288 }
289 let clicolor = anstyle_query::clicolor();
290 let clicolor_enabled = clicolor.unwrap_or(false);
291 let clicolor_disabled = !clicolor.unwrap_or(true);
292 let terminal = TERMINAL.load(sync::atomic::Ordering::SeqCst);
293 let is_terminal = TerminalFile::try_from(terminal).is_ok_and(|tf| tf.is_terminal());
294 let is_enabled = ENABLED.load(sync::atomic::Ordering::SeqCst);
295
296 is_terminal
297 && is_enabled
298 && !anstyle_query::no_color()
299 && !clicolor_disabled
300 && (anstyle_query::term_supports_color() || clicolor_enabled || anstyle_query::is_ci())
301 || anstyle_query::clicolor_force()
302 }
303
304 pub fn truecolor() -> bool {
306 static TRUECOLOR: LazyLock<bool> = LazyLock::new(anstyle_query::term_supports_color);
307 *TRUECOLOR
308 }
309
310 pub fn enable() {
312 ENABLED.store(true, sync::atomic::Ordering::SeqCst);
313 }
314
315 pub fn set_terminal(tf: TerminalFile) {
318 TERMINAL.store(tf as i32, sync::atomic::Ordering::SeqCst);
319 }
320
321 pub fn force(force: bool) {
324 FORCED.store(force, sync::atomic::Ordering::SeqCst);
325 }
326
327 pub fn disable() {
329 ENABLED.store(false, sync::atomic::Ordering::SeqCst);
330 }
331}
332
333#[derive(Debug, Clone)]
335pub struct Filled<T> {
336 pub item: T,
337 pub color: Color,
338}
339
340impl<T: fmt::Display> fmt::Display for Filled<T> {
341 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342 write!(f, "{}", Paint::wrapping(&self.item).bg(self.color))
343 }
344}
345
346pub fn paint<T>(item: T) -> Paint<T> {
348 Paint::new(item)
349}