rolldown_ariadne/
draw.rs

1use super::*;
2use yansi::Paint;
3
4pub struct Characters {
5    pub hbar: char,
6    pub vbar: char,
7    pub xbar: char,
8    #[allow(unused)]
9    pub vbar_break: char,
10    pub vbar_gap: char,
11
12    pub uarrow: char,
13    pub rarrow: char,
14
15    pub ltop: char,
16    #[allow(unused)]
17    pub mtop: char,
18    pub rtop: char,
19    pub lbot: char,
20    pub rbot: char,
21    pub mbot: char,
22
23    pub lbox: char,
24    pub rbox: char,
25
26    pub lcross: char,
27    #[allow(unused)]
28    pub rcross: char,
29
30    pub underbar: char,
31    pub underline: char,
32}
33
34impl Characters {
35    pub fn unicode() -> Self {
36        Self {
37            hbar: '─',
38            vbar: '│',
39            xbar: '┼',
40            vbar_break: '┆',
41            vbar_gap: '┆',
42            uarrow: '▲',
43            rarrow: '▶',
44            ltop: '╭',
45            mtop: '┬',
46            rtop: '╮',
47            lbot: '╰',
48            mbot: '┴',
49            rbot: '╯',
50            lbox: '[',
51            rbox: ']',
52            lcross: '├',
53            rcross: '┤',
54            underbar: '┬',
55            underline: '─',
56        }
57    }
58
59    pub fn ascii() -> Self {
60        Self {
61            hbar: '-',
62            vbar: '|',
63            xbar: '+',
64            vbar_break: '*',
65            vbar_gap: ':',
66            uarrow: '^',
67            rarrow: '>',
68            ltop: ',',
69            mtop: 'v',
70            rtop: '.',
71            lbot: '`',
72            mbot: '^',
73            rbot: '\'',
74            lbox: '[',
75            rbox: ']',
76            lcross: '|',
77            rcross: '|',
78            underbar: '|',
79            underline: '^',
80        }
81    }
82}
83
84/// Output stream to check for whether color is enabled.
85#[derive(Clone, Copy, Debug)]
86pub enum StreamType {
87    /// Standard Output
88    Stdout,
89    /// Standard Error
90    Stderr,
91}
92
93#[cfg(feature = "concolor")]
94impl From<StreamType> for concolor::Stream {
95    fn from(s: StreamType) -> Self {
96        match s {
97            StreamType::Stdout => concolor::Stream::Stdout,
98            StreamType::Stderr => concolor::Stream::Stderr,
99        }
100    }
101}
102
103/// A trait used to add formatting attributes to displayable items intended to be written to a
104/// particular stream (`stdout` or `stderr`).
105///
106/// Attributes specified through this trait are not composable (i.e: the behaviour of two nested attributes each with a
107/// conflicting attribute is left unspecified).
108pub trait StreamAwareFmt: Sized {
109    #[cfg(feature = "concolor")]
110    /// Returns true if color is enabled for the given stream.
111    fn color_enabled_for(s: StreamType) -> bool {
112        concolor::get(s.into()).color()
113    }
114
115    #[cfg(not(feature = "concolor"))]
116    #[doc(hidden)]
117    fn color_enabled_for(_: StreamType) -> bool {
118        true
119    }
120
121    /// Give this value the specified foreground colour, when color is enabled for the specified stream.
122    fn fg<C: Into<Option<Color>>>(self, color: C, stream: StreamType) -> Foreground<Self> {
123        if Self::color_enabled_for(stream) {
124            Foreground(self, color.into())
125        } else {
126            Foreground(self, None)
127        }
128    }
129
130    /// Give this value the specified background colour, when color is enabled for the specified stream.
131    fn bg<C: Into<Option<Color>>>(self, color: C, stream: StreamType) -> Background<Self> {
132        if Self::color_enabled_for(stream) {
133            Background(self, color.into())
134        } else {
135            Background(self, None)
136        }
137    }
138}
139
140impl<T: fmt::Display> StreamAwareFmt for T {}
141
142/// A trait used to add formatting attributes to displayable items.
143///
144/// If using the `concolor` feature, this trait assumes that the items are going to be printed to
145/// `stderr`. If you are printing to `stdout`, `use` the [`StdoutFmt`] trait instead.
146///
147/// Attributes specified through this trait are not composable (i.e: the behaviour of two nested attributes each with a
148/// conflicting attribute is left unspecified).
149pub trait Fmt: Sized {
150    /// Give this value the specified foreground colour.
151    fn fg<C: Into<Option<Color>>>(self, color: C) -> Foreground<Self>
152    where
153        Self: fmt::Display,
154    {
155        if cfg!(feature = "concolor") {
156            StreamAwareFmt::fg(self, color, StreamType::Stderr)
157        } else {
158            Foreground(self, color.into())
159        }
160    }
161
162    /// Give this value the specified background colour.
163    fn bg<C: Into<Option<Color>>>(self, color: C) -> Background<Self>
164    where
165        Self: fmt::Display,
166    {
167        if cfg!(feature = "concolor") {
168            StreamAwareFmt::bg(self, color, StreamType::Stdout)
169        } else {
170            Background(self, color.into())
171        }
172    }
173}
174
175impl<T: fmt::Display> Fmt for T {}
176
177/// A trait used to add formatting attributes to displayable items intended to be written to `stdout`.
178///
179/// Attributes specified through this trait are not composable (i.e: the behaviour of two nested attributes each with a
180/// conflicting attribute is left unspecified).
181#[cfg(any(feature = "concolor", doc))]
182pub trait StdoutFmt: StreamAwareFmt {
183    /// Give this value the specified foreground colour, when color is enabled for `stdout`.
184    fn fg<C: Into<Option<Color>>>(self, color: C) -> Foreground<Self> {
185        StreamAwareFmt::fg(self, color, StreamType::Stdout)
186    }
187
188    /// Give this value the specified background colour, when color is enabled for `stdout`.
189    fn bg<C: Into<Option<Color>>>(self, color: C) -> Background<Self> {
190        StreamAwareFmt::bg(self, color, StreamType::Stdout)
191    }
192}
193
194#[cfg(feature = "concolor")]
195impl<T: fmt::Display> StdoutFmt for T {}
196
197#[derive(Copy, Clone, Debug)]
198pub struct Foreground<T>(T, Option<Color>);
199impl<T: fmt::Display> fmt::Display for Foreground<T> {
200    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
201        if let Some(col) = self.1 {
202            write!(f, "{}", Paint::new(&self.0).fg(col))
203        } else {
204            write!(f, "{}", self.0)
205        }
206    }
207}
208
209#[derive(Copy, Clone, Debug)]
210pub struct Background<T>(T, Option<Color>);
211impl<T: fmt::Display> fmt::Display for Background<T> {
212    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
213        if let Some(col) = self.1 {
214            write!(f, "{}", Paint::new(&self.0).bg(col))
215        } else {
216            write!(f, "{}", self.0)
217        }
218    }
219}
220
221/// A type that can generate distinct 8-bit colors.
222pub struct ColorGenerator {
223    state: [u16; 3],
224    min_brightness: f32,
225}
226
227impl Default for ColorGenerator {
228    fn default() -> Self {
229        Self::new()
230    }
231}
232
233impl ColorGenerator {
234    /// Create a new [`ColorGenerator`] with the given pre-chosen state.
235    ///
236    /// The minimum brightness can be used to control the colour brightness (0.0 - 1.0). The default is 0.5.
237    pub const fn from_state(state: [u16; 3], min_brightness: f32) -> Self {
238        Self {
239            state,
240            min_brightness: min_brightness.clamp(0.0, 1.0),
241        }
242    }
243
244    /// Create a new [`ColorGenerator`] with the default state.
245    pub const fn new() -> Self {
246        Self::from_state([30000, 15000, 35000], 0.5)
247    }
248
249    /// Generate the next colour in the sequence.
250    #[allow(clippy::should_implement_trait)]
251    pub const fn next(&mut self) -> Color {
252        // `for i in n..m` not supported in const fn, so workaround that
253        let mut i = 0;
254        while i < 3 {
255            // magic constant, one of only two that have this property!
256            self.state[i] = (self.state[i] as usize).wrapping_add(40503 * (i * 4 + 1130)) as u16;
257            i += 1;
258        }
259        Color::Fixed(
260            16 + ((self.state[2] as f32 / 65535.0 * (1.0 - self.min_brightness)
261                + self.min_brightness)
262                * 5.0
263                + (self.state[1] as f32 / 65535.0 * (1.0 - self.min_brightness)
264                    + self.min_brightness)
265                    * 30.0
266                + (self.state[0] as f32 / 65535.0 * (1.0 - self.min_brightness)
267                    + self.min_brightness)
268                    * 180.0) as u8,
269        )
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276
277    #[test]
278    fn const_colors() {
279        const COLORS: [Color; 3] = {
280            let mut gen = ColorGenerator::new();
281            [gen.next(), gen.next(), gen.next()]
282        };
283        assert_ne!(COLORS[0], COLORS[1]);
284        assert_ne!(COLORS[1], COLORS[2]);
285        assert_ne!(COLORS[2], COLORS[0]);
286    }
287}