text_style/
termion.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 [`termion`][]’s text style types.
5//!
6//! *Requires the `termion` feature.*
7//!
8//! Termion does not use store the text format in generic data types together with the formatted
9//! texts.  Instead, if provides separate commands for all formatting options.  These commands
10//! produce ANSI escape sequencens that can be printed to stdout.
11//!
12//! This module defines the [`Termion`][] trait that is implemented for [`StyledStr`][] and
13//! [`StyledString`][].  Its [`termion`][`Termion::termion`] method produces an instance of the
14//! [`TermionStr`][] struct that can be converted into the escape sequences produced by `termion`
15//! using its [`Display`][] implementation.
16//!
17//! Alternatively, you can use the [`render`][] function to render a single string and the
18//! [`render_iter`][] function to render an iterator over strings.
19//!
20//! Note that this implementation always uses [`termion::style::Reset`][] to clear the formatting
21//! instead of [`termion::style::NoBold`][] etc. for compatibility with terminals that don’t
22//! support the *No Bold* style.
23//!
24//! # Examples
25//!
26//! Rendering a single string:
27//!
28//! ```
29//! let s = text_style::StyledStr::plain("test").bold();
30//! text_style::termion::render(std::io::stdout(), s)
31//!     .expect("Failed to render string");
32//! ```
33//!
34//! Rendering multiple strings:
35//!
36//! ```
37//! let v = vec![
38//!     text_style::StyledStr::plain("test").bold(),
39//!     text_style::StyledStr::plain(" "),
40//!     text_style::StyledStr::plain("test2").italic(),
41//! ];
42//! text_style::termion::render_iter(std::io::stdout(), v.iter())
43//!     .expect("Failed to render string");
44//! ```
45//!
46//! Using the [`Termion`][] trait:
47//!
48//! ```
49//! use text_style::termion::Termion;
50//!
51//! println!("{}", text_style::StyledStr::plain("test").bold().termion());
52//! ```
53//!
54//! [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html
55//! [`termion`]: https://docs.rs/termion
56//! [`termion::style::Reset`]: https://docs.rs/termion/latest/termion/style/struct.Reset.html
57//! [`termion::style::NoBold`]: https://docs.rs/termion/latest/termion/style/struct.NoBold.html
58//! [`StyledStr`]: ../struct.StyledStr.html
59//! [`StyledString`]: ../struct.StyledString.html
60//! [`render`]: fn.render.html
61//! [`render_iter`]: fn.render_iter.html
62//! [`Termion`]: trait.Termion.html
63//! [`Termion::termion`]: trait.Termion.html#tymethod.termion
64//! [`TermionStr`]: struct.TermionStr.html
65
66use std::borrow;
67use std::fmt;
68use std::io;
69
70use termion::{color, style};
71
72use crate::{AnsiColor, AnsiMode, Color, Effect, Style, StyledStr, StyledString};
73
74/// A styled string that can be rendered using `termion`.
75///
76/// The [`Display`][] implementation of this struct produces a formatted string using the escape
77/// sequencens generated by termion.
78///
79/// # Example
80///
81/// ```
82/// use text_style::termion::Termion;
83///
84/// println!("{}", text_style::StyledStr::plain("test").bold().termion());
85/// ```
86pub struct TermionStr<'a> {
87    s: &'a str,
88    style: Option<Style>,
89}
90
91impl<'a> fmt::Display for TermionStr<'a> {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        if let Some(style) = &self.style {
94            if let Some(fg) = style.fg {
95                f.write_str(get_fg(fg).as_ref())?;
96            }
97            if let Some(bg) = style.bg {
98                f.write_str(get_bg(bg).as_ref())?;
99            }
100            for effect in style.effects {
101                f.write_str(get_effect(effect))?;
102            }
103        }
104        f.write_str(self.s)?;
105        if let Some(style) = &self.style {
106            if style.fg.is_some() || style.bg.is_some() || !style.effects.is_empty() {
107                f.write_str(style::Reset.as_ref())?;
108            }
109        }
110        Ok(())
111    }
112}
113
114/// Extension trait for producing formatted strings with `termion`.
115///
116/// # Example
117///
118/// ```
119/// use text_style::termion::Termion;
120///
121/// println!("{}", text_style::StyledStr::plain("test").bold().termion());
122/// ```
123pub trait Termion {
124    /// Convert this string into a [`TermionStr`][] that can be formatted with `termion`.
125    ///
126    /// # Example
127    ///
128    /// ```
129    /// use text_style::termion::Termion;
130    ///
131    /// println!("{}", text_style::StyledStr::plain("test").bold().termion());
132    /// ```
133    ///
134    /// [`TermionStr`]: struct.TermionStr.html
135    fn termion(&self) -> TermionStr<'_>;
136}
137
138impl<'a> Termion for StyledStr<'a> {
139    fn termion(&self) -> TermionStr<'_> {
140        TermionStr {
141            s: self.s,
142            style: self.style,
143        }
144    }
145}
146
147impl Termion for StyledString {
148    fn termion(&self) -> TermionStr<'_> {
149        TermionStr {
150            s: &self.s,
151            style: self.style,
152        }
153    }
154}
155
156fn get_bg(color: Color) -> borrow::Cow<'static, str> {
157    match color {
158        Color::Ansi { color, mode } => get_ansi_bg(color, mode).into(),
159        Color::Rgb { r, g, b } => color::Rgb(r, g, b).bg_string().into(),
160    }
161}
162
163fn get_ansi_bg(color: AnsiColor, mode: AnsiMode) -> &'static str {
164    use AnsiColor::*;
165    use AnsiMode::*;
166
167    match (mode, color) {
168        (Dark, Black) => color::Black.bg_str(),
169        (Dark, Red) => color::Red.bg_str(),
170        (Dark, Green) => color::Green.bg_str(),
171        (Dark, Yellow) => color::Yellow.bg_str(),
172        (Dark, Blue) => color::Blue.bg_str(),
173        (Dark, Magenta) => color::Magenta.bg_str(),
174        (Dark, Cyan) => color::Cyan.bg_str(),
175        (Dark, White) => color::White.bg_str(),
176        (Light, Black) => color::LightBlack.bg_str(),
177        (Light, Red) => color::LightRed.bg_str(),
178        (Light, Green) => color::LightGreen.bg_str(),
179        (Light, Yellow) => color::LightYellow.bg_str(),
180        (Light, Blue) => color::LightBlue.bg_str(),
181        (Light, Magenta) => color::LightMagenta.bg_str(),
182        (Light, Cyan) => color::LightCyan.bg_str(),
183        (Light, White) => color::LightWhite.bg_str(),
184    }
185}
186
187fn get_fg(color: Color) -> borrow::Cow<'static, str> {
188    match color {
189        Color::Ansi { color, mode } => get_ansi_fg(color, mode).into(),
190        Color::Rgb { r, g, b } => color::Rgb(r, g, b).fg_string().into(),
191    }
192}
193
194fn get_ansi_fg(color: AnsiColor, mode: AnsiMode) -> &'static str {
195    use AnsiColor::*;
196    use AnsiMode::*;
197
198    match (mode, color) {
199        (Dark, Black) => color::Black.fg_str(),
200        (Dark, Red) => color::Red.fg_str(),
201        (Dark, Green) => color::Green.fg_str(),
202        (Dark, Yellow) => color::Yellow.fg_str(),
203        (Dark, Blue) => color::Blue.fg_str(),
204        (Dark, Magenta) => color::Magenta.fg_str(),
205        (Dark, Cyan) => color::Cyan.fg_str(),
206        (Dark, White) => color::White.fg_str(),
207        (Light, Black) => color::LightBlack.fg_str(),
208        (Light, Red) => color::LightRed.fg_str(),
209        (Light, Green) => color::LightGreen.fg_str(),
210        (Light, Yellow) => color::LightYellow.fg_str(),
211        (Light, Blue) => color::LightBlue.fg_str(),
212        (Light, Magenta) => color::LightMagenta.fg_str(),
213        (Light, Cyan) => color::LightCyan.fg_str(),
214        (Light, White) => color::LightWhite.fg_str(),
215    }
216}
217
218fn get_effect(effect: Effect) -> &'static str {
219    match effect {
220        Effect::Bold => style::Bold.as_ref(),
221        Effect::Italic => style::Italic.as_ref(),
222        Effect::Underline => style::Underline.as_ref(),
223        Effect::Strikethrough => style::CrossedOut.as_ref(),
224    }
225}
226
227/// Renders a styled string to the given output using `termion`.
228///
229/// # Example
230///
231/// ```
232/// let s = text_style::StyledStr::plain("test").bold();
233/// text_style::termion::render(std::io::stdout(), s)
234///     .expect("Failed to render string");
235/// ```
236pub fn render<'a>(mut w: impl io::Write, s: impl Into<StyledStr<'a>>) -> io::Result<()> {
237    write!(w, "{}", s.into().termion())
238}
239
240/// Renders multiple styled string to the given output using `termion`.
241///
242/// # Example
243///
244/// ```
245/// let v = vec![
246///     text_style::StyledStr::plain("test").bold(),
247///     text_style::StyledStr::plain(" "),
248///     text_style::StyledStr::plain("test2").italic(),
249/// ];
250/// text_style::termion::render_iter(std::io::stdout(), v.iter())
251///     .expect("Failed to render string");
252/// ```
253pub fn render_iter<'a, I, Iter, S, W>(mut w: W, iter: I) -> io::Result<()>
254where
255    I: IntoIterator<Item = S, IntoIter = Iter>,
256    Iter: Iterator<Item = S>,
257    S: Into<StyledStr<'a>>,
258    W: io::Write,
259{
260    for s in iter {
261        write!(w, "{}", s.into().termion())?;
262    }
263    Ok(())
264}