radicle_term/ansi/
paint.rs

1use 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
11/// What file is used for text output.
12static TERMINAL: AtomicI32 = AtomicI32::new(libc::STDOUT_FILENO);
13/// Whether paint styling is enabled or not.
14static ENABLED: AtomicBool = AtomicBool::new(true);
15/// Whether paint styling should be forced.
16static FORCED: AtomicBool = AtomicBool::new(false);
17
18/// A structure encapsulating an item and styling.
19#[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    /// Return plain content.
27    pub fn content(&self) -> &str {
28        self.item
29    }
30}
31
32impl Paint<String> {
33    /// Return plain content.
34    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    /// Constructs a new `Paint` structure encapsulating `item` with no set
68    /// styling.
69    #[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    /// Constructs a new _wrapping_ `Paint` structure encapsulating `item` with
83    /// default styling.
84    ///
85    /// A wrapping `Paint` converts all color resets written out by the internal
86    /// value to the styling of itself. This allows for seamless color wrapping
87    /// of other colored text.
88    ///
89    /// # Performance
90    ///
91    /// In order to wrap an internal value, the internal value must first be
92    /// written out to a local buffer and examined. As a result, displaying a
93    /// wrapped value is likely to result in a heap allocation and copy.
94    #[inline]
95    pub const fn wrapping(item: T) -> Paint<T> {
96        Paint::new(item).wrap()
97    }
98
99    /// Constructs a new `Paint` structure encapsulating `item` with the
100    /// foreground color set to the RGB color `r`, `g`, `b`.
101    #[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    /// Constructs a new `Paint` structure encapsulating `item` with the
107    /// foreground color set to the fixed 8-bit color `color`.
108    #[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    /// Retrieves the style currently set on `self`.
146    #[inline]
147    pub const fn style(&self) -> Style {
148        self.style
149    }
150
151    /// Retrieves a borrow to the inner item.
152    #[inline]
153    pub const fn inner(&self) -> &T {
154        &self.item
155    }
156
157    /// Sets the style of `self` to `style`.
158    #[inline]
159    pub fn with_style(mut self, style: Style) -> Paint<T> {
160        self.style = style;
161        self
162    }
163
164    /// Makes `self` a _wrapping_ `Paint`.
165    ///
166    /// A wrapping `Paint` converts all color resets written out by the internal
167    /// value to the styling of itself. This allows for seamless color wrapping
168    /// of other colored text.
169    ///
170    /// # Performance
171    ///
172    /// In order to wrap an internal value, the internal value must first be
173    /// written out to a local buffer and examined. As a result, displaying a
174    /// wrapped value is likely to result in a heap allocation and copy.
175    #[inline]
176    pub const fn wrap(mut self) -> Paint<T> {
177        self.style.wrap = true;
178        self
179    }
180
181    /// Sets the foreground to `color`.
182    #[inline]
183    pub const fn fg(mut self, color: Color) -> Paint<T> {
184        self.style.foreground = color;
185        self
186    }
187
188    /// Sets the background to `color`.
189    #[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    /// Returns `true` if coloring is enabled and `false` otherwise.
259    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    /// Check 24-bit RGB color support.
279    pub fn truecolor() -> bool {
280        static TRUECOLOR: Lazy<bool> = Lazy::new(anstyle_query::term_supports_color);
281        *TRUECOLOR
282    }
283
284    /// Enable paint styling.
285    pub fn enable() {
286        ENABLED.store(true, sync::atomic::Ordering::SeqCst);
287    }
288
289    /// Set the terminal we are writing to. This influences the logic that checks whether or not to
290    /// include colors.
291    pub fn set_terminal(fd: impl AsRawFd) {
292        TERMINAL.store(fd.as_raw_fd(), sync::atomic::Ordering::SeqCst);
293    }
294
295    /// Force paint styling.
296    /// Useful when you want to output colors to a non-TTY.
297    pub fn force(force: bool) {
298        FORCED.store(force, sync::atomic::Ordering::SeqCst);
299    }
300
301    /// Disable paint styling.
302    pub fn disable() {
303        ENABLED.store(false, sync::atomic::Ordering::SeqCst);
304    }
305}
306
307/// An object filled with a background color.
308#[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
320/// Shorthand for [`Paint::new`].
321pub fn paint<T>(item: T) -> Paint<T> {
322    Paint::new(item)
323}