Skip to main content

tree_display/
color.rs

1//! Colors used for drawing tree structures in the terminal.
2//!
3//! This module provides color themes for tree display elements including types,
4//! keywords, strings, values, and tree line characters.
5
6// ──── API ───────────────────────────────────────────────────────────────────────────────────────
7
8pub use inner::Color;
9
10/// Color configuration for tree display elements.
11///
12/// Each element of the tree can be individually colored, allowing you
13/// to highlight different syntactic categories like types, keywords, strings,
14/// and tree lines. Colors are optional, so you can enable only the highlighting
15/// you want.
16///
17/// ## Example
18/// ```no_run
19/// use tree_display::Colors;
20///
21/// let dark = Colors::VSCODE_DARK_PLUS;
22/// let light = Colors::VSCODE_LIGHT_PLUS;
23///
24/// // Customize individual colors
25/// let custom = Colors::new()
26///     .types(dark.types)
27///     .strings(light.strings);
28/// ```
29#[non_exhaustive]
30#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
31pub struct Colors {
32  /// Color for branch lines (horizontal connectors between nodes)
33  pub branches: Option<Color>,
34  /// Color for vertical tree lines
35  pub vertical: Option<Color>,
36  /// Color for keyword like values (e.g. None)
37  pub keywords: Option<Color>,
38  /// Color for struct/enum member names
39  pub members: Option<Color>,
40  /// Color for string literals
41  pub strings: Option<Color>,
42  /// Color for literal values (e.g. integers, floats, bools)
43  pub values: Option<Color>,
44  /// Color for type names (e.g., [`String`], [`Vec`], [`Option`])
45  pub types: Option<Color>,
46}
47
48// ──── Predefined themes ─────────────────────────────────────────────────────────────────────────
49
50impl Colors {
51  /// No colors (all elements use default terminal color).
52  pub const NONE: Self = Self::new();
53
54  /// Visual Studio Code Dark+ theme.
55  pub const VSCODE_DARK_PLUS: Self = Self::new()
56    .branches(Some(inner::ansi256(32)))
57    .vertical(Some(inner::ansi256(24)))
58    .keywords(Some(inner::ansi256(74)))
59    .members(Some(inner::ansi256(153)))
60    .strings(Some(inner::ansi256(173)))
61    .values(Some(inner::ansi256(187)))
62    .types(Some(inner::ansi256(79)));
63
64  /// Visual Studio Code Light+ theme.
65  pub const VSCODE_LIGHT_PLUS: Self = Self::new()
66    .branches(Some(inner::ansi256(28)))
67    .vertical(Some(inner::ansi256(23)))
68    .keywords(Some(inner::ansi256(133)))
69    .members(Some(inner::ansi256(25)))
70    .strings(Some(inner::ansi256(88)))
71    .values(Some(inner::ansi256(94)))
72    .types(Some(inner::ansi256(26)));
73
74  /// Solarized Dark theme.
75  pub const SOLARIZED_DARK: Self = Self::new()
76    .branches(Some(inner::ansi256(101)))
77    .vertical(Some(inner::ansi256(60)))
78    .keywords(Some(inner::ansi256(33)))
79    .members(Some(inner::ansi256(112)))
80    .strings(Some(inner::ansi256(106)))
81    .values(Some(inner::ansi256(179)))
82    .types(Some(inner::ansi256(68)));
83
84  /// Solarized Light theme.
85  pub const SOLARIZED_LIGHT: Self = Self::new()
86    .branches(Some(inner::ansi256(101)))
87    .vertical(Some(inner::ansi256(60)))
88    .keywords(Some(inner::ansi256(33)))
89    .members(Some(inner::ansi256(112)))
90    .strings(Some(inner::ansi256(106)))
91    .values(Some(inner::ansi256(179)))
92    .types(Some(inner::ansi256(68)));
93
94  /// Dracula theme.
95  pub const DRACULA: Self = Self::new()
96    .branches(Some(inner::ansi256(102)))
97    .vertical(Some(inner::ansi256(59)))
98    .keywords(Some(inner::ansi256(141)))
99    .members(Some(inner::ansi256(147)))
100    .strings(Some(inner::ansi256(114)))
101    .values(Some(inner::ansi256(186)))
102    .types(Some(inner::ansi256(147)));
103
104  /// Monokai theme.
105  pub const MONOKAI: Self = Self::new()
106    .branches(Some(inner::ansi256(144)))
107    .vertical(Some(inner::ansi256(59)))
108    .keywords(Some(inner::ansi256(204)))
109    .members(Some(inner::ansi256(147)))
110    .strings(Some(inner::ansi256(113)))
111    .values(Some(inner::ansi256(186)))
112    .types(Some(inner::ansi256(147)));
113
114  /// Nord theme.
115  pub const NORD: Self = Self::new()
116    .branches(Some(inner::ansi256(109)))
117    .vertical(Some(inner::ansi256(66)))
118    .keywords(Some(inner::ansi256(117)))
119    .members(Some(inner::ansi256(148)))
120    .strings(Some(inner::ansi256(150)))
121    .values(Some(inner::ansi256(179)))
122    .types(Some(inner::ansi256(148)));
123
124  /// GitHub Dark theme.
125  pub const GITHUB_DARK: Self = Self::new()
126    .branches(Some(inner::ansi256(102)))
127    .vertical(Some(inner::ansi256(59)))
128    .keywords(Some(inner::ansi256(204)))
129    .members(Some(inner::ansi256(117)))
130    .strings(Some(inner::ansi256(142)))
131    .values(Some(inner::ansi256(186)))
132    .types(Some(inner::ansi256(117)));
133
134  /// GitHub Light theme.
135  pub const GITHUB_LIGHT: Self = Self::new()
136    .branches(Some(inner::ansi256(102)))
137    .vertical(Some(inner::ansi256(59)))
138    .keywords(Some(inner::ansi256(204)))
139    .members(Some(inner::ansi256(26)))
140    .strings(Some(inner::ansi256(142)))
141    .values(Some(inner::ansi256(186)))
142    .types(Some(inner::ansi256(26)));
143}
144
145// ──── Utility ───────────────────────────────────────────────────────────────────────────────────
146
147impl Colors {
148  /// Creates a new [`Colors`] instance with `Visual Studio Code Dark+` colors.
149  pub const fn new() -> Self {
150    Self {
151      branches: None,
152      vertical: None,
153      keywords: None,
154      members: None,
155      strings: None,
156      values: None,
157      types: None,
158    }
159  }
160
161  /// Sets the color for branch lines.
162  ///
163  /// Branch lines are the horizontal connectors that extend from vertical lines
164  /// to node labels. They form the "arms" of the tree structure.
165  ///
166  /// Pass `None` to disable coloring for this element.
167  pub const fn branches(mut self, color: Option<Color>) -> Self {
168    self.branches = color;
169    self
170  }
171
172  /// Sets the color for vertical tree lines.
173  ///
174  /// Vertical lines connect parent nodes to their children, forming the main
175  /// backbone of the tree hierarchy.
176  ///
177  /// Pass `None` to disable coloring for this element.
178  pub const fn vertical(mut self, color: Option<Color>) -> Self {
179    self.vertical = color;
180    self
181  }
182
183  /// Sets the color for language keywords.
184  ///
185  /// Keywords refer to keyword-like values such as enum variant names.
186  ///
187  /// Pass `None` to disable coloring for this element.
188  pub const fn keywords(mut self, color: Option<Color>) -> Self {
189    self.keywords = color;
190    self
191  }
192
193  /// Sets the color for struct/enum member names.
194  ///
195  /// Member names are the fields of structs and variants of enums. They are
196  /// typically displayed alongside their containing type.
197  ///
198  /// Pass `None` to disable coloring for this element.
199  pub const fn members(mut self, color: Option<Color>) -> Self {
200    self.members = color;
201    self
202  }
203
204  /// Sets the color for string literals.
205  ///
206  /// String literals are quoted text values, such as `"hello"` or multi-line string blocks.
207  ///
208  /// Pass `None` to disable coloring for this element.
209  pub const fn strings(mut self, color: Option<Color>) -> Self {
210    self.strings = color;
211    self
212  }
213
214  /// Sets the color for literal values.
215  ///
216  /// Literal values include numbers (`42`, `3.14`), booleans (`true`, `false`),
217  /// characters (`'a'`), and other primitive constant values.
218  ///
219  /// Pass `None` to disable coloring for this element.
220  pub const fn values(mut self, color: Option<Color>) -> Self {
221    self.values = color;
222    self
223  }
224
225  /// Sets the color for type names.
226  ///
227  /// Type names are identifiers that refer to types, such as [`String`], [`Vec`],
228  /// [`Option`], [`i32`], and user-defined structs and enums.
229  ///
230  /// Pass `None` to disable coloring for this element.
231  pub const fn types(mut self, color: Option<Color>) -> Self {
232    self.types = color;
233    self
234  }
235
236  /// Sets all color elements to the same value.
237  ///
238  /// This is a convenience method for quickly creating a uniform theme
239  /// where all elements share the same color.
240  ///
241  /// Pass `None` to disable all coloring.
242  pub const fn all(mut self, color: Option<Color>) -> Self {
243    self.branches = color;
244    self.vertical = color;
245    self.keywords = color;
246    self.members = color;
247    self.strings = color;
248    self.values = color;
249    self.types = color;
250    self
251  }
252}
253
254impl Default for Colors {
255  /// Creates a new [`Colors`] instance with `Visual Studio Code Dark+` colors.
256  fn default() -> Self {
257    Self::VSCODE_DARK_PLUS
258  }
259}
260
261// ──── Impl ──────────────────────────────────────────────────────────────────────────────────────
262
263pub(crate) trait Colored {
264  fn fg(&self, color: Option<Color>) -> String;
265  fn stripped(&self) -> String;
266}
267
268#[cfg(feature = "color")]
269mod inner {
270  pub use anstyle::Color;
271
272  pub(crate) const fn ansi256(color: u8) -> Color {
273    Color::Ansi256(anstyle::Ansi256Color(color))
274  }
275
276  impl<T: AsRef<str>> super::Colored for T {
277    fn fg(&self, color: Option<Color>) -> String {
278      let style = anstyle::Style::new().fg_color(color);
279      let style_intro = style.render().to_string();
280      let style_reset = style.render_reset().to_string();
281      format!("{}{}{}", style_intro, self.as_ref(), style_reset)
282    }
283
284    fn stripped(&self) -> String {
285      use anstream::adapter::strip_str;
286      strip_str(self.as_ref()).to_string()
287    }
288  }
289}
290
291#[cfg(not(feature = "color"))]
292mod inner {
293  /// A placeholder color type used when the `color` feature is disabled.
294  ///
295  /// All color operations become no-ops, rendering text without ANSI escape codes.
296  /// This allows the crate to be used without color dependencies.
297  #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
298  pub struct Color;
299
300  pub(crate) const fn ansi256(_: u8) -> Color {
301    Color
302  }
303
304  impl<T: AsRef<str>> super::Colored for T {
305    fn fg(&self, _: Option<Color>) -> String {
306      self.as_ref().to_string()
307    }
308
309    fn stripped(&self) -> String {
310      self.as_ref().to_string()
311    }
312  }
313}