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}