radicle_term/ansi/
paint.rs1use std::io::IsTerminal as _;
2use std::os::fd::{AsRawFd, BorrowedFd};
3use std::sync::atomic::{AtomicBool, AtomicI32};
4use std::sync::LazyLock;
5use std::{fmt, sync};
6
7use super::color::Color;
8use super::style::{Property, Style};
9
10static TERMINAL: AtomicI32 = AtomicI32::new(libc::STDOUT_FILENO);
12static ENABLED: AtomicBool = AtomicBool::new(true);
14static FORCED: AtomicBool = AtomicBool::new(false);
16
17#[derive(Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Copy, Clone)]
19pub struct Paint<T> {
20 pub item: T,
21 pub style: Style,
22}
23
24impl Paint<&str> {
25 pub fn content(&self) -> &str {
27 self.item
28 }
29}
30
31impl Paint<String> {
32 pub fn content(&self) -> &str {
34 self.item.as_str()
35 }
36}
37
38impl<T> From<T> for Paint<T> {
39 fn from(value: T) -> Self {
40 Self::new(value)
41 }
42}
43
44impl From<&str> for Paint<String> {
45 fn from(item: &str) -> Self {
46 Self::new(item.to_string())
47 }
48}
49
50impl From<Paint<&str>> for Paint<String> {
51 fn from(paint: Paint<&str>) -> Self {
52 Self {
53 item: paint.item.to_owned(),
54 style: paint.style,
55 }
56 }
57}
58
59impl From<Paint<String>> for String {
60 fn from(value: Paint<String>) -> Self {
61 value.item
62 }
63}
64
65impl<T> Paint<T> {
66 #[inline]
69 pub const fn new(item: T) -> Paint<T> {
70 Paint {
71 item,
72 style: Style {
73 foreground: Color::Unset,
74 background: Color::Unset,
75 properties: Property::new(),
76 wrap: false,
77 },
78 }
79 }
80
81 #[inline]
94 pub const fn wrapping(item: T) -> Paint<T> {
95 Paint::new(item).wrap()
96 }
97
98 #[inline]
101 pub const fn rgb(r: u8, g: u8, b: u8, item: T) -> Paint<T> {
102 Paint::new(item).fg(Color::RGB(r, g, b))
103 }
104
105 #[inline]
108 pub const fn fixed(color: u8, item: T) -> Paint<T> {
109 Paint::new(item).fg(Color::Fixed(color))
110 }
111
112 pub const fn red(item: T) -> Paint<T> {
113 Paint::new(item).fg(Color::Red)
114 }
115
116 pub const fn black(item: T) -> Paint<T> {
117 Paint::new(item).fg(Color::Black)
118 }
119
120 pub const fn yellow(item: T) -> Paint<T> {
121 Paint::new(item).fg(Color::Yellow)
122 }
123
124 pub const fn green(item: T) -> Paint<T> {
125 Paint::new(item).fg(Color::Green)
126 }
127
128 pub const fn cyan(item: T) -> Paint<T> {
129 Paint::new(item).fg(Color::Cyan)
130 }
131
132 pub const fn blue(item: T) -> Paint<T> {
133 Paint::new(item).fg(Color::Blue)
134 }
135
136 pub const fn magenta(item: T) -> Paint<T> {
137 Paint::new(item).fg(Color::Magenta)
138 }
139
140 pub const fn white(item: T) -> Paint<T> {
141 Paint::new(item).fg(Color::White)
142 }
143
144 #[inline]
146 pub const fn style(&self) -> Style {
147 self.style
148 }
149
150 #[inline]
152 pub const fn inner(&self) -> &T {
153 &self.item
154 }
155
156 #[inline]
158 pub fn with_style(mut self, style: Style) -> Paint<T> {
159 self.style = style;
160 self
161 }
162
163 #[inline]
175 pub const fn wrap(mut self) -> Paint<T> {
176 self.style.wrap = true;
177 self
178 }
179
180 #[inline]
182 pub const fn fg(mut self, color: Color) -> Paint<T> {
183 self.style.foreground = color;
184 self
185 }
186
187 #[inline]
189 pub const fn bg(mut self, color: Color) -> Paint<T> {
190 self.style.background = color;
191 self
192 }
193
194 pub fn bold(mut self) -> Self {
195 self.style.properties.set(Property::BOLD);
196 self
197 }
198
199 pub fn dim(mut self) -> Self {
200 self.style.properties.set(Property::DIM);
201 self
202 }
203
204 pub fn italic(mut self) -> Self {
205 self.style.properties.set(Property::ITALIC);
206 self
207 }
208
209 pub fn underline(mut self) -> Self {
210 self.style.properties.set(Property::UNDERLINE);
211 self
212 }
213
214 pub fn invert(mut self) -> Self {
215 self.style.properties.set(Property::INVERT);
216 self
217 }
218
219 pub fn strikethrough(mut self) -> Self {
220 self.style.properties.set(Property::STRIKETHROUGH);
221 self
222 }
223
224 pub fn blink(mut self) -> Self {
225 self.style.properties.set(Property::BLINK);
226 self
227 }
228
229 pub fn hidden(mut self) -> Self {
230 self.style.properties.set(Property::HIDDEN);
231 self
232 }
233}
234
235impl<T: fmt::Display> fmt::Display for Paint<T> {
236 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
237 if Paint::is_enabled() && self.style.wrap {
238 let mut prefix = String::new();
239 prefix.push_str("\x1B[0m");
240 self.style.fmt_prefix(&mut prefix)?;
241 self.style.fmt_prefix(f)?;
242
243 let item = format!("{}", self.item).replace("\x1B[0m", &prefix);
244 fmt::Display::fmt(&item, f)?;
245 self.style.fmt_suffix(f)
246 } else if Paint::is_enabled() {
247 self.style.fmt_prefix(f)?;
248 fmt::Display::fmt(&self.item, f)?;
249 self.style.fmt_suffix(f)
250 } else {
251 fmt::Display::fmt(&self.item, f)
252 }
253 }
254}
255
256impl Paint<()> {
257 pub fn is_enabled() -> bool {
259 if FORCED.load(sync::atomic::Ordering::SeqCst) {
260 return true;
261 }
262 let clicolor = anstyle_query::clicolor();
263 let clicolor_enabled = clicolor.unwrap_or(false);
264 let clicolor_disabled = !clicolor.unwrap_or(true);
265 let terminal = TERMINAL.load(sync::atomic::Ordering::SeqCst);
266 let is_terminal = unsafe { BorrowedFd::borrow_raw(terminal).is_terminal() };
267 let is_enabled = ENABLED.load(sync::atomic::Ordering::SeqCst);
268
269 is_terminal
270 && is_enabled
271 && !anstyle_query::no_color()
272 && !clicolor_disabled
273 && (anstyle_query::term_supports_color() || clicolor_enabled || anstyle_query::is_ci())
274 || anstyle_query::clicolor_force()
275 }
276
277 pub fn truecolor() -> bool {
279 static TRUECOLOR: LazyLock<bool> = LazyLock::new(anstyle_query::term_supports_color);
280 *TRUECOLOR
281 }
282
283 pub fn enable() {
285 ENABLED.store(true, sync::atomic::Ordering::SeqCst);
286 }
287
288 pub fn set_terminal(fd: impl AsRawFd) {
291 TERMINAL.store(fd.as_raw_fd(), sync::atomic::Ordering::SeqCst);
292 }
293
294 pub fn force(force: bool) {
297 FORCED.store(force, sync::atomic::Ordering::SeqCst);
298 }
299
300 pub fn disable() {
302 ENABLED.store(false, sync::atomic::Ordering::SeqCst);
303 }
304}
305
306#[derive(Debug, Clone)]
308pub struct Filled<T> {
309 pub item: T,
310 pub color: Color,
311}
312
313impl<T: fmt::Display> fmt::Display for Filled<T> {
314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315 write!(f, "{}", Paint::wrapping(&self.item).bg(self.color))
316 }
317}
318
319pub fn paint<T>(item: T) -> Paint<T> {
321 Paint::new(item)
322}