Skip to main content

console/
utils.rs

1use alloc::borrow::Cow;
2use core::{
3    fmt::{self, Debug, Formatter},
4    sync::atomic::{AtomicBool, Ordering},
5};
6use std::env;
7
8use std::sync::OnceLock;
9
10use crate::term::{wants_emoji, Term};
11
12#[cfg(feature = "ansi-parsing")]
13use crate::ansi::AnsiCodeIterator;
14
15fn default_colors_enabled(out: &Term) -> bool {
16    (out.features().colors_supported()
17        && &env::var("CLICOLOR").unwrap_or_else(|_| "1".into()) != "0")
18        || &env::var("CLICOLOR_FORCE").unwrap_or_else(|_| "0".into()) != "0"
19}
20
21fn default_true_colors_enabled(out: &Term) -> bool {
22    out.features().true_colors_supported()
23}
24
25fn stdout_colors() -> &'static AtomicBool {
26    static ENABLED: OnceLock<AtomicBool> = OnceLock::new();
27    ENABLED.get_or_init(|| AtomicBool::new(default_colors_enabled(&Term::stdout())))
28}
29fn stdout_true_colors() -> &'static AtomicBool {
30    static ENABLED: OnceLock<AtomicBool> = OnceLock::new();
31    ENABLED.get_or_init(|| AtomicBool::new(default_true_colors_enabled(&Term::stdout())))
32}
33fn stderr_colors() -> &'static AtomicBool {
34    static ENABLED: OnceLock<AtomicBool> = OnceLock::new();
35    ENABLED.get_or_init(|| AtomicBool::new(default_colors_enabled(&Term::stderr())))
36}
37fn stderr_true_colors() -> &'static AtomicBool {
38    static ENABLED: OnceLock<AtomicBool> = OnceLock::new();
39    ENABLED.get_or_init(|| AtomicBool::new(default_true_colors_enabled(&Term::stderr())))
40}
41
42/// Returns `true` if colors should be enabled for stdout.
43///
44/// This honors the [clicolors spec](http://bixense.com/clicolors/).
45///
46/// * `CLICOLOR != 0`: ANSI colors are supported and should be used when the program isn't piped.
47/// * `CLICOLOR == 0`: Don't output ANSI color escape codes.
48/// * `CLICOLOR_FORCE != 0`: ANSI colors should be enabled no matter what.
49#[inline]
50pub fn colors_enabled() -> bool {
51    stdout_colors().load(Ordering::Relaxed)
52}
53
54/// Returns `true` if true colors should be enabled for stdout.
55#[inline]
56pub fn true_colors_enabled() -> bool {
57    stdout_true_colors().load(Ordering::Relaxed)
58}
59
60/// Forces colorization on or off for stdout.
61///
62/// This overrides the default for the current process and changes the return value of the
63/// `colors_enabled` function.
64#[inline]
65pub fn set_colors_enabled(val: bool) {
66    stdout_colors().store(val, Ordering::Relaxed)
67}
68
69/// Forces true colorization on or off for stdout.
70///
71/// This overrides the default for the current process and changes the return value of the
72/// `true_colors_enabled` function.
73#[inline]
74pub fn set_true_colors_enabled(val: bool) {
75    stdout_true_colors().store(val, Ordering::Relaxed)
76}
77
78/// Returns `true` if colors should be enabled for stderr.
79///
80/// This honors the [clicolors spec](http://bixense.com/clicolors/).
81///
82/// * `CLICOLOR != 0`: ANSI colors are supported and should be used when the program isn't piped.
83/// * `CLICOLOR == 0`: Don't output ANSI color escape codes.
84/// * `CLICOLOR_FORCE != 0`: ANSI colors should be enabled no matter what.
85#[inline]
86pub fn colors_enabled_stderr() -> bool {
87    stderr_colors().load(Ordering::Relaxed)
88}
89
90/// Returns `true` if true colors should be enabled for stderr.
91#[inline]
92pub fn true_colors_enabled_stderr() -> bool {
93    stderr_true_colors().load(Ordering::Relaxed)
94}
95
96/// Forces colorization on or off for stderr.
97///
98/// This overrides the default for the current process and changes the return value of the
99/// `colors_enabled_stderr` function.
100#[inline]
101pub fn set_colors_enabled_stderr(val: bool) {
102    stderr_colors().store(val, Ordering::Relaxed)
103}
104
105/// Forces true colorization on or off for stderr.
106///
107/// This overrides the default for the current process and changes the return value of the
108/// `true_colors_enabled_stderr` function.
109#[inline]
110pub fn set_true_colors_enabled_stderr(val: bool) {
111    stderr_true_colors().store(val, Ordering::Relaxed)
112}
113
114/// Measure the width of a string in terminal characters.
115pub fn measure_text_width(s: &str) -> usize {
116    #[cfg(feature = "ansi-parsing")]
117    {
118        AnsiCodeIterator::new(s)
119            .filter_map(|(s, is_ansi)| match is_ansi {
120                false => Some(str_width(s)),
121                true => None,
122            })
123            .sum()
124    }
125    #[cfg(not(feature = "ansi-parsing"))]
126    {
127        str_width(s)
128    }
129}
130
131/// A terminal color.
132#[derive(Copy, Clone, Debug, PartialEq, Eq)]
133pub enum Color {
134    Black,
135    Red,
136    Green,
137    Yellow,
138    Blue,
139    Magenta,
140    Cyan,
141    White,
142    Color256(u8),
143    TrueColor(u8, u8, u8),
144}
145
146impl Color {
147    #[inline]
148    fn ansi_num(self) -> usize {
149        match self {
150            Color::Black => 0,
151            Color::Red => 1,
152            Color::Green => 2,
153            Color::Yellow => 3,
154            Color::Blue => 4,
155            Color::Magenta => 5,
156            Color::Cyan => 6,
157            Color::White => 7,
158            Color::Color256(x) => x as usize,
159            Color::TrueColor(_, _, _) => panic!("RGB colors must be handled separately"),
160        }
161    }
162
163    #[inline]
164    fn is_color256(self) -> bool {
165        #[allow(clippy::match_like_matches_macro)]
166        match self {
167            Color::Color256(_) => true,
168            _ => false,
169        }
170    }
171}
172
173/// A terminal style attribute.
174#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
175#[repr(u16)]
176pub enum Attribute {
177    // This mapping is important, it exactly matches ansi_num = (x as u16 + 1)
178    // See `ATTRIBUTES_LOOKUP` as well
179    Bold = 0,
180    Dim = 1,
181    Italic = 2,
182    Underlined = 3,
183    Blink = 4,
184    BlinkFast = 5,
185    Reverse = 6,
186    Hidden = 7,
187    StrikeThrough = 8,
188}
189
190impl Attribute {
191    const MAP: [Attribute; 9] = [
192        Attribute::Bold,
193        Attribute::Dim,
194        Attribute::Italic,
195        Attribute::Underlined,
196        Attribute::Blink,
197        Attribute::BlinkFast,
198        Attribute::Reverse,
199        Attribute::Hidden,
200        Attribute::StrikeThrough,
201    ];
202}
203
204#[derive(Clone, Copy, PartialEq, Eq)]
205struct Attributes(u16);
206
207impl Attributes {
208    #[inline]
209    const fn new() -> Self {
210        Self(0)
211    }
212
213    #[inline]
214    #[must_use]
215    const fn insert(mut self, attr: Attribute) -> Self {
216        let bit = attr as u16;
217        self.0 |= 1 << bit;
218        self
219    }
220
221    #[inline]
222    const fn bits(self) -> BitsIter {
223        BitsIter(self.0)
224    }
225
226    #[inline]
227    fn attrs(self) -> impl Iterator<Item = Attribute> {
228        self.bits().map(|bit| Attribute::MAP[bit as usize])
229    }
230
231    #[inline]
232    fn is_empty(self) -> bool {
233        self.0 == 0
234    }
235}
236
237impl fmt::Display for Attributes {
238    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
239        for ansi in self.bits().map(|bit| bit + 1) {
240            write!(f, "\x1b[{ansi}m")?;
241        }
242        Ok(())
243    }
244}
245
246struct BitsIter(u16);
247
248impl Iterator for BitsIter {
249    type Item = u16;
250
251    fn next(&mut self) -> Option<Self::Item> {
252        if self.0 == 0 {
253            return None;
254        }
255        let bit = self.0.trailing_zeros();
256        self.0 ^= (1 << bit) as u16;
257        Some(bit as u16)
258    }
259}
260
261impl Debug for Attributes {
262    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
263        f.debug_set().entries(self.attrs()).finish()
264    }
265}
266
267/// Defines the alignment for padding operations.
268#[derive(Copy, Clone, Debug, PartialEq, Eq)]
269pub enum Alignment {
270    Left,
271    Center,
272    Right,
273}
274
275/// A stored style that can be applied.
276#[derive(Clone, Debug, PartialEq, Eq)]
277pub struct Style {
278    fg: Option<Color>,
279    bg: Option<Color>,
280    fg_bright: bool,
281    bg_bright: bool,
282    attrs: Attributes,
283    force: Option<bool>,
284    for_stderr: bool,
285}
286
287impl Default for Style {
288    fn default() -> Self {
289        Self::new()
290    }
291}
292
293impl Style {
294    /// Returns an empty default style.
295    pub const fn new() -> Self {
296        Self {
297            fg: None,
298            bg: None,
299            fg_bright: false,
300            bg_bright: false,
301            attrs: Attributes::new(),
302            force: None,
303            for_stderr: false,
304        }
305    }
306
307    /// Creates a style from a dotted string.
308    ///
309    /// Effectively the string is split at each dot and then the
310    /// terms in between are applied.  For instance `red.on_blue` will
311    /// create a string that is red on blue background. `9.on_12` is
312    /// the same, but using 256 color numbers. Unknown terms are
313    /// ignored.
314    pub fn from_dotted_str(s: &str) -> Self {
315        let mut rv = Self::new();
316        for part in s.split('.') {
317            rv = match part {
318                "black" => rv.black(),
319                "red" => rv.red(),
320                "green" => rv.green(),
321                "yellow" => rv.yellow(),
322                "blue" => rv.blue(),
323                "magenta" => rv.magenta(),
324                "cyan" => rv.cyan(),
325                "white" => rv.white(),
326                "bright" => rv.bright(),
327                "on_black" => rv.on_black(),
328                "on_red" => rv.on_red(),
329                "on_green" => rv.on_green(),
330                "on_yellow" => rv.on_yellow(),
331                "on_blue" => rv.on_blue(),
332                "on_magenta" => rv.on_magenta(),
333                "on_cyan" => rv.on_cyan(),
334                "on_white" => rv.on_white(),
335                "on_bright" => rv.on_bright(),
336                "bold" => rv.bold(),
337                "dim" => rv.dim(),
338                "underlined" => rv.underlined(),
339                "blink" => rv.blink(),
340                "blink_fast" => rv.blink_fast(),
341                "reverse" => rv.reverse(),
342                "hidden" => rv.hidden(),
343                "strikethrough" => rv.strikethrough(),
344                on_true_color
345                    if on_true_color.starts_with("on_#")
346                        && on_true_color.len() == 10
347                        && on_true_color.is_ascii() =>
348                {
349                    if let (Ok(r), Ok(g), Ok(b)) = (
350                        u8::from_str_radix(&on_true_color[4..6], 16),
351                        u8::from_str_radix(&on_true_color[6..8], 16),
352                        u8::from_str_radix(&on_true_color[8..10], 16),
353                    ) {
354                        rv.on_true_color(r, g, b)
355                    } else {
356                        continue;
357                    }
358                }
359                true_color
360                    if true_color.starts_with('#')
361                        && true_color.len() == 7
362                        && true_color.is_ascii() =>
363                {
364                    if let (Ok(r), Ok(g), Ok(b)) = (
365                        u8::from_str_radix(&true_color[1..3], 16),
366                        u8::from_str_radix(&true_color[3..5], 16),
367                        u8::from_str_radix(&true_color[5..7], 16),
368                    ) {
369                        rv.true_color(r, g, b)
370                    } else {
371                        continue;
372                    }
373                }
374                on_c if on_c.starts_with("on_") => {
375                    if let Ok(n) = on_c[3..].parse::<u8>() {
376                        rv.on_color256(n)
377                    } else {
378                        continue;
379                    }
380                }
381                c => {
382                    if let Ok(n) = c.parse::<u8>() {
383                        rv.color256(n)
384                    } else {
385                        continue;
386                    }
387                }
388            };
389        }
390        rv
391    }
392
393    /// Apply the style to something that can be displayed.
394    pub fn apply_to<D>(&self, val: D) -> StyledObject<D> {
395        StyledObject {
396            style: self.clone(),
397            val,
398        }
399    }
400
401    /// Forces styling on or off.
402    ///
403    /// This overrides the automatic detection.
404    #[inline]
405    pub const fn force_styling(mut self, value: bool) -> Self {
406        self.force = Some(value);
407        self
408    }
409
410    /// Specifies that style is applying to something being written on stderr.
411    #[inline]
412    pub const fn for_stderr(mut self) -> Self {
413        self.for_stderr = true;
414        self
415    }
416
417    /// Specifies that style is applying to something being written on stdout.
418    ///
419    /// This is the default behaviour.
420    #[inline]
421    pub const fn for_stdout(mut self) -> Self {
422        self.for_stderr = false;
423        self
424    }
425
426    /// Sets a foreground color.
427    #[inline]
428    pub const fn fg(mut self, color: Color) -> Self {
429        self.fg = Some(color);
430        self
431    }
432
433    /// Sets a background color.
434    #[inline]
435    pub const fn bg(mut self, color: Color) -> Self {
436        self.bg = Some(color);
437        self
438    }
439
440    /// Adds a attr.
441    #[inline]
442    pub const fn attr(mut self, attr: Attribute) -> Self {
443        self.attrs = self.attrs.insert(attr);
444        self
445    }
446
447    #[inline]
448    pub const fn black(self) -> Self {
449        self.fg(Color::Black)
450    }
451    #[inline]
452    pub const fn red(self) -> Self {
453        self.fg(Color::Red)
454    }
455    #[inline]
456    pub const fn green(self) -> Self {
457        self.fg(Color::Green)
458    }
459    #[inline]
460    pub const fn yellow(self) -> Self {
461        self.fg(Color::Yellow)
462    }
463    #[inline]
464    pub const fn blue(self) -> Self {
465        self.fg(Color::Blue)
466    }
467    #[inline]
468    pub const fn magenta(self) -> Self {
469        self.fg(Color::Magenta)
470    }
471    #[inline]
472    pub const fn cyan(self) -> Self {
473        self.fg(Color::Cyan)
474    }
475    #[inline]
476    pub const fn white(self) -> Self {
477        self.fg(Color::White)
478    }
479    #[inline]
480    pub const fn color256(self, color: u8) -> Self {
481        self.fg(Color::Color256(color))
482    }
483    #[inline]
484    pub const fn true_color(self, r: u8, g: u8, b: u8) -> Self {
485        self.fg(Color::TrueColor(r, g, b))
486    }
487
488    #[inline]
489    pub const fn bright(mut self) -> Self {
490        self.fg_bright = true;
491        self
492    }
493
494    #[inline]
495    pub const fn on_black(self) -> Self {
496        self.bg(Color::Black)
497    }
498    #[inline]
499    pub const fn on_red(self) -> Self {
500        self.bg(Color::Red)
501    }
502    #[inline]
503    pub const fn on_green(self) -> Self {
504        self.bg(Color::Green)
505    }
506    #[inline]
507    pub const fn on_yellow(self) -> Self {
508        self.bg(Color::Yellow)
509    }
510    #[inline]
511    pub const fn on_blue(self) -> Self {
512        self.bg(Color::Blue)
513    }
514    #[inline]
515    pub const fn on_magenta(self) -> Self {
516        self.bg(Color::Magenta)
517    }
518    #[inline]
519    pub const fn on_cyan(self) -> Self {
520        self.bg(Color::Cyan)
521    }
522    #[inline]
523    pub const fn on_white(self) -> Self {
524        self.bg(Color::White)
525    }
526    #[inline]
527    pub const fn on_color256(self, color: u8) -> Self {
528        self.bg(Color::Color256(color))
529    }
530    #[inline]
531    pub const fn on_true_color(self, r: u8, g: u8, b: u8) -> Self {
532        self.bg(Color::TrueColor(r, g, b))
533    }
534
535    #[inline]
536    pub const fn on_bright(mut self) -> Self {
537        self.bg_bright = true;
538        self
539    }
540
541    #[inline]
542    pub const fn bold(self) -> Self {
543        self.attr(Attribute::Bold)
544    }
545    #[inline]
546    pub const fn dim(self) -> Self {
547        self.attr(Attribute::Dim)
548    }
549    #[inline]
550    pub const fn italic(self) -> Self {
551        self.attr(Attribute::Italic)
552    }
553    #[inline]
554    pub const fn underlined(self) -> Self {
555        self.attr(Attribute::Underlined)
556    }
557    #[inline]
558    pub const fn blink(self) -> Self {
559        self.attr(Attribute::Blink)
560    }
561    #[inline]
562    pub const fn blink_fast(self) -> Self {
563        self.attr(Attribute::BlinkFast)
564    }
565    #[inline]
566    pub const fn reverse(self) -> Self {
567        self.attr(Attribute::Reverse)
568    }
569    #[inline]
570    pub const fn hidden(self) -> Self {
571        self.attr(Attribute::Hidden)
572    }
573    #[inline]
574    pub const fn strikethrough(self) -> Self {
575        self.attr(Attribute::StrikeThrough)
576    }
577}
578
579/// Wraps an object for formatting for styling.
580///
581/// Example:
582///
583/// ```rust,no_run
584/// # use console::style;
585/// format!("Hello {}", style("World").cyan());
586/// ```
587///
588/// This is a shortcut for making a new style and applying it
589/// to a value:
590///
591/// ```rust,no_run
592/// # use console::Style;
593/// format!("Hello {}", Style::new().cyan().apply_to("World"));
594/// ```
595pub fn style<D>(val: D) -> StyledObject<D> {
596    Style::new().apply_to(val)
597}
598
599/// A formatting wrapper that can be styled for a terminal.
600#[derive(Clone)]
601pub struct StyledObject<D> {
602    style: Style,
603    val: D,
604}
605
606impl<D> StyledObject<D> {
607    /// Forces styling on or off.
608    ///
609    /// This overrides the automatic detection.
610    #[inline]
611    pub fn force_styling(mut self, value: bool) -> StyledObject<D> {
612        self.style = self.style.force_styling(value);
613        self
614    }
615
616    /// Specifies that style is applying to something being written on stderr
617    #[inline]
618    pub fn for_stderr(mut self) -> StyledObject<D> {
619        self.style = self.style.for_stderr();
620        self
621    }
622
623    /// Specifies that style is applying to something being written on stdout
624    ///
625    /// This is the default
626    #[inline]
627    pub const fn for_stdout(mut self) -> StyledObject<D> {
628        self.style = self.style.for_stdout();
629        self
630    }
631
632    /// Sets a foreground color.
633    #[inline]
634    pub const fn fg(mut self, color: Color) -> StyledObject<D> {
635        self.style = self.style.fg(color);
636        self
637    }
638
639    /// Sets a background color.
640    #[inline]
641    pub const fn bg(mut self, color: Color) -> StyledObject<D> {
642        self.style = self.style.bg(color);
643        self
644    }
645
646    /// Adds a attr.
647    #[inline]
648    pub const fn attr(mut self, attr: Attribute) -> StyledObject<D> {
649        self.style = self.style.attr(attr);
650        self
651    }
652
653    #[inline]
654    pub const fn black(self) -> StyledObject<D> {
655        self.fg(Color::Black)
656    }
657    #[inline]
658    pub const fn red(self) -> StyledObject<D> {
659        self.fg(Color::Red)
660    }
661    #[inline]
662    pub const fn green(self) -> StyledObject<D> {
663        self.fg(Color::Green)
664    }
665    #[inline]
666    pub const fn yellow(self) -> StyledObject<D> {
667        self.fg(Color::Yellow)
668    }
669    #[inline]
670    pub const fn blue(self) -> StyledObject<D> {
671        self.fg(Color::Blue)
672    }
673    #[inline]
674    pub const fn magenta(self) -> StyledObject<D> {
675        self.fg(Color::Magenta)
676    }
677    #[inline]
678    pub const fn cyan(self) -> StyledObject<D> {
679        self.fg(Color::Cyan)
680    }
681    #[inline]
682    pub const fn white(self) -> StyledObject<D> {
683        self.fg(Color::White)
684    }
685    #[inline]
686    pub const fn color256(self, color: u8) -> StyledObject<D> {
687        self.fg(Color::Color256(color))
688    }
689    #[inline]
690    pub const fn true_color(self, r: u8, g: u8, b: u8) -> StyledObject<D> {
691        self.fg(Color::TrueColor(r, g, b))
692    }
693
694    #[inline]
695    pub const fn bright(mut self) -> StyledObject<D> {
696        self.style = self.style.bright();
697        self
698    }
699
700    #[inline]
701    pub const fn on_black(self) -> StyledObject<D> {
702        self.bg(Color::Black)
703    }
704    #[inline]
705    pub const fn on_red(self) -> StyledObject<D> {
706        self.bg(Color::Red)
707    }
708    #[inline]
709    pub const fn on_green(self) -> StyledObject<D> {
710        self.bg(Color::Green)
711    }
712    #[inline]
713    pub const fn on_yellow(self) -> StyledObject<D> {
714        self.bg(Color::Yellow)
715    }
716    #[inline]
717    pub const fn on_blue(self) -> StyledObject<D> {
718        self.bg(Color::Blue)
719    }
720    #[inline]
721    pub const fn on_magenta(self) -> StyledObject<D> {
722        self.bg(Color::Magenta)
723    }
724    #[inline]
725    pub const fn on_cyan(self) -> StyledObject<D> {
726        self.bg(Color::Cyan)
727    }
728    #[inline]
729    pub const fn on_white(self) -> StyledObject<D> {
730        self.bg(Color::White)
731    }
732    #[inline]
733    pub const fn on_color256(self, color: u8) -> StyledObject<D> {
734        self.bg(Color::Color256(color))
735    }
736    #[inline]
737    pub const fn on_true_color(self, r: u8, g: u8, b: u8) -> StyledObject<D> {
738        self.bg(Color::TrueColor(r, g, b))
739    }
740
741    #[inline]
742    pub const fn on_bright(mut self) -> StyledObject<D> {
743        self.style = self.style.on_bright();
744        self
745    }
746
747    #[inline]
748    pub const fn bold(self) -> StyledObject<D> {
749        self.attr(Attribute::Bold)
750    }
751    #[inline]
752    pub const fn dim(self) -> StyledObject<D> {
753        self.attr(Attribute::Dim)
754    }
755    #[inline]
756    pub const fn italic(self) -> StyledObject<D> {
757        self.attr(Attribute::Italic)
758    }
759    #[inline]
760    pub const fn underlined(self) -> StyledObject<D> {
761        self.attr(Attribute::Underlined)
762    }
763    #[inline]
764    pub const fn blink(self) -> StyledObject<D> {
765        self.attr(Attribute::Blink)
766    }
767    #[inline]
768    pub const fn blink_fast(self) -> StyledObject<D> {
769        self.attr(Attribute::BlinkFast)
770    }
771    #[inline]
772    pub const fn reverse(self) -> StyledObject<D> {
773        self.attr(Attribute::Reverse)
774    }
775    #[inline]
776    pub const fn hidden(self) -> StyledObject<D> {
777        self.attr(Attribute::Hidden)
778    }
779    #[inline]
780    pub const fn strikethrough(self) -> StyledObject<D> {
781        self.attr(Attribute::StrikeThrough)
782    }
783}
784
785macro_rules! impl_fmt {
786    ($name:ident) => {
787        impl<D: fmt::$name> fmt::$name for StyledObject<D> {
788            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
789                let mut reset = false;
790                if self
791                    .style
792                    .force
793                    .unwrap_or_else(|| match self.style.for_stderr {
794                        true => colors_enabled_stderr(),
795                        false => colors_enabled(),
796                    })
797                {
798                    if let Some(fg) = self.style.fg {
799                        if let Color::TrueColor(r, g, b) = fg {
800                            write!(f, "\x1b[38;2;{};{};{}m", r, g, b)?;
801                        } else if fg.is_color256() {
802                            write!(f, "\x1b[38;5;{}m", fg.ansi_num())?;
803                        } else if self.style.fg_bright {
804                            write!(f, "\x1b[38;5;{}m", fg.ansi_num() + 8)?;
805                        } else {
806                            write!(f, "\x1b[{}m", fg.ansi_num() + 30)?;
807                        }
808                        reset = true;
809                    }
810                    if let Some(bg) = self.style.bg {
811                        if let Color::TrueColor(r, g, b) = bg {
812                            write!(f, "\x1b[48;2;{};{};{}m", r, g, b)?;
813                        } else if bg.is_color256() {
814                            write!(f, "\x1b[48;5;{}m", bg.ansi_num())?;
815                        } else if self.style.bg_bright {
816                            write!(f, "\x1b[48;5;{}m", bg.ansi_num() + 8)?;
817                        } else {
818                            write!(f, "\x1b[{}m", bg.ansi_num() + 40)?;
819                        }
820                        reset = true;
821                    }
822                    if !self.style.attrs.is_empty() {
823                        write!(f, "{}", self.style.attrs)?;
824                        reset = true;
825                    }
826                }
827                fmt::$name::fmt(&self.val, f)?;
828                if reset {
829                    write!(f, "\x1b[0m")?;
830                }
831                Ok(())
832            }
833        }
834    };
835}
836
837impl_fmt!(Binary);
838impl_fmt!(Debug);
839impl_fmt!(Display);
840impl_fmt!(LowerExp);
841impl_fmt!(LowerHex);
842impl_fmt!(Octal);
843impl_fmt!(Pointer);
844impl_fmt!(UpperExp);
845impl_fmt!(UpperHex);
846
847/// "Intelligent" emoji formatter.
848///
849/// This struct intelligently wraps an emoji so that it is rendered
850/// only on systems that want emojis and renders a fallback on others.
851///
852/// Example:
853///
854/// ```rust
855/// use console::Emoji;
856/// println!("[3/4] {}Downloading ...", Emoji("🚚 ", ""));
857/// println!("[4/4] {} Done!", Emoji("✨", ":-)"));
858/// ```
859#[derive(Copy, Clone)]
860pub struct Emoji<'a, 'b>(pub &'a str, pub &'b str);
861
862impl<'a, 'b> Emoji<'a, 'b> {
863    pub fn new(emoji: &'a str, fallback: &'b str) -> Emoji<'a, 'b> {
864        Emoji(emoji, fallback)
865    }
866}
867
868impl fmt::Display for Emoji<'_, '_> {
869    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
870        if wants_emoji() {
871            write!(f, "{}", self.0)
872        } else {
873            write!(f, "{}", self.1)
874        }
875    }
876}
877
878fn str_width(s: &str) -> usize {
879    #[cfg(feature = "unicode-width")]
880    {
881        use unicode_width::UnicodeWidthStr;
882        s.width()
883    }
884    #[cfg(not(feature = "unicode-width"))]
885    {
886        s.chars().count()
887    }
888}
889
890#[cfg(feature = "ansi-parsing")]
891pub(crate) fn char_width(c: char) -> usize {
892    #[cfg(feature = "unicode-width")]
893    {
894        use unicode_width::UnicodeWidthChar;
895        c.width().unwrap_or(0)
896    }
897    #[cfg(not(feature = "unicode-width"))]
898    {
899        let _c = c;
900        1
901    }
902}
903
904#[cfg(not(feature = "ansi-parsing"))]
905pub(crate) fn char_width(_c: char) -> usize {
906    1
907}
908
909/// Truncates a string to a certain number of characters.
910///
911/// This ensures that escape codes are not screwed up in the process.
912/// If the maximum length is hit the string will be truncated but
913/// escapes code will still be honored.  If truncation takes place
914/// the tail string will be appended.
915pub fn truncate_str<'a>(s: &'a str, width: usize, tail: &str) -> Cow<'a, str> {
916    if measure_text_width(s) <= width {
917        return Cow::Borrowed(s);
918    }
919
920    #[cfg(feature = "ansi-parsing")]
921    {
922        use core::cmp::Ordering;
923        let mut iter = AnsiCodeIterator::new(s);
924        let mut length = 0;
925        let mut rv = None;
926
927        while let Some(item) = iter.next() {
928            match item {
929                (s, false) => {
930                    if rv.is_none() {
931                        if str_width(s) + length > width.saturating_sub(str_width(tail)) {
932                            let ts = iter.current_slice();
933
934                            let mut s_byte = 0;
935                            let mut s_width = 0;
936                            let rest_width =
937                                width.saturating_sub(str_width(tail)).saturating_sub(length);
938                            for c in s.chars() {
939                                s_byte += c.len_utf8();
940                                s_width += char_width(c);
941                                match s_width.cmp(&rest_width) {
942                                    Ordering::Equal => break,
943                                    Ordering::Greater => {
944                                        s_byte -= c.len_utf8();
945                                        break;
946                                    }
947                                    Ordering::Less => continue,
948                                }
949                            }
950
951                            let idx = ts.len() - s.len() + s_byte;
952                            let mut buf = ts[..idx].to_string();
953                            buf.push_str(tail);
954                            rv = Some(buf);
955                        }
956                        length += str_width(s);
957                    }
958                }
959                (s, true) => {
960                    if let Some(ref mut rv) = rv {
961                        rv.push_str(s);
962                    }
963                }
964            }
965        }
966
967        if let Some(buf) = rv {
968            Cow::Owned(buf)
969        } else {
970            Cow::Borrowed(s)
971        }
972    }
973
974    #[cfg(not(feature = "ansi-parsing"))]
975    {
976        Cow::Owned(format!(
977            "{}{}",
978            &s[..width.saturating_sub(tail.len())],
979            tail
980        ))
981    }
982}
983
984/// Pads a string to fill a certain number of characters.
985///
986/// This will honor ansi codes correctly and allows you to align a string
987/// on the left, right or centered.  Additionally truncation can be enabled
988/// by setting `truncate` to a string that should be used as a truncation
989/// marker.
990pub fn pad_str<'a>(
991    s: &'a str,
992    width: usize,
993    align: Alignment,
994    truncate: Option<&str>,
995) -> Cow<'a, str> {
996    pad_str_with(s, width, align, truncate, ' ')
997}
998/// Pads a string with specific padding to fill a certain number of characters.
999///
1000/// This will honor ansi codes correctly and allows you to align a string
1001/// on the left, right or centered.  Additionally truncation can be enabled
1002/// by setting `truncate` to a string that should be used as a truncation
1003/// marker.
1004pub fn pad_str_with<'a>(
1005    s: &'a str,
1006    width: usize,
1007    align: Alignment,
1008    truncate: Option<&str>,
1009    pad: char,
1010) -> Cow<'a, str> {
1011    let cols = measure_text_width(s);
1012
1013    if cols >= width {
1014        return match truncate {
1015            None => Cow::Borrowed(s),
1016            Some(tail) => truncate_str(s, width, tail),
1017        };
1018    }
1019
1020    let diff = width - cols;
1021
1022    let (left_pad, right_pad) = match align {
1023        Alignment::Left => (0, diff),
1024        Alignment::Right => (diff, 0),
1025        Alignment::Center => (diff / 2, diff - diff / 2),
1026    };
1027
1028    let mut rv = String::new();
1029    for _ in 0..left_pad {
1030        rv.push(pad);
1031    }
1032    rv.push_str(s);
1033    for _ in 0..right_pad {
1034        rv.push(pad);
1035    }
1036    Cow::Owned(rv)
1037}
1038
1039#[test]
1040fn test_text_width() {
1041    let s = style("foo")
1042        .red()
1043        .on_black()
1044        .bold()
1045        .force_styling(true)
1046        .to_string();
1047
1048    assert_eq!(
1049        measure_text_width(&s),
1050        if cfg!(feature = "ansi-parsing") {
1051            3
1052        } else {
1053            21
1054        }
1055    );
1056
1057    let s = style("🐶 <3").red().force_styling(true).to_string();
1058
1059    assert_eq!(
1060        measure_text_width(&s),
1061        match (
1062            cfg!(feature = "ansi-parsing"),
1063            cfg!(feature = "unicode-width")
1064        ) {
1065            (true, true) => 5,    // "🐶 <3"
1066            (true, false) => 4,   // "🐶 <3", no unicode-aware width
1067            (false, true) => 14,  // full string
1068            (false, false) => 13, // full string, no unicode-aware width
1069        }
1070    );
1071}
1072
1073#[test]
1074#[cfg(all(feature = "unicode-width", feature = "ansi-parsing"))]
1075fn test_truncate_str() {
1076    let s = format!("foo {}", style("bar").red().force_styling(true));
1077    assert_eq!(
1078        &truncate_str(&s, 5, ""),
1079        &format!("foo {}", style("b").red().force_styling(true))
1080    );
1081    let s = format!("foo {}", style("bar").red().force_styling(true));
1082    assert_eq!(
1083        &truncate_str(&s, 5, "!"),
1084        &format!("foo {}", style("!").red().force_styling(true))
1085    );
1086    let s = format!("foo {} baz", style("bar").red().force_styling(true));
1087    assert_eq!(
1088        &truncate_str(&s, 10, "..."),
1089        &format!("foo {}...", style("bar").red().force_styling(true))
1090    );
1091    let s = format!("foo {}", style("バー").red().force_styling(true));
1092    assert_eq!(
1093        &truncate_str(&s, 5, ""),
1094        &format!("foo {}", style("").red().force_styling(true))
1095    );
1096    let s = format!("foo {}", style("バー").red().force_styling(true));
1097    assert_eq!(
1098        &truncate_str(&s, 6, ""),
1099        &format!("foo {}", style("バ").red().force_styling(true))
1100    );
1101    let s = format!("foo {}", style("バー").red().force_styling(true));
1102    assert_eq!(
1103        &truncate_str(&s, 2, "!!!"),
1104        &format!("!!!{}", style("").red().force_styling(true))
1105    );
1106}
1107
1108#[test]
1109fn test_truncate_str_no_ansi() {
1110    assert_eq!(&truncate_str("foo bar", 7, "!"), "foo bar");
1111    assert_eq!(&truncate_str("foo bar", 5, ""), "foo b");
1112    assert_eq!(&truncate_str("foo bar", 5, "!"), "foo !");
1113    assert_eq!(&truncate_str("foo bar baz", 10, "..."), "foo bar...");
1114    assert_eq!(&truncate_str("foo bar", 0, ""), "");
1115    assert_eq!(&truncate_str("foo bar", 0, "!"), "!");
1116    assert_eq!(&truncate_str("foo bar", 2, "!!!"), "!!!");
1117    assert_eq!(&truncate_str("ab", 2, "!!!"), "ab");
1118}
1119
1120#[test]
1121fn test_pad_str() {
1122    assert_eq!(pad_str("foo", 7, Alignment::Center, None), "  foo  ");
1123    assert_eq!(pad_str("foo", 7, Alignment::Left, None), "foo    ");
1124    assert_eq!(pad_str("foo", 7, Alignment::Right, None), "    foo");
1125    assert_eq!(pad_str("foo", 3, Alignment::Left, None), "foo");
1126    assert_eq!(pad_str("foobar", 3, Alignment::Left, None), "foobar");
1127    assert_eq!(pad_str("foobar", 3, Alignment::Left, Some("")), "foo");
1128    assert_eq!(
1129        pad_str("foobarbaz", 6, Alignment::Left, Some("...")),
1130        "foo..."
1131    );
1132}
1133
1134#[test]
1135fn test_pad_str_with() {
1136    assert_eq!(
1137        pad_str_with("foo", 7, Alignment::Center, None, '#'),
1138        "##foo##"
1139    );
1140    assert_eq!(
1141        pad_str_with("foo", 7, Alignment::Left, None, '#'),
1142        "foo####"
1143    );
1144    assert_eq!(
1145        pad_str_with("foo", 7, Alignment::Right, None, '#'),
1146        "####foo"
1147    );
1148    assert_eq!(pad_str_with("foo", 3, Alignment::Left, None, '#'), "foo");
1149    assert_eq!(
1150        pad_str_with("foobar", 3, Alignment::Left, None, '#'),
1151        "foobar"
1152    );
1153    assert_eq!(
1154        pad_str_with("foobar", 3, Alignment::Left, Some(""), '#'),
1155        "foo"
1156    );
1157    assert_eq!(
1158        pad_str_with("foobarbaz", 6, Alignment::Left, Some("..."), '#'),
1159        "foo..."
1160    );
1161}
1162
1163#[test]
1164fn test_attributes_single() {
1165    for attr in Attribute::MAP {
1166        let attrs = Attributes::new().insert(attr);
1167        assert_eq!(attrs.bits().collect::<Vec<_>>(), [attr as u16]);
1168        assert_eq!(attrs.attrs().collect::<Vec<_>>(), [attr]);
1169        assert_eq!(format!("{attrs:?}"), format!("{{{:?}}}", attr));
1170    }
1171}
1172
1173#[test]
1174fn test_attributes_many() {
1175    let tests: [&[Attribute]; 3] = [
1176        &[
1177            Attribute::Bold,
1178            Attribute::Underlined,
1179            Attribute::BlinkFast,
1180            Attribute::Hidden,
1181        ],
1182        &[
1183            Attribute::Dim,
1184            Attribute::Italic,
1185            Attribute::Blink,
1186            Attribute::Reverse,
1187            Attribute::StrikeThrough,
1188        ],
1189        &Attribute::MAP,
1190    ];
1191    for test_attrs in tests {
1192        let mut attrs = Attributes::new();
1193        for attr in test_attrs {
1194            attrs = attrs.insert(*attr);
1195        }
1196        assert_eq!(
1197            attrs.bits().collect::<Vec<_>>(),
1198            test_attrs
1199                .iter()
1200                .map(|attr| *attr as u16)
1201                .collect::<Vec<_>>()
1202        );
1203        assert_eq!(&attrs.attrs().collect::<Vec<_>>(), test_attrs);
1204    }
1205}
1206
1207#[test]
1208fn test_style_from_non_ascii_fg() {
1209    // len() == 7, starts_with('#'), but slices [1..3] land mid-€ (3 bytes)
1210    let fg = "#€€";
1211    assert_eq!(fg.len(), 7);
1212
1213    let parsed_style = Style::from_dotted_str(fg);
1214
1215    // silently ignores non-ascii
1216    assert_eq!(parsed_style, Style::default());
1217}
1218
1219#[test]
1220fn test_style_from_non_ascii_bg() {
1221    // len() == 10, starts_with("on_#"), but slices [4..6] land mid-€
1222    let bg = "on_#€€";
1223    assert_eq!(bg.len(), 10);
1224
1225    let parsed_style = Style::from_dotted_str(bg);
1226
1227    // silently ignores non-ascii
1228    assert_eq!(parsed_style, Style::default());
1229}