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