1/// Terminal-styling container
2///
3/// For now, this is the same as a [`Str`][crate::builder::Str].  This exists to reserve space in
4/// the API for exposing terminal styling.
5#[derive(Clone, Default, Debug, PartialEq, Eq)]
6pub struct StyledStr {
7    #[cfg(feature = "color")]
8    pieces: Vec<(Option<Style>, String)>,
9    #[cfg(not(feature = "color"))]
10    pieces: String,
11}
12
13impl StyledStr {
14    /// Create an empty buffer
15    #[cfg(feature = "color")]
16    pub const fn new() -> Self {
17        Self { pieces: Vec::new() }
18    }
19
20    /// Create an empty buffer
21    #[cfg(not(feature = "color"))]
22    pub const fn new() -> Self {
23        Self {
24            pieces: String::new(),
25        }
26    }
27
28    /// Display using [ANSI Escape Code](https://en.wikipedia.org/wiki/ANSI_escape_code) styling
29    #[cfg(feature = "color")]
30    pub fn ansi(&self) -> impl std::fmt::Display + '_ {
31        AnsiDisplay { styled: self }
32    }
33
34    pub(crate) fn header(&mut self, msg: impl Into<String>) {
35        self.stylize_(Some(Style::Header), msg.into());
36    }
37
38    pub(crate) fn literal(&mut self, msg: impl Into<String>) {
39        self.stylize_(Some(Style::Literal), msg.into());
40    }
41
42    pub(crate) fn placeholder(&mut self, msg: impl Into<String>) {
43        self.stylize_(Some(Style::Placeholder), msg.into());
44    }
45
46    #[cfg_attr(not(feature = "error-context"), allow(dead_code))]
47    pub(crate) fn good(&mut self, msg: impl Into<String>) {
48        self.stylize_(Some(Style::Good), msg.into());
49    }
50
51    #[cfg_attr(not(feature = "error-context"), allow(dead_code))]
52    pub(crate) fn warning(&mut self, msg: impl Into<String>) {
53        self.stylize_(Some(Style::Warning), msg.into());
54    }
55
56    pub(crate) fn error(&mut self, msg: impl Into<String>) {
57        self.stylize_(Some(Style::Error), msg.into());
58    }
59
60    #[allow(dead_code)]
61    pub(crate) fn hint(&mut self, msg: impl Into<String>) {
62        self.stylize_(Some(Style::Hint), msg.into());
63    }
64
65    pub(crate) fn none(&mut self, msg: impl Into<String>) {
66        self.stylize_(None, msg.into());
67    }
68
69    pub(crate) fn stylize(&mut self, style: impl Into<Option<Style>>, msg: impl Into<String>) {
70        self.stylize_(style.into(), msg.into());
71    }
72
73    pub(crate) fn trim(&mut self) {
74        self.trim_start();
75        self.trim_end();
76    }
77
78    pub(crate) fn trim_start(&mut self) {
79        if let Some((_, item)) = self.iter_mut().next() {
80            *item = item.trim_start().to_owned();
81        }
82    }
83
84    #[cfg(feature = "color")]
85    pub(crate) fn trim_end(&mut self) {
86        if let Some((_, item)) = self.pieces.last_mut() {
87            *item = item.trim_end().to_owned();
88        }
89    }
90
91    #[cfg(not(feature = "color"))]
92    pub(crate) fn trim_end(&mut self) {
93        self.pieces = self.pieces.trim_end().to_owned();
94    }
95
96    #[cfg(feature = "help")]
97    pub(crate) fn indent(&mut self, initial: &str, trailing: &str) {
98        if let Some((_, first)) = self.iter_mut().next() {
99            first.insert_str(0, initial);
100        }
101        let mut line_sep = "\n".to_owned();
102        line_sep.push_str(trailing);
103        for (_, content) in self.iter_mut() {
104            *content = content.replace('\n', &line_sep);
105        }
106    }
107
108    #[cfg(all(not(feature = "wrap_help"), feature = "help"))]
109    pub(crate) fn wrap(&mut self, _hard_width: usize) {}
110
111    #[cfg(feature = "wrap_help")]
112    pub(crate) fn wrap(&mut self, hard_width: usize) {
113        let mut wrapper = crate::output::textwrap::wrap_algorithms::LineWrapper::new(hard_width);
114        for (_, content) in self.iter_mut() {
115            let mut total = Vec::new();
116            for (i, line) in content.split_inclusive('\n').enumerate() {
117                if 0 < i {
118                    // start of a section does not imply newline
119                    wrapper.reset();
120                }
121                let line = crate::output::textwrap::word_separators::find_words_ascii_space(line)
122                    .collect::<Vec<_>>();
123                total.extend(wrapper.wrap(line));
124            }
125            let total = total.join("");
126            *content = total;
127        }
128
129        self.trim_end();
130    }
131
132    #[cfg(feature = "color")]
133    fn stylize_(&mut self, style: Option<Style>, msg: String) {
134        if !msg.is_empty() {
135            self.pieces.push((style, msg));
136        }
137    }
138
139    #[cfg(not(feature = "color"))]
140    fn stylize_(&mut self, _style: Option<Style>, msg: String) {
141        self.pieces.push_str(&msg);
142    }
143
144    #[inline(never)]
145    #[cfg(feature = "help")]
146    pub(crate) fn display_width(&self) -> usize {
147        let mut width = 0;
148        for (_, c) in self.iter() {
149            width += crate::output::display_width(c);
150        }
151        width
152    }
153
154    #[cfg(feature = "help")]
155    pub(crate) fn is_empty(&self) -> bool {
156        self.pieces.is_empty()
157    }
158
159    #[cfg(feature = "color")]
160    pub(crate) fn iter(&self) -> impl Iterator<Item = (Option<Style>, &str)> {
161        self.pieces.iter().map(|(s, c)| (*s, c.as_str()))
162    }
163
164    #[cfg(not(feature = "color"))]
165    pub(crate) fn iter(&self) -> impl Iterator<Item = (Option<Style>, &str)> {
166        [(None, self.pieces.as_str())].into_iter()
167    }
168
169    #[cfg(feature = "color")]
170    pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = (Option<Style>, &mut String)> {
171        self.pieces.iter_mut().map(|(s, c)| (*s, c))
172    }
173
174    #[cfg(not(feature = "color"))]
175    pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = (Option<Style>, &mut String)> {
176        [(None, &mut self.pieces)].into_iter()
177    }
178
179    #[cfg(feature = "color")]
180    pub(crate) fn into_iter(self) -> impl Iterator<Item = (Option<Style>, String)> {
181        self.pieces.into_iter()
182    }
183
184    #[cfg(not(feature = "color"))]
185    pub(crate) fn into_iter(self) -> impl Iterator<Item = (Option<Style>, String)> {
186        [(None, self.pieces)].into_iter()
187    }
188
189    pub(crate) fn extend(
190        &mut self,
191        other: impl IntoIterator<Item = (impl Into<Option<Style>>, impl Into<String>)>,
192    ) {
193        for (style, content) in other {
194            self.stylize(style.into(), content.into());
195        }
196    }
197
198    #[cfg(feature = "color")]
199    pub(crate) fn write_colored(&self, buffer: &mut termcolor::Buffer) -> std::io::Result<()> {
200        use std::io::Write;
201        use termcolor::WriteColor;
202
203        for (style, content) in &self.pieces {
204            let mut color = termcolor::ColorSpec::new();
205            match style {
206                Some(Style::Header) => {
207                    color.set_bold(true);
208                    color.set_underline(true);
209                }
210                Some(Style::Literal) => {
211                    color.set_bold(true);
212                }
213                Some(Style::Placeholder) => {}
214                Some(Style::Good) => {
215                    color.set_fg(Some(termcolor::Color::Green));
216                }
217                Some(Style::Warning) => {
218                    color.set_fg(Some(termcolor::Color::Yellow));
219                }
220                Some(Style::Error) => {
221                    color.set_fg(Some(termcolor::Color::Red));
222                    color.set_bold(true);
223                }
224                Some(Style::Hint) => {
225                    color.set_dimmed(true);
226                }
227                None => {}
228            }
229
230            ok!(buffer.set_color(&color));
231            ok!(buffer.write_all(content.as_bytes()));
232            ok!(buffer.reset());
233        }
234
235        Ok(())
236    }
237}
238
239impl Default for &'_ StyledStr {
240    fn default() -> Self {
241        static DEFAULT: StyledStr = StyledStr::new();
242        &DEFAULT
243    }
244}
245
246impl From<std::string::String> for StyledStr {
247    fn from(name: std::string::String) -> Self {
248        let mut styled = StyledStr::new();
249        styled.none(name);
250        styled
251    }
252}
253
254impl From<&'_ std::string::String> for StyledStr {
255    fn from(name: &'_ std::string::String) -> Self {
256        let mut styled = StyledStr::new();
257        styled.none(name);
258        styled
259    }
260}
261
262impl From<&'static str> for StyledStr {
263    fn from(name: &'static str) -> Self {
264        let mut styled = StyledStr::new();
265        styled.none(name);
266        styled
267    }
268}
269
270impl From<&'_ &'static str> for StyledStr {
271    fn from(name: &'_ &'static str) -> Self {
272        StyledStr::from(*name)
273    }
274}
275
276impl PartialOrd for StyledStr {
277    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
278        Some(self.cmp(other))
279    }
280}
281
282impl Ord for StyledStr {
283    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
284        self.iter().map(cmp_key).cmp(other.iter().map(cmp_key))
285    }
286}
287
288fn cmp_key(c: (Option<Style>, &str)) -> (Option<usize>, &str) {
289    let style = c.0.map(|s| s.as_usize());
290    let content = c.1;
291    (style, content)
292}
293
294/// Color-unaware printing. Never uses coloring.
295impl std::fmt::Display for StyledStr {
296    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
297        for (_, content) in self.iter() {
298            ok!(std::fmt::Display::fmt(content, f));
299        }
300
301        Ok(())
302    }
303}
304
305#[cfg(feature = "color")]
306struct AnsiDisplay<'s> {
307    styled: &'s StyledStr,
308}
309
310#[cfg(feature = "color")]
311impl std::fmt::Display for AnsiDisplay<'_> {
312    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
313        let mut buffer = termcolor::Buffer::ansi();
314        ok!(self
315            .styled
316            .write_colored(&mut buffer)
317            .map_err(|_| std::fmt::Error));
318        let buffer = buffer.into_inner();
319        let buffer = ok!(String::from_utf8(buffer).map_err(|_| std::fmt::Error));
320        ok!(std::fmt::Display::fmt(&buffer, f));
321
322        Ok(())
323    }
324}
325
326#[derive(Copy, Clone, Debug, PartialEq, Eq)]
327pub(crate) enum Style {
328    Header,
329    Literal,
330    Placeholder,
331    Good,
332    Warning,
333    Error,
334    Hint,
335}
336
337impl Style {
338    fn as_usize(&self) -> usize {
339        match self {
340            Self::Header => 0,
341            Self::Literal => 1,
342            Self::Placeholder => 2,
343            Self::Good => 3,
344            Self::Warning => 4,
345            Self::Error => 5,
346            Self::Hint => 6,
347        }
348    }
349}
350