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