text_style/
lib.rs

1// SPDX-FileCopyrightText: 2020 Robin Krahl <robin.krahl@ireas.org>
2// SPDX-License-Identifier: Apache-2.0 or MIT
3
4//! The `text_style` crate provides types and conversions for styled text.
5//!
6//! # Overview
7//!
8//! The central types of this crate are [`StyledStr`][] and [`StyledString`][]:  owned and borrowed
9//! strings that are annotated with an optional style information, [`Style`][].  This style
10//! information consists of foreground and background colors ([`Color`][]) and multiple effects
11//! ([`Effect`][]: bold, italic, underline or strikeout).
12//!
13//! `text_style`’s types can be created directly or converted from or to several formats (all
14//! optional and activated by features):
15//!
16//! - [`ansi_term`][]: convert to [`ansi_term::ANSIString`][]
17//! - [`crossterm`][]: convert to [`crossterm::style::StyledContent`][]
18//! - [`cursive`][]: convert to [`cursive::utils::markup::StyledString`][]
19//! - [`genpdf`][]: convert to [`genpdf::style::StyledStr`][] and [`genpdf::style::StyledString`][]
20//! - [`syntect`][]: convert from [`syntect::highlighting::Style`][]
21//! - [`termion`][]: convert to a termion escape string
22//!
23//! # Background
24//!
25//! There is a plethora of crates that produce or consume styled text.  Most of these crates use
26//! very similar types: ANSI and RGB colors, text effects, styled strings.  But converting between
27//! these types requires a lot of boilerplate code.  The goal of this crate is to provide a subset
28//! of common style types to enable conversion between the different crates.
29//!
30//! # Usage
31//!
32//! ## Creating styled text
33//!
34//! The [`StyledString`][] and [`StyledStr`][] structs provide many utility methods for creating
35//! styled strings easily:
36//!
37//! ```
38//! use text_style::{AnsiColor, Effect, StyledStr};
39//! let s = StyledStr::plain("text")
40//!     .with(AnsiColor::Red.light())
41//!     .on(AnsiColor::Green.dark())
42//!     .bold();
43//! text_style::ansi_term::render(std::io::stdout(), s)
44//!     .expect("Could not render line");
45//! ```
46//!
47//! If the `syntect` feature is activated, conversion traits from `syntect`’s style types are
48//! implemented:
49//!
50//! ```
51//! use syntect::{easy, parsing, highlighting, util};
52//!
53//! let ps = parsing::SyntaxSet::load_defaults_newlines();
54//! let ts = highlighting::ThemeSet::load_defaults();
55//!
56//! let syntax = ps.find_syntax_by_extension("rs").unwrap();
57//! let mut h = easy::HighlightLines::new(syntax, &ts.themes["base16-ocean.dark"]);
58//! let s = "pub struct Wow { hi: u64 }\nfn blah() -> u64 {}";
59//! for line in util::LinesWithEndings::from(s) {
60//!     let ranges: Vec<(highlighting::Style, &str)> = h.highlight(line, &ps);
61//!     text_style::ansi_term::render_iter(std::io::stdout(), ranges.iter())
62//!         .expect("Could not render line");
63//! }
64//! ```
65//!
66//! ## Rendering styled text
67//!
68//! The backends define conversion traits from or to the `text_style` types where applicable.
69//!
70//! Most backends also define `render` and `render_iter` methods to display a styled string or an
71//! iterator over styled strings:
72//!
73//! ```
74//! let s = text_style::StyledStr::plain("test").bold();
75//!
76//! let mut w = std::io::stdout();
77//! text_style::ansi_term::render(&mut w, &s).expect("Rendering failed");
78//! text_style::crossterm::render(&mut w, &s).expect("Rendering failed");
79//! text_style::termion::render(&mut w, &s).expect("Rendering failed");
80//! ```
81//!
82//! For more information, see the module documentations.
83//!
84//! [`Color`]: enum.Color.html
85//! [`Effect`]: enum.Effect.html
86//! [`Style`]: struct.Style.html
87//! [`StyledStr`]: struct.StyledStr.html
88//! [`StyledString`]: struct.StyledString.html
89//! [`ansi_term`]: ./ansi_term/index.html
90//! [`crossterm`]: ./crossterm/index.html
91//! [`cursive`]: ./cursive/index.html
92//! [`genpdf`]: ./genpdf/index.html
93//! [`syntect`]: ./syntect/index.html
94//! [`termion`]: ./termion/index.html
95//! [`ansi_term::ANSIString`]: https://docs.rs/ansi_term/latest/ansi_term/type.ANSIString.html
96//! [`crossterm::style::StyledContent`]: https://docs.rs/crossterm/latest/crossterm/style/struct.StyledContent.html
97//! [`cursive::utils::markup::StyledString`]: https://docs.rs/cursive/latest/cursive/utils/markup/type.StyledString.html
98//! [`genpdf::style::StyledStr`]: https://docs.rs/genpdf/latest/genpdf/style/struct.StyledStr.html
99//! [`genpdf::style::StyledString`]: https://docs.rs/genpdf/latest/genpdf/style/struct.StyledString.html
100//! [`syntect::highlighting::Style`]: https://docs.rs/syntect/latest/syntect/highlighting/struct.Style.html
101
102#![warn(missing_docs, rust_2018_idioms)]
103
104#[cfg(feature = "ansi_term")]
105pub mod ansi_term;
106#[cfg(feature = "crossterm")]
107pub mod crossterm;
108#[cfg(feature = "cursive")]
109pub mod cursive;
110#[cfg(feature = "genpdf")]
111pub mod genpdf;
112#[cfg(feature = "syntect")]
113pub mod syntect;
114#[cfg(feature = "termion")]
115pub mod termion;
116
117/// A borrowed string with an optional style annotation.
118///
119/// # Example
120///
121/// ```
122/// let s = text_style::StyledStr::plain("test").bold();
123///
124/// let s1 = text_style::StyledStr::styled("test", text_style::Style::fg(text_style::AnsiColor::Red.dark()));
125/// let s2 = text_style::StyledStr::plain("test").with(text_style::AnsiColor::Red.dark());
126/// assert_eq!(s1, s2);
127/// ```
128#[derive(Clone, Debug, PartialEq)]
129pub struct StyledStr<'a> {
130    /// The content of this string.
131    pub s: &'a str,
132    /// The style of this string.
133    pub style: Option<Style>,
134}
135
136/// An owned string with an optional style annotation.
137///
138/// # Example
139///
140/// ```
141/// let s = format!("Some number: {}", 42);
142///
143/// let s0 = text_style::StyledString::plain(s.clone()).bold();
144///
145/// let s1 = text_style::StyledString::styled(s.clone(), text_style::Style::fg(text_style::AnsiColor::Red.dark()));
146/// let s2 = text_style::StyledString::plain(s.clone()).with(text_style::AnsiColor::Red.dark());
147/// assert_eq!(s1, s2);
148/// ```
149#[derive(Clone, Debug, Default, PartialEq)]
150pub struct StyledString {
151    /// The content of this string.
152    pub s: String,
153    /// The style of this string.
154    pub style: Option<Style>,
155}
156
157/// A text style, a combination of a foreground color, a background color and text effects (all
158/// optional).
159#[derive(Clone, Copy, Debug, Default, PartialEq)]
160pub struct Style {
161    /// The foreground color (if set).
162    pub fg: Option<Color>,
163    /// The background color (if set).
164    pub bg: Option<Color>,
165    /// The text effects.
166    pub effects: Effects,
167}
168
169/// A text effect.
170#[derive(Clone, Copy, Debug, PartialEq)]
171pub enum Effect {
172    /// Bold text.
173    Bold,
174    /// Italic text.
175    Italic,
176    /// Underlined text.
177    Underline,
178    /// Struckthrough text.
179    Strikethrough,
180}
181
182/// All available text effects.
183pub const EFFECTS: &[Effect] = &[
184    Effect::Bold,
185    Effect::Italic,
186    Effect::Underline,
187    Effect::Strikethrough,
188];
189
190/// A set of text effects.
191#[derive(Clone, Copy, Debug, Default, PartialEq)]
192pub struct Effects {
193    /// Whether the bold text effect is set.
194    pub is_bold: bool,
195    /// Whether the italic text effect is set.
196    pub is_italic: bool,
197    /// Whether the underline text effect is set.
198    pub is_underline: bool,
199    /// Whether the strikethrough text effect is set.
200    pub is_strikethrough: bool,
201}
202
203/// An iterator over text effects.
204#[derive(Clone, Copy, Debug, PartialEq)]
205pub struct EffectsIter {
206    effects: Effects,
207    i: usize,
208}
209
210/// A color.
211///
212/// This enum stores colors, either as an ANSI color (see [`AnsiColor`][] and [`AnsiMode`][]) or as
213/// an RGB color.
214///
215/// [`AnsiColor`]: enum.AnsiColor.html
216/// [`AnsiMode`]: enum.AnsiMode.html
217#[derive(Clone, Copy, Debug, PartialEq)]
218pub enum Color {
219    /// An ANSI color.
220    Ansi {
221        /// The ANSI base color.
222        color: AnsiColor,
223        /// The variant of the ANSI base color (light or dark).
224        mode: AnsiMode,
225    },
226    /// An RGB color.
227    Rgb {
228        /// The red component.
229        r: u8,
230        /// The green component.
231        g: u8,
232        /// THe blue component.
233        b: u8,
234    },
235}
236
237/// An ANSI base color.
238///
239/// This enum contains the basic eight ANSI colors.  These colors are available in two modes:
240/// [`Dark`][] and [`Light`][].  Combinations of an ANSI color and a mode are stored in the
241/// [`Color`][] enum.
242///
243/// [`Color`]: enum.Color.html
244/// [`Dark`]: enum.AnsiMode.html#variant.Dark
245/// [`Light`]: enum.AnsiMode.html#variant.Light
246#[derive(Clone, Copy, Debug, PartialEq)]
247pub enum AnsiColor {
248    /// Black (ANSI color #0 (dark) or #8 (light)).
249    Black,
250    /// Red (ANSI color #1 (dark) or #9 (light)).
251    Red,
252    /// Green (ANSI color #2 (dark) or #10 (light)).
253    Green,
254    /// Yellow (ANSI color #3 (dark) or #11 (light)).
255    Yellow,
256    /// Blue (ANSI color #4 (dark) or #12 (light)).
257    Blue,
258    /// Magenta (ANSI color #5 (dark) or #13 (light)).
259    Magenta,
260    /// Cyan (ANSI color #6 (dark) or #14 (light)).
261    Cyan,
262    /// White (ANSI color #7 (dark) or #15 (light)).
263    White,
264}
265
266/// An ANSI color mode.
267///
268/// The ANSI base colors, stored in the [`AnsiColor`][] enum, are available in two modes:
269/// [`Dark`][] and [`Light`][].  Combinations of an ANIS color and a mode are stored in the
270/// [`Color`][] enum.
271///
272/// [`AnsiColor`]: enum.AnsiColor.html
273/// [`Color`]: enum.Color.html
274/// [`Dark`]: #variant.Dark
275/// [`Light`]: #variant.Light
276#[derive(Clone, Copy, Debug, PartialEq)]
277pub enum AnsiMode {
278    /// The dark variant of an ANSI color.
279    Dark,
280    /// The light variant of an ANSI color.
281    Light,
282}
283
284impl<'a> StyledStr<'a> {
285    /// Creates a new styled string from the given string and an optional style.
286    pub fn new(s: &'a str, style: Option<Style>) -> StyledStr<'a> {
287        StyledStr { s, style }
288    }
289
290    /// Creates a new styled string from the given string and style.
291    pub fn styled(s: &'a str, style: Style) -> StyledStr<'a> {
292        StyledStr::new(s, Some(style))
293    }
294
295    /// Creates a new styled string from the given string without a style.
296    pub fn plain(s: &'a str) -> StyledStr<'a> {
297        StyledStr::new(s, None)
298    }
299
300    /// Sets the foreground color for this styled string.
301    pub fn with(mut self, fg: Color) -> Self {
302        self.style_mut().fg = Some(fg);
303        self
304    }
305
306    /// Sets the background color for this styled string.
307    pub fn on(mut self, bg: Color) -> Self {
308        self.style_mut().bg = Some(bg);
309        self
310    }
311
312    /// Sets the bold effect for this styled string.
313    pub fn bold(self) -> Self {
314        self.effect(Effect::Bold)
315    }
316
317    /// Sets the italic effect for this styled string.
318    pub fn italic(self) -> Self {
319        self.effect(Effect::Italic)
320    }
321
322    /// Sets the underline effect for this styled string.
323    pub fn underline(self) -> Self {
324        self.effect(Effect::Underline)
325    }
326
327    /// Sets the strikethrough effect for this styled string.
328    pub fn strikethrough(self) -> Self {
329        self.effect(Effect::Strikethrough)
330    }
331
332    /// Sets the given effect for this styled string.
333    pub fn effect(mut self, effect: Effect) -> Self {
334        self.style_mut().effects.set(effect, true);
335        self
336    }
337
338    /// Returns a mutable reference to the style of this string, creating a new style with the
339    /// default settings if the style is currently `None`.
340    pub fn style_mut(&mut self) -> &mut Style {
341        self.style.get_or_insert_with(Default::default)
342    }
343}
344
345impl StyledString {
346    /// Creates a new styled string from the given string and an optional style.
347    pub fn new(s: String, style: Option<Style>) -> StyledString {
348        StyledString { s, style }
349    }
350
351    /// Creates a new styled string from the given string and style.
352    pub fn styled(s: String, style: Style) -> StyledString {
353        StyledString::new(s, Some(style))
354    }
355
356    /// Creates a new styled string from the given string and style.
357    pub fn plain(s: String) -> StyledString {
358        StyledString::new(s, None)
359    }
360
361    /// Sets the foreground color for this styled string.
362    pub fn with(mut self, fg: Color) -> Self {
363        self.style_mut().fg = Some(fg);
364        self
365    }
366
367    /// Sets the background color for this styled string.
368    pub fn on(mut self, bg: Color) -> Self {
369        self.style_mut().bg = Some(bg);
370        self
371    }
372
373    /// Sets the bold effect for this styled string.
374    pub fn bold(self) -> Self {
375        self.effect(Effect::Bold)
376    }
377
378    /// Sets the italic effect for this styled string.
379    pub fn italic(self) -> Self {
380        self.effect(Effect::Italic)
381    }
382
383    /// Sets the underline effect for this styled string.
384    pub fn underline(self) -> Self {
385        self.effect(Effect::Underline)
386    }
387
388    /// Sets the strikethrough effect for this styled string.
389    pub fn strikethrough(self) -> Self {
390        self.effect(Effect::Strikethrough)
391    }
392
393    /// Sets the given effect for this styled string.
394    pub fn effect(mut self, effect: Effect) -> Self {
395        self.style_mut().effects.set(effect, true);
396        self
397    }
398
399    /// Returns a mutable reference to the style of this string, creating a new style with the
400    /// default settings if the style is currently `None`.
401    pub fn style_mut(&mut self) -> &mut Style {
402        self.style.get_or_insert_with(Default::default)
403    }
404}
405
406impl<'a, 'b> From<&'b StyledStr<'a>> for StyledStr<'a> {
407    fn from(s: &'b StyledStr<'a>) -> StyledStr<'a> {
408        StyledStr {
409            s: &s.s,
410            style: s.style,
411        }
412    }
413}
414
415impl<'a> From<&'a StyledString> for StyledStr<'a> {
416    fn from(s: &'a StyledString) -> StyledStr<'a> {
417        StyledStr {
418            s: &s.s,
419            style: s.style,
420        }
421    }
422}
423
424impl<'a> From<StyledStr<'a>> for StyledString {
425    fn from(s: StyledStr<'a>) -> StyledString {
426        StyledString {
427            s: s.s.to_owned(),
428            style: s.style,
429        }
430    }
431}
432
433impl<'a> From<&'a str> for StyledStr<'a> {
434    fn from(s: &'a str) -> StyledStr<'a> {
435        StyledStr::plain(s)
436    }
437}
438
439impl From<String> for StyledString {
440    fn from(s: String) -> StyledString {
441        StyledString::plain(s)
442    }
443}
444
445impl Style {
446    /// Creates a new style with the given foreground and background colors and effects.
447    pub fn new(fg: Option<Color>, bg: Option<Color>, effects: Effects) -> Style {
448        Style { fg, bg, effects }
449    }
450
451    /// Creates a new style with the given foreground color.
452    pub fn fg(color: Color) -> Style {
453        Style::new(Some(color), None, Effects::new())
454    }
455
456    /// Creates a new style with the given background color.
457    pub fn bg(color: Color) -> Style {
458        Style::new(None, Some(color), Effects::new())
459    }
460
461    /// Creates a new style with the given text effect.
462    pub fn effect(effect: Effect) -> Style {
463        Style::new(None, None, Effects::only(effect))
464    }
465
466    /// Creates a new style with the given text effects.
467    pub fn effects(effects: Effects) -> Style {
468        Style::new(None, None, effects)
469    }
470
471    /// Combines this style with another style.
472    ///
473    /// If a color is set by both styles, the current color is overwritten.
474    ///
475    /// # Example
476    ///
477    /// ```
478    /// use text_style::{AnsiColor, Effects, Style};
479    ///
480    /// assert_eq!(
481    ///     Style::fg(AnsiColor::Red.dark()).and(Style::bg(AnsiColor::White.dark())),
482    ///     Style::new(Some(AnsiColor::Red.dark()), Some(AnsiColor::White.dark()), Effects::empty()),
483    /// );
484    /// ```
485    pub fn and(mut self, style: Style) -> Style {
486        if let Some(fg) = style.fg {
487            self.fg = Some(fg);
488        }
489        if let Some(bg) = style.bg {
490            self.bg = Some(bg);
491        }
492        self.effects = self.effects.and(style.effects);
493        self
494    }
495
496    /// Sets the foreground color of this style.
497    pub fn set_fg(&mut self, color: Color) {
498        self.fg = Some(color);
499    }
500
501    /// Sets the background color of this style.
502    pub fn set_bg(&mut self, color: Color) {
503        self.bg = Some(color);
504    }
505
506    /// Sets or unsets the bold effect for this style.
507    pub fn set_bold(&mut self, bold: bool) {
508        self.effects.is_bold = bold;
509    }
510
511    /// Sets or unsets the italic effect for this style.
512    pub fn set_italic(&mut self, italic: bool) {
513        self.effects.is_italic = italic;
514    }
515
516    /// Sets or unsets the underline effect for this style.
517    pub fn set_underline(&mut self, underline: bool) {
518        self.effects.is_underline = underline;
519    }
520
521    /// Sets or unsets the strikethrough effect for this style.
522    pub fn strikethrough(&mut self, strikethrough: bool) {
523        self.effects.is_strikethrough = strikethrough;
524    }
525
526    /// Sets or unsets the given effect for this style.
527    pub fn set_effect(&mut self, effect: Effect, set: bool) {
528        self.effects.set(effect, set);
529    }
530}
531
532impl From<Effect> for Style {
533    fn from(effect: Effect) -> Style {
534        Style::effect(effect)
535    }
536}
537
538impl From<Effects> for Style {
539    fn from(effects: Effects) -> Style {
540        Style::effects(effects)
541    }
542}
543
544impl Effects {
545    /// Creates an empty set of text effects.
546    pub fn new() -> Effects {
547        Default::default()
548    }
549
550    /// Creates an empty set of text effects.
551    pub fn empty() -> Effects {
552        Effects::new()
553    }
554
555    /// Creates a set of text effects with only the given effect.
556    pub fn only(effect: Effect) -> Effects {
557        Effects::from(effect)
558    }
559
560    /// Sets or unsets the given text effect.
561    pub fn set(&mut self, effect: Effect, set: bool) {
562        match effect {
563            Effect::Bold => self.is_bold = set,
564            Effect::Italic => self.is_italic = set,
565            Effect::Underline => self.is_underline = set,
566            Effect::Strikethrough => self.is_strikethrough = set,
567        }
568    }
569
570    /// Checks whether the given effect is set.
571    pub fn is_set(&self, effect: Effect) -> bool {
572        match effect {
573            Effect::Bold => self.is_bold,
574            Effect::Italic => self.is_italic,
575            Effect::Underline => self.is_underline,
576            Effect::Strikethrough => self.is_strikethrough,
577        }
578    }
579
580    /// Combines this set of text effects with another set of text effects.
581    pub fn and(&self, other: Effects) -> Effects {
582        Effects {
583            is_bold: self.is_bold || other.is_bold,
584            is_italic: self.is_italic || other.is_italic,
585            is_underline: self.is_underline || other.is_underline,
586            is_strikethrough: self.is_strikethrough || other.is_strikethrough,
587        }
588    }
589
590    /// Checks whether this set of text effects is empty.
591    pub fn is_empty(&self) -> bool {
592        !self.is_bold && !self.is_italic && !self.is_underline && !self.is_strikethrough
593    }
594}
595
596impl std::iter::FromIterator<Effect> for Effects {
597    fn from_iter<I: IntoIterator<Item = Effect>>(iter: I) -> Effects {
598        let mut effects = Effects::new();
599        for effect in iter {
600            effects.set(effect, true);
601        }
602        effects
603    }
604}
605
606impl IntoIterator for Effects {
607    type Item = Effect;
608    type IntoIter = EffectsIter;
609
610    fn into_iter(self) -> EffectsIter {
611        EffectsIter::from(self)
612    }
613}
614
615impl From<Effect> for Effects {
616    fn from(effect: Effect) -> Effects {
617        let mut effects = Effects::new();
618        effects.set(effect, true);
619        effects
620    }
621}
622
623impl Iterator for EffectsIter {
624    type Item = Effect;
625
626    fn next(&mut self) -> Option<Effect> {
627        let mut next = None;
628        while let Some(effect) = EFFECTS.get(self.i) {
629            self.i += 1;
630            if self.effects.is_set(*effect) {
631                next = Some(*effect);
632                break;
633            }
634        }
635        next
636    }
637}
638
639impl From<Effects> for EffectsIter {
640    fn from(effects: Effects) -> EffectsIter {
641        EffectsIter { effects, i: 0 }
642    }
643}
644
645impl AnsiColor {
646    /// Returns the dark variant of this ANSI color.
647    pub fn dark(self) -> Color {
648        Color::Ansi {
649            color: self,
650            mode: AnsiMode::Dark,
651        }
652    }
653
654    /// Returns the light variant of this ANSI color.
655    pub fn light(self) -> Color {
656        Color::Ansi {
657            color: self,
658            mode: AnsiMode::Light,
659        }
660    }
661}