diagnostic/style/style.rs
1use crate::{Color, Paint};
2use std::{
3 fmt::{self, Display},
4 hash::{Hash, Hasher},
5 ops::BitOr,
6};
7
8#[derive(Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Copy, Clone)]
9pub struct Property(u8);
10
11impl Property {
12 pub const BOLD: Self = Property(1 << 0);
13 pub const DIMMED: Self = Property(1 << 1);
14 pub const ITALIC: Self = Property(1 << 2);
15 pub const UNDERLINE: Self = Property(1 << 3);
16 pub const BLINK: Self = Property(1 << 4);
17 pub const INVERT: Self = Property(1 << 5);
18 pub const HIDDEN: Self = Property(1 << 6);
19 pub const STRIKETHROUGH: Self = Property(1 << 7);
20
21 #[inline(always)]
22 pub fn contains(self, other: Property) -> bool {
23 (other.0 & self.0) == other.0
24 }
25
26 #[inline(always)]
27 pub fn set(&mut self, other: Property) {
28 self.0 |= other.0;
29 }
30
31 #[inline(always)]
32 pub fn iter(self) -> Iter {
33 Iter { index: 0, properties: self }
34 }
35}
36
37impl BitOr for Property {
38 type Output = Self;
39
40 #[inline(always)]
41 fn bitor(self, rhs: Self) -> Self {
42 Property(self.0 | rhs.0)
43 }
44}
45
46pub struct Iter {
47 index: u8,
48 properties: Property,
49}
50
51impl Iterator for Iter {
52 type Item = usize;
53
54 fn next(&mut self) -> Option<Self::Item> {
55 while self.index < 8 {
56 let index = self.index;
57 self.index += 1;
58
59 if self.properties.contains(Property(1 << index)) {
60 return Some(index as usize);
61 }
62 }
63
64 None
65 }
66}
67
68/// Represents a set of styling options.
69///
70/// See the [crate level documentation](./) for usage information.
71///
72/// # Method Glossary
73///
74/// The `Style` structure exposes many methods for convenience. The majority of
75/// these methods are shared with [`Paint`](Paint).
76///
77/// ### Foreground Color Constructors
78///
79/// Return a new `Style` structure with a foreground `color` applied.
80///
81/// * [`Style::new(color: Color)`](Style::new())
82///
83/// ### Setters
84///
85/// Set a style property on a given `Style` structure.
86///
87/// * [`style.fg(color: Color)`](Style::fg())
88/// * [`style.bg(color: Color)`](Style::bg())
89/// * [`style.mask()`](Style::mask())
90/// * [`style.wrap()`](Style::wrap())
91/// * [`style.bold()`](Style::bold())
92/// * [`style.dimmed()`](Style::dimmed())
93/// * [`style.italic()`](Style::italic())
94/// * [`style.underline()`](Style::underline())
95/// * [`style.blink()`](Style::blink())
96/// * [`style.invert()`](Style::invert())
97/// * [`style.hidden()`](Style::hidden())
98/// * [`style.strikethrough()`](Style::strikethrough())
99///
100/// These methods can be chained:
101///
102/// ```rust
103/// use diagnostic::{
104/// Color::{Magenta, Red},
105/// Style,
106/// };
107///
108/// Style::new(Red).bg(Magenta).underline().invert().italic().dimmed().bold();
109/// ```
110///
111/// ### Converters
112///
113/// Convert a `Style` into another structure.
114///
115/// * [`style.paint<T>(item: T) -> Paint<T>`](Style::paint())
116///
117/// ### Getters
118///
119/// Return information about a `Style` structure.
120///
121/// * [`style.fg_color()`](Style::fg_color())
122/// * [`style.bg_color()`](Style::bg_color())
123/// * [`style.is_masked()`](Style::is_masked())
124/// * [`style.is_wrapping()`](Style::is_wrapping())
125/// * [`style.is_bold()`](Style::is_bold())
126/// * [`style.is_dimmed()`](Style::is_dimmed())
127/// * [`style.is_italic()`](Style::is_italic())
128/// * [`style.is_underline()`](Style::is_underline())
129/// * [`style.is_blink()`](Style::is_blink())
130/// * [`style.is_invert()`](Style::is_invert())
131/// * [`style.is_hidden()`](Style::is_hidden())
132/// * [`style.is_strikethrough()`](Style::is_strikethrough())
133///
134/// ### Raw Formatters
135///
136/// Write the raw ANSI codes for a given `Style` to any `fmt::Write`.
137///
138/// * [`style.fmt_prefix(f: &mut fmt::Write)`](Style::fmt_prefix())
139/// * [`style.fmt_suffix(f: &mut fmt::Write)`](Style::fmt_suffix())
140#[repr(packed)]
141#[derive(Default, Debug, Eq, Ord, PartialOrd, Copy, Clone)]
142pub struct Style {
143 pub(crate) foreground: Color,
144 pub(crate) background: Color,
145 pub(crate) properties: Property,
146 pub(crate) masked: bool,
147 pub(crate) wrap: bool,
148}
149
150impl PartialEq for Style {
151 fn eq(&self, other: &Style) -> bool {
152 self.foreground == other.foreground && self.background == other.background && self.properties == other.properties
153 }
154}
155
156impl Hash for Style {
157 fn hash<H: Hasher>(&self, state: &mut H) {
158 self.foreground.hash(state);
159 self.background.hash(state);
160 self.properties.hash(state);
161 }
162}
163
164macro_rules! checker_for {
165 ($($name:ident ($fn_name:ident): $property:ident),*) => ($(
166 #[doc = concat!(
167 "Returns `true` if the _", stringify!($name), "_ property is set on `self`.\n",
168 "```rust\n",
169 "use diagnostic::Style;\n",
170 "\n",
171 "let plain = Style::default();\n",
172 "assert!(!plain.", stringify!($fn_name), "());\n",
173 "\n",
174 "let styled = plain.", stringify!($name), "();\n",
175 "assert!(styled.", stringify!($fn_name), "());\n",
176 "```\n"
177 )]
178 #[inline]
179 pub fn $fn_name(&self) -> bool {
180 self.properties.contains(Property::$property)
181 }
182 )*)
183}
184
185#[inline]
186fn write_spliced<T: Display>(c: &mut bool, f: &mut impl std::fmt::Write, t: T) -> fmt::Result {
187 if *c {
188 write!(f, ";{}", t)
189 }
190 else {
191 *c = true;
192 write!(f, "{}", t)
193 }
194}
195
196impl Style {
197 /// Default style with the foreground set to `color` and no other set
198 /// properties.
199 ///
200 /// ```rust
201 /// use diagnostic::Style;
202 ///
203 /// let plain = Style::default();
204 /// assert_eq!(plain, Style::default());
205 /// ```
206 #[inline]
207 pub fn new(color: Color) -> Style {
208 Self::default().fg(color)
209 }
210
211 /// Sets the foreground to `color`.
212 ///
213 /// ```rust
214 /// use diagnostic::{Color, Style};
215 ///
216 /// let red_fg = Style::default().fg(Color::Red);
217 /// ```
218 #[inline]
219 pub fn fg(mut self, color: Color) -> Style {
220 self.foreground = color;
221 self
222 }
223
224 /// Sets the background to `color`.
225 ///
226 /// ```rust
227 /// use diagnostic::{Color, Style};
228 ///
229 /// let red_bg = Style::default().bg(Color::Red);
230 /// ```
231 #[inline]
232 pub fn bg(mut self, color: Color) -> Style {
233 self.background = color;
234 self
235 }
236
237 /// Sets `self` to be masked.
238 ///
239 /// An item with _masked_ styling is not written out when painting is
240 /// disabled during `Display` or `Debug` invocations. When painting is
241 /// enabled, masking has no effect.
242 ///
243 /// ```rust
244 /// use diagnostic::Style;
245 ///
246 /// let masked = Style::default().mask();
247 ///
248 /// // "Whoops! " will only print when coloring is enabled.
249 /// println!("{}Something happened.", masked.paint("Whoops! "));
250 /// ```
251 #[inline]
252 pub fn mask(mut self) -> Style {
253 self.masked = true;
254 self
255 }
256
257 /// Sets `self` to be wrapping.
258 ///
259 /// A wrapping `Style` converts all color resets written out by the internal
260 /// value to the styling of itself. This allows for seamless color wrapping
261 /// of other colored text.
262 ///
263 /// # Performance
264 ///
265 /// In order to wrap an internal value, the internal value must first be
266 /// written out to a local buffer and examined. As a result, displaying a
267 /// wrapped value is likely to result in a heap allocation and copy.
268 ///
269 /// ```rust
270 /// use diagnostic::{Color, Paint, Style};
271 ///
272 /// let inner = format!("{} and {}", Paint::red("Stop"), Paint::green("Go"));
273 /// let wrapping = Style::new(Color::Blue).wrap();
274 ///
275 /// // 'Hey!' will be unstyled, "Stop" will be red, "and" will be blue, and
276 /// // "Go" will be green. Without a wrapping `Paint`, "and" would be
277 /// // unstyled.
278 /// println!("Hey! {}", wrapping.paint(inner));
279 /// ```
280 #[inline]
281 pub fn wrap(mut self) -> Style {
282 self.wrap = true;
283 self
284 }
285
286 style_builder_for!(Style, |style| style.properties,
287 bold: BOLD, dimmed: DIMMED, italic: ITALIC,
288 underline: UNDERLINE, blink: BLINK, invert: INVERT,
289 hidden: HIDDEN, strikethrough: STRIKETHROUGH);
290
291 /// Constructs a new `Paint` structure that encapsulates `item` with the
292 /// style set to `self`.
293 ///
294 /// ```rust
295 /// use diagnostic::{Color, Style};
296 ///
297 /// let alert = Style::new(Color::Red).bold().underline();
298 /// println!("Alert: {}", alert.paint("This thing happened!"));
299 /// ```
300 #[inline]
301 pub fn paint<T>(self, item: T) -> Paint<T> {
302 Paint::new(item).with_style(self)
303 }
304
305 /// Returns the foreground color of `self`.
306 ///
307 /// ```rust
308 /// use diagnostic::{Color, Style};
309 ///
310 /// let plain = Style::default();
311 /// assert_eq!(plain.fg_color(), Color::Unset);
312 ///
313 /// let red = plain.fg(Color::Red);
314 /// assert_eq!(red.fg_color(), Color::Red);
315 /// ```
316 #[inline]
317 pub fn fg_color(&self) -> Color {
318 self.foreground
319 }
320
321 /// Returns the foreground color of `self`.
322 ///
323 /// ```rust
324 /// use diagnostic::{Color, Style};
325 ///
326 /// let plain = Style::default();
327 /// assert_eq!(plain.bg_color(), Color::Unset);
328 ///
329 /// let white = plain.bg(Color::White);
330 /// assert_eq!(white.bg_color(), Color::White);
331 /// ```
332 #[inline]
333 pub fn bg_color(&self) -> Color {
334 self.background
335 }
336
337 /// Returns `true` if `self` is masked.
338 ///
339 /// ```rust
340 /// use diagnostic::Style;
341 ///
342 /// let plain = Style::default();
343 /// assert!(!plain.is_masked());
344 ///
345 /// let masked = plain.mask();
346 /// assert!(masked.is_masked());
347 /// ```
348 #[inline]
349 pub fn is_masked(&self) -> bool {
350 self.masked
351 }
352
353 /// Returns `true` if `self` is wrapping.
354 ///
355 /// ```rust
356 /// use diagnostic::Style;
357 ///
358 /// let plain = Style::default();
359 /// assert!(!plain.is_wrapping());
360 ///
361 /// let wrapping = plain.wrap();
362 /// assert!(wrapping.is_wrapping());
363 /// ```
364 #[inline]
365 pub fn is_wrapping(&self) -> bool {
366 self.wrap
367 }
368
369 checker_for!(bold (is_bold): BOLD, dimmed (is_dimmed): DIMMED,
370 italic (is_italic): ITALIC, underline (is_underline): UNDERLINE,
371 blink (is_blink): BLINK, invert (is_invert): INVERT,
372 hidden (is_hidden): HIDDEN,
373 strikethrough (is_strikethrough): STRIKETHROUGH);
374
375 #[inline(always)]
376 fn is_plain(&self) -> bool {
377 self == &Style::default()
378 }
379
380 /// Writes the ANSI code prefix for the currently set styles.
381 ///
382 /// This method is intended to be used inside of [`fmt::Display`] and
383 /// [`fmt::Debug`] implementations for custom or specialized use-cases. Most
384 /// users should use [`Paint`] for all painting needs.
385 ///
386 /// This method writes the ANSI code prefix irrespective of whether painting
387 /// is currently enabled or disabled. To write the prefix only if painting
388 /// is enabled, condition a call to this method on [`Paint::is_enabled()`].
389 ///
390 /// [`fmt::Display`]: fmt::Display
391 /// [`fmt::Debug`]: fmt::Debug
392 /// [`Paint`]: Paint
393 /// [`Paint::is_enabled()`]: Paint::is_enabled()
394 ///
395 /// # Example
396 ///
397 /// ```rust
398 /// use diagnostic::Style;
399 /// use std::fmt;
400 ///
401 /// struct CustomItem {
402 /// item: u32,
403 /// style: Style,
404 /// }
405 ///
406 /// impl fmt::Display for CustomItem {
407 /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
408 /// self.style.fmt_prefix(f)?;
409 /// write!(f, "number: {}", self.item)?;
410 /// self.style.fmt_suffix(f)
411 /// }
412 /// }
413 /// ```
414 pub fn fmt_prefix(&self, f: &mut impl std::fmt::Write) -> fmt::Result {
415 // A user may just want a code-free string when no styles are applied.
416 if self.is_plain() {
417 return Ok(());
418 }
419
420 let mut splice = false;
421 write!(f, "\x1B[")?;
422
423 for i in self.properties.iter() {
424 let k = if i >= 5 { i + 2 } else { i + 1 };
425 write_spliced(&mut splice, f, k)?;
426 }
427
428 if self.background != Color::Unset {
429 write_spliced(&mut splice, f, "4")?;
430 self.background.ascii_fmt(f)?;
431 }
432
433 if self.foreground != Color::Unset {
434 write_spliced(&mut splice, f, "3")?;
435 self.foreground.ascii_fmt(f)?;
436 }
437
438 // All the codes end with an `m`.
439 write!(f, "m")
440 }
441
442 /// Writes the ANSI code suffix for the currently set styles.
443 ///
444 /// This method is intended to be used inside of [`fmt::Display`] and
445 /// [`fmt::Debug`] implementations for custom or specialized use-cases. Most
446 /// users should use [`Paint`] for all painting needs.
447 ///
448 /// This method writes the ANSI code suffix irrespective of whether painting
449 /// is currently enabled or disabled. To write the suffix only if painting
450 /// is enabled, condition a call to this method on [`Paint::is_enabled()`].
451 ///
452 /// [`fmt::Display`]: fmt::Display
453 /// [`fmt::Debug`]: fmt::Debug
454 /// [`Paint`]: Paint
455 /// [`Paint::is_enabled()`]: Paint::is_enabled()
456 ///
457 /// # Example
458 ///
459 /// ```rust
460 /// use diagnostic::Style;
461 /// use std::fmt;
462 ///
463 /// struct CustomItem {
464 /// item: u32,
465 /// style: Style,
466 /// }
467 ///
468 /// impl fmt::Display for CustomItem {
469 /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
470 /// self.style.fmt_prefix(f)?;
471 /// write!(f, "number: {}", self.item)?;
472 /// self.style.fmt_suffix(f)
473 /// }
474 /// }
475 /// ```
476 pub fn fmt_suffix(&self, f: &mut impl std::fmt::Write) -> fmt::Result {
477 if self.is_plain() {
478 return Ok(());
479 }
480
481 write!(f, "\x1B[0m")
482 }
483}