text_style/
crossterm.rs

1// SPDX-FileCopyrightText: 2020 Robin Krahl <robin.krahl@ireas.org>
2// SPDX-License-Identifier: Apache-2.0 or MIT
3
4//! Conversion methods for [`crossterm`][]’s text style types.
5//!
6//! *Requires the `crossterm` feature.*
7//!
8//! This module implements these conversions:
9//! - [`Color`][] to [`crossterm::style::Color`][]
10//! - [`Effect`][] to [`crossterm::style::Attribute`][]
11//! - [`Effects`][] to [`crossterm::style::Attributes`][]
12//! - [`Style`][] to [`crossterm::style::ContentStyle`][]
13//! - [`StyledStr`][] and [`StyledString`][] to [`crossterm::style::StyledContent`][]
14//!
15//! It also provides the [`render`][] and [`render_iter`][] methods to render strings and iterators
16//! over strings.
17//!
18//! # Examples
19//!
20//! Rendering a single string:
21//!
22//! ```
23//! let s = text_style::StyledStr::plain("test").bold();
24//! text_style::crossterm::render(std::io::stdout(), s)
25//!     .expect("Failed to render string");
26//! ```
27//!
28//! Rendering multiple strings:
29//!
30//! ```
31//! let v = vec![
32//!     text_style::StyledStr::plain("test").bold(),
33//!     text_style::StyledStr::plain(" "),
34//!     text_style::StyledStr::plain("test2").italic(),
35//! ];
36//! text_style::crossterm::render_iter(std::io::stdout(), v.iter())
37//!     .expect("Failed to render string");
38//! ```
39//!
40//! [`crossterm`]: https://docs.rs/crossterm
41//! [`crossterm::style::Attribute`]: https://docs.rs/crossterm/latest/crossterm/style/enum.Attribute.html
42//! [`crossterm::style::Attributes`]: https://docs.rs/crossterm/latest/crossterm/style/struct.Attributes.html
43//! [`crossterm::style::Color`]: https://docs.rs/crossterm/latest/crossterm/style/enum.Color.html
44//! [`crossterm::style::ContentStyle`]: https://docs.rs/crossterm/latest/crossterm/style/struct.ContentStyle.html
45//! [`crossterm::style::StyledContent`]: https://docs.rs/crossterm/latest/crossterm/style/struct.StyledContent.html
46//! [`Color`]: ../enum.Color.html
47//! [`Effect`]: ../enum.Effect.html
48//! [`Effects`]: ../struct.Effects.html
49//! [`Style`]: ../struct.Style.html
50//! [`StyledStr`]: ../struct.StyledStr.html
51//! [`StyledString`]: ../struct.StyledString.html
52//! [`render`]: fn.render.html
53//! [`render_iter`]: fn.render_iter.html
54
55use std::io;
56
57use crossterm::style;
58
59use crate::{AnsiColor, AnsiMode, Color, Effect, Effects, Style, StyledStr, StyledString};
60
61impl From<Color> for style::Color {
62    fn from(color: Color) -> style::Color {
63        use AnsiColor::*;
64        use AnsiMode::*;
65
66        match color {
67            Color::Ansi { color, mode } => match (mode, color) {
68                (Dark, Black) => style::Color::Black,
69                (Dark, Red) => style::Color::DarkRed,
70                (Dark, Green) => style::Color::DarkGreen,
71                (Dark, Yellow) => style::Color::DarkYellow,
72                (Dark, Blue) => style::Color::DarkBlue,
73                (Dark, Magenta) => style::Color::DarkMagenta,
74                (Dark, Cyan) => style::Color::DarkCyan,
75                // TODO: check greys
76                (Dark, White) => style::Color::Grey,
77                (Light, Black) => style::Color::DarkGrey,
78                (Light, Red) => style::Color::Red,
79                (Light, Green) => style::Color::Green,
80                (Light, Yellow) => style::Color::Yellow,
81                (Light, Blue) => style::Color::Blue,
82                (Light, Magenta) => style::Color::Magenta,
83                (Light, Cyan) => style::Color::Cyan,
84                (Light, White) => style::Color::White,
85            },
86            Color::Rgb { r, g, b } => style::Color::Rgb { r, g, b },
87        }
88    }
89}
90
91impl From<Effect> for style::Attribute {
92    fn from(effect: Effect) -> style::Attribute {
93        match effect {
94            Effect::Bold => style::Attribute::Bold,
95            Effect::Italic => style::Attribute::Italic,
96            Effect::Underline => style::Attribute::Underlined,
97            Effect::Strikethrough => style::Attribute::CrossedOut,
98        }
99    }
100}
101
102impl From<Effects> for style::Attributes {
103    fn from(effects: Effects) -> style::Attributes {
104        let mut attributes = style::Attributes::default();
105        for effect in effects {
106            attributes.set(effect.into());
107        }
108        attributes
109    }
110}
111
112impl From<Style> for style::ContentStyle {
113    fn from(style: Style) -> style::ContentStyle {
114        style::ContentStyle {
115            foreground_color: style.fg.map(Into::into),
116            background_color: style.bg.map(Into::into),
117            attributes: style.effects.into(),
118        }
119    }
120}
121
122impl<'a, 'b> From<&'b StyledStr<'a>> for style::StyledContent<&'a str> {
123    fn from(s: &'b StyledStr<'a>) -> style::StyledContent<&'a str> {
124        style::StyledContent::new(s.style.map(Into::into).unwrap_or_default(), s.s)
125    }
126}
127
128impl<'a> From<StyledStr<'a>> for style::StyledContent<&'a str> {
129    fn from(s: StyledStr<'a>) -> style::StyledContent<&'a str> {
130        style::StyledContent::new(s.style.map(Into::into).unwrap_or_default(), s.s)
131    }
132}
133
134impl From<StyledString> for style::StyledContent<String> {
135    fn from(s: StyledString) -> style::StyledContent<String> {
136        style::StyledContent::new(s.style.map(Into::into).unwrap_or_default(), s.s)
137    }
138}
139
140/// Renders a styled string to the given output using `crossterm`.
141///
142/// # Example
143///
144/// ```
145/// let s = text_style::StyledStr::plain("test").bold();
146/// text_style::crossterm::render(std::io::stdout(), s)
147///     .expect("Failed to render string");
148/// ```
149pub fn render<'a>(mut w: impl io::Write, s: impl Into<StyledStr<'a>>) -> crossterm::Result<()> {
150    use crossterm::ExecutableCommand;
151
152    w.execute(crossterm::style::PrintStyledContent(s.into().into()))
153        .map(|_| {})
154}
155
156/// Renders multiple styled string to the given output using `crossterm`.
157///
158/// This function queues the draw commands, so the output has to be flushed by the caller.
159///
160/// # Example
161///
162/// ```
163/// let v = vec![
164///     text_style::StyledStr::plain("test").bold(),
165///     text_style::StyledStr::plain(" "),
166///     text_style::StyledStr::plain("test2").italic(),
167/// ];
168/// text_style::crossterm::render_iter(std::io::stdout(), v.iter())
169///     .expect("Failed to render string");
170/// ```
171pub fn render_iter<'a, I, Iter, S, W>(mut w: W, iter: I) -> crossterm::Result<()>
172where
173    I: IntoIterator<Item = S, IntoIter = Iter>,
174    Iter: Iterator<Item = S>,
175    S: Into<StyledStr<'a>>,
176    W: io::Write,
177{
178    use crossterm::QueueableCommand;
179
180    for s in iter {
181        w.queue(crossterm::style::PrintStyledContent(s.into().into()))?;
182    }
183    Ok(())
184}