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#[inline]
50pub fn colors_enabled() -> bool {
51 stdout_colors().load(Ordering::Relaxed)
52}
53
54#[inline]
56pub fn true_colors_enabled() -> bool {
57 stdout_true_colors().load(Ordering::Relaxed)
58}
59
60#[inline]
65pub fn set_colors_enabled(val: bool) {
66 stdout_colors().store(val, Ordering::Relaxed)
67}
68
69#[inline]
74pub fn set_true_colors_enabled(val: bool) {
75 stdout_true_colors().store(val, Ordering::Relaxed)
76}
77
78#[inline]
86pub fn colors_enabled_stderr() -> bool {
87 stderr_colors().load(Ordering::Relaxed)
88}
89
90#[inline]
92pub fn true_colors_enabled_stderr() -> bool {
93 stderr_true_colors().load(Ordering::Relaxed)
94}
95
96#[inline]
101pub fn set_colors_enabled_stderr(val: bool) {
102 stderr_colors().store(val, Ordering::Relaxed)
103}
104
105#[inline]
110pub fn set_true_colors_enabled_stderr(val: bool) {
111 stderr_true_colors().store(val, Ordering::Relaxed)
112}
113
114pub 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#[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#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
175#[repr(u16)]
176pub enum Attribute {
177 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#[derive(Copy, Clone, Debug, PartialEq, Eq)]
269pub enum Alignment {
270 Left,
271 Center,
272 Right,
273}
274
275#[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 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 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 if on_true_color.starts_with("on_#") && on_true_color.len() == 10 => {
345 if let (Ok(r), Ok(g), Ok(b)) = (
346 u8::from_str_radix(&on_true_color[4..6], 16),
347 u8::from_str_radix(&on_true_color[6..8], 16),
348 u8::from_str_radix(&on_true_color[8..10], 16),
349 ) {
350 rv.on_true_color(r, g, b)
351 } else {
352 continue;
353 }
354 }
355 true_color if true_color.starts_with('#') && true_color.len() == 7 => {
356 if let (Ok(r), Ok(g), Ok(b)) = (
357 u8::from_str_radix(&true_color[1..3], 16),
358 u8::from_str_radix(&true_color[3..5], 16),
359 u8::from_str_radix(&true_color[5..7], 16),
360 ) {
361 rv.true_color(r, g, b)
362 } else {
363 continue;
364 }
365 }
366 on_c if on_c.starts_with("on_") => {
367 if let Ok(n) = on_c[3..].parse::<u8>() {
368 rv.on_color256(n)
369 } else {
370 continue;
371 }
372 }
373 c => {
374 if let Ok(n) = c.parse::<u8>() {
375 rv.color256(n)
376 } else {
377 continue;
378 }
379 }
380 };
381 }
382 rv
383 }
384
385 pub fn apply_to<D>(&self, val: D) -> StyledObject<D> {
387 StyledObject {
388 style: self.clone(),
389 val,
390 }
391 }
392
393 #[inline]
397 pub const fn force_styling(mut self, value: bool) -> Self {
398 self.force = Some(value);
399 self
400 }
401
402 #[inline]
404 pub const fn for_stderr(mut self) -> Self {
405 self.for_stderr = true;
406 self
407 }
408
409 #[inline]
413 pub const fn for_stdout(mut self) -> Self {
414 self.for_stderr = false;
415 self
416 }
417
418 #[inline]
420 pub const fn fg(mut self, color: Color) -> Self {
421 self.fg = Some(color);
422 self
423 }
424
425 #[inline]
427 pub const fn bg(mut self, color: Color) -> Self {
428 self.bg = Some(color);
429 self
430 }
431
432 #[inline]
434 pub const fn attr(mut self, attr: Attribute) -> Self {
435 self.attrs = self.attrs.insert(attr);
436 self
437 }
438
439 #[inline]
440 pub const fn black(self) -> Self {
441 self.fg(Color::Black)
442 }
443 #[inline]
444 pub const fn red(self) -> Self {
445 self.fg(Color::Red)
446 }
447 #[inline]
448 pub const fn green(self) -> Self {
449 self.fg(Color::Green)
450 }
451 #[inline]
452 pub const fn yellow(self) -> Self {
453 self.fg(Color::Yellow)
454 }
455 #[inline]
456 pub const fn blue(self) -> Self {
457 self.fg(Color::Blue)
458 }
459 #[inline]
460 pub const fn magenta(self) -> Self {
461 self.fg(Color::Magenta)
462 }
463 #[inline]
464 pub const fn cyan(self) -> Self {
465 self.fg(Color::Cyan)
466 }
467 #[inline]
468 pub const fn white(self) -> Self {
469 self.fg(Color::White)
470 }
471 #[inline]
472 pub const fn color256(self, color: u8) -> Self {
473 self.fg(Color::Color256(color))
474 }
475 #[inline]
476 pub const fn true_color(self, r: u8, g: u8, b: u8) -> Self {
477 self.fg(Color::TrueColor(r, g, b))
478 }
479
480 #[inline]
481 pub const fn bright(mut self) -> Self {
482 self.fg_bright = true;
483 self
484 }
485
486 #[inline]
487 pub const fn on_black(self) -> Self {
488 self.bg(Color::Black)
489 }
490 #[inline]
491 pub const fn on_red(self) -> Self {
492 self.bg(Color::Red)
493 }
494 #[inline]
495 pub const fn on_green(self) -> Self {
496 self.bg(Color::Green)
497 }
498 #[inline]
499 pub const fn on_yellow(self) -> Self {
500 self.bg(Color::Yellow)
501 }
502 #[inline]
503 pub const fn on_blue(self) -> Self {
504 self.bg(Color::Blue)
505 }
506 #[inline]
507 pub const fn on_magenta(self) -> Self {
508 self.bg(Color::Magenta)
509 }
510 #[inline]
511 pub const fn on_cyan(self) -> Self {
512 self.bg(Color::Cyan)
513 }
514 #[inline]
515 pub const fn on_white(self) -> Self {
516 self.bg(Color::White)
517 }
518 #[inline]
519 pub const fn on_color256(self, color: u8) -> Self {
520 self.bg(Color::Color256(color))
521 }
522 #[inline]
523 pub const fn on_true_color(self, r: u8, g: u8, b: u8) -> Self {
524 self.bg(Color::TrueColor(r, g, b))
525 }
526
527 #[inline]
528 pub const fn on_bright(mut self) -> Self {
529 self.bg_bright = true;
530 self
531 }
532
533 #[inline]
534 pub const fn bold(self) -> Self {
535 self.attr(Attribute::Bold)
536 }
537 #[inline]
538 pub const fn dim(self) -> Self {
539 self.attr(Attribute::Dim)
540 }
541 #[inline]
542 pub const fn italic(self) -> Self {
543 self.attr(Attribute::Italic)
544 }
545 #[inline]
546 pub const fn underlined(self) -> Self {
547 self.attr(Attribute::Underlined)
548 }
549 #[inline]
550 pub const fn blink(self) -> Self {
551 self.attr(Attribute::Blink)
552 }
553 #[inline]
554 pub const fn blink_fast(self) -> Self {
555 self.attr(Attribute::BlinkFast)
556 }
557 #[inline]
558 pub const fn reverse(self) -> Self {
559 self.attr(Attribute::Reverse)
560 }
561 #[inline]
562 pub const fn hidden(self) -> Self {
563 self.attr(Attribute::Hidden)
564 }
565 #[inline]
566 pub const fn strikethrough(self) -> Self {
567 self.attr(Attribute::StrikeThrough)
568 }
569}
570
571pub fn style<D>(val: D) -> StyledObject<D> {
588 Style::new().apply_to(val)
589}
590
591#[derive(Clone)]
593pub struct StyledObject<D> {
594 style: Style,
595 val: D,
596}
597
598impl<D> StyledObject<D> {
599 #[inline]
603 pub fn force_styling(mut self, value: bool) -> StyledObject<D> {
604 self.style = self.style.force_styling(value);
605 self
606 }
607
608 #[inline]
610 pub fn for_stderr(mut self) -> StyledObject<D> {
611 self.style = self.style.for_stderr();
612 self
613 }
614
615 #[inline]
619 pub const fn for_stdout(mut self) -> StyledObject<D> {
620 self.style = self.style.for_stdout();
621 self
622 }
623
624 #[inline]
626 pub const fn fg(mut self, color: Color) -> StyledObject<D> {
627 self.style = self.style.fg(color);
628 self
629 }
630
631 #[inline]
633 pub const fn bg(mut self, color: Color) -> StyledObject<D> {
634 self.style = self.style.bg(color);
635 self
636 }
637
638 #[inline]
640 pub const fn attr(mut self, attr: Attribute) -> StyledObject<D> {
641 self.style = self.style.attr(attr);
642 self
643 }
644
645 #[inline]
646 pub const fn black(self) -> StyledObject<D> {
647 self.fg(Color::Black)
648 }
649 #[inline]
650 pub const fn red(self) -> StyledObject<D> {
651 self.fg(Color::Red)
652 }
653 #[inline]
654 pub const fn green(self) -> StyledObject<D> {
655 self.fg(Color::Green)
656 }
657 #[inline]
658 pub const fn yellow(self) -> StyledObject<D> {
659 self.fg(Color::Yellow)
660 }
661 #[inline]
662 pub const fn blue(self) -> StyledObject<D> {
663 self.fg(Color::Blue)
664 }
665 #[inline]
666 pub const fn magenta(self) -> StyledObject<D> {
667 self.fg(Color::Magenta)
668 }
669 #[inline]
670 pub const fn cyan(self) -> StyledObject<D> {
671 self.fg(Color::Cyan)
672 }
673 #[inline]
674 pub const fn white(self) -> StyledObject<D> {
675 self.fg(Color::White)
676 }
677 #[inline]
678 pub const fn color256(self, color: u8) -> StyledObject<D> {
679 self.fg(Color::Color256(color))
680 }
681 #[inline]
682 pub const fn true_color(self, r: u8, g: u8, b: u8) -> StyledObject<D> {
683 self.fg(Color::TrueColor(r, g, b))
684 }
685
686 #[inline]
687 pub const fn bright(mut self) -> StyledObject<D> {
688 self.style = self.style.bright();
689 self
690 }
691
692 #[inline]
693 pub const fn on_black(self) -> StyledObject<D> {
694 self.bg(Color::Black)
695 }
696 #[inline]
697 pub const fn on_red(self) -> StyledObject<D> {
698 self.bg(Color::Red)
699 }
700 #[inline]
701 pub const fn on_green(self) -> StyledObject<D> {
702 self.bg(Color::Green)
703 }
704 #[inline]
705 pub const fn on_yellow(self) -> StyledObject<D> {
706 self.bg(Color::Yellow)
707 }
708 #[inline]
709 pub const fn on_blue(self) -> StyledObject<D> {
710 self.bg(Color::Blue)
711 }
712 #[inline]
713 pub const fn on_magenta(self) -> StyledObject<D> {
714 self.bg(Color::Magenta)
715 }
716 #[inline]
717 pub const fn on_cyan(self) -> StyledObject<D> {
718 self.bg(Color::Cyan)
719 }
720 #[inline]
721 pub const fn on_white(self) -> StyledObject<D> {
722 self.bg(Color::White)
723 }
724 #[inline]
725 pub const fn on_color256(self, color: u8) -> StyledObject<D> {
726 self.bg(Color::Color256(color))
727 }
728 #[inline]
729 pub const fn on_true_color(self, r: u8, g: u8, b: u8) -> StyledObject<D> {
730 self.bg(Color::TrueColor(r, g, b))
731 }
732
733 #[inline]
734 pub const fn on_bright(mut self) -> StyledObject<D> {
735 self.style = self.style.on_bright();
736 self
737 }
738
739 #[inline]
740 pub const fn bold(self) -> StyledObject<D> {
741 self.attr(Attribute::Bold)
742 }
743 #[inline]
744 pub const fn dim(self) -> StyledObject<D> {
745 self.attr(Attribute::Dim)
746 }
747 #[inline]
748 pub const fn italic(self) -> StyledObject<D> {
749 self.attr(Attribute::Italic)
750 }
751 #[inline]
752 pub const fn underlined(self) -> StyledObject<D> {
753 self.attr(Attribute::Underlined)
754 }
755 #[inline]
756 pub const fn blink(self) -> StyledObject<D> {
757 self.attr(Attribute::Blink)
758 }
759 #[inline]
760 pub const fn blink_fast(self) -> StyledObject<D> {
761 self.attr(Attribute::BlinkFast)
762 }
763 #[inline]
764 pub const fn reverse(self) -> StyledObject<D> {
765 self.attr(Attribute::Reverse)
766 }
767 #[inline]
768 pub const fn hidden(self) -> StyledObject<D> {
769 self.attr(Attribute::Hidden)
770 }
771 #[inline]
772 pub const fn strikethrough(self) -> StyledObject<D> {
773 self.attr(Attribute::StrikeThrough)
774 }
775}
776
777macro_rules! impl_fmt {
778 ($name:ident) => {
779 impl<D: fmt::$name> fmt::$name for StyledObject<D> {
780 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
781 let mut reset = false;
782 if self
783 .style
784 .force
785 .unwrap_or_else(|| match self.style.for_stderr {
786 true => colors_enabled_stderr(),
787 false => colors_enabled(),
788 })
789 {
790 if let Some(fg) = self.style.fg {
791 if let Color::TrueColor(r, g, b) = fg {
792 write!(f, "\x1b[38;2;{};{};{}m", r, g, b)?;
793 } else if fg.is_color256() {
794 write!(f, "\x1b[38;5;{}m", fg.ansi_num())?;
795 } else if self.style.fg_bright {
796 write!(f, "\x1b[38;5;{}m", fg.ansi_num() + 8)?;
797 } else {
798 write!(f, "\x1b[{}m", fg.ansi_num() + 30)?;
799 }
800 reset = true;
801 }
802 if let Some(bg) = self.style.bg {
803 if let Color::TrueColor(r, g, b) = bg {
804 write!(f, "\x1b[48;2;{};{};{}m", r, g, b)?;
805 } else if bg.is_color256() {
806 write!(f, "\x1b[48;5;{}m", bg.ansi_num())?;
807 } else if self.style.bg_bright {
808 write!(f, "\x1b[48;5;{}m", bg.ansi_num() + 8)?;
809 } else {
810 write!(f, "\x1b[{}m", bg.ansi_num() + 40)?;
811 }
812 reset = true;
813 }
814 if !self.style.attrs.is_empty() {
815 write!(f, "{}", self.style.attrs)?;
816 reset = true;
817 }
818 }
819 fmt::$name::fmt(&self.val, f)?;
820 if reset {
821 write!(f, "\x1b[0m")?;
822 }
823 Ok(())
824 }
825 }
826 };
827}
828
829impl_fmt!(Binary);
830impl_fmt!(Debug);
831impl_fmt!(Display);
832impl_fmt!(LowerExp);
833impl_fmt!(LowerHex);
834impl_fmt!(Octal);
835impl_fmt!(Pointer);
836impl_fmt!(UpperExp);
837impl_fmt!(UpperHex);
838
839#[derive(Copy, Clone)]
852pub struct Emoji<'a, 'b>(pub &'a str, pub &'b str);
853
854impl<'a, 'b> Emoji<'a, 'b> {
855 pub fn new(emoji: &'a str, fallback: &'b str) -> Emoji<'a, 'b> {
856 Emoji(emoji, fallback)
857 }
858}
859
860impl fmt::Display for Emoji<'_, '_> {
861 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
862 if wants_emoji() {
863 write!(f, "{}", self.0)
864 } else {
865 write!(f, "{}", self.1)
866 }
867 }
868}
869
870fn str_width(s: &str) -> usize {
871 #[cfg(feature = "unicode-width")]
872 {
873 use unicode_width::UnicodeWidthStr;
874 s.width()
875 }
876 #[cfg(not(feature = "unicode-width"))]
877 {
878 s.chars().count()
879 }
880}
881
882#[cfg(feature = "ansi-parsing")]
883pub(crate) fn char_width(c: char) -> usize {
884 #[cfg(feature = "unicode-width")]
885 {
886 use unicode_width::UnicodeWidthChar;
887 c.width().unwrap_or(0)
888 }
889 #[cfg(not(feature = "unicode-width"))]
890 {
891 let _c = c;
892 1
893 }
894}
895
896#[cfg(not(feature = "ansi-parsing"))]
897pub(crate) fn char_width(_c: char) -> usize {
898 1
899}
900
901pub fn truncate_str<'a>(s: &'a str, width: usize, tail: &str) -> Cow<'a, str> {
908 if measure_text_width(s) <= width {
909 return Cow::Borrowed(s);
910 }
911
912 #[cfg(feature = "ansi-parsing")]
913 {
914 use core::cmp::Ordering;
915 let mut iter = AnsiCodeIterator::new(s);
916 let mut length = 0;
917 let mut rv = None;
918
919 while let Some(item) = iter.next() {
920 match item {
921 (s, false) => {
922 if rv.is_none() {
923 if str_width(s) + length > width.saturating_sub(str_width(tail)) {
924 let ts = iter.current_slice();
925
926 let mut s_byte = 0;
927 let mut s_width = 0;
928 let rest_width =
929 width.saturating_sub(str_width(tail)).saturating_sub(length);
930 for c in s.chars() {
931 s_byte += c.len_utf8();
932 s_width += char_width(c);
933 match s_width.cmp(&rest_width) {
934 Ordering::Equal => break,
935 Ordering::Greater => {
936 s_byte -= c.len_utf8();
937 break;
938 }
939 Ordering::Less => continue,
940 }
941 }
942
943 let idx = ts.len() - s.len() + s_byte;
944 let mut buf = ts[..idx].to_string();
945 buf.push_str(tail);
946 rv = Some(buf);
947 }
948 length += str_width(s);
949 }
950 }
951 (s, true) => {
952 if let Some(ref mut rv) = rv {
953 rv.push_str(s);
954 }
955 }
956 }
957 }
958
959 if let Some(buf) = rv {
960 Cow::Owned(buf)
961 } else {
962 Cow::Borrowed(s)
963 }
964 }
965
966 #[cfg(not(feature = "ansi-parsing"))]
967 {
968 Cow::Owned(format!(
969 "{}{}",
970 &s[..width.saturating_sub(tail.len())],
971 tail
972 ))
973 }
974}
975
976pub fn pad_str<'a>(
983 s: &'a str,
984 width: usize,
985 align: Alignment,
986 truncate: Option<&str>,
987) -> Cow<'a, str> {
988 pad_str_with(s, width, align, truncate, ' ')
989}
990pub fn pad_str_with<'a>(
997 s: &'a str,
998 width: usize,
999 align: Alignment,
1000 truncate: Option<&str>,
1001 pad: char,
1002) -> Cow<'a, str> {
1003 let cols = measure_text_width(s);
1004
1005 if cols >= width {
1006 return match truncate {
1007 None => Cow::Borrowed(s),
1008 Some(tail) => truncate_str(s, width, tail),
1009 };
1010 }
1011
1012 let diff = width - cols;
1013
1014 let (left_pad, right_pad) = match align {
1015 Alignment::Left => (0, diff),
1016 Alignment::Right => (diff, 0),
1017 Alignment::Center => (diff / 2, diff - diff / 2),
1018 };
1019
1020 let mut rv = String::new();
1021 for _ in 0..left_pad {
1022 rv.push(pad);
1023 }
1024 rv.push_str(s);
1025 for _ in 0..right_pad {
1026 rv.push(pad);
1027 }
1028 Cow::Owned(rv)
1029}
1030
1031#[test]
1032fn test_text_width() {
1033 let s = style("foo")
1034 .red()
1035 .on_black()
1036 .bold()
1037 .force_styling(true)
1038 .to_string();
1039
1040 assert_eq!(
1041 measure_text_width(&s),
1042 if cfg!(feature = "ansi-parsing") {
1043 3
1044 } else {
1045 21
1046 }
1047 );
1048
1049 let s = style("🐶 <3").red().force_styling(true).to_string();
1050
1051 assert_eq!(
1052 measure_text_width(&s),
1053 match (
1054 cfg!(feature = "ansi-parsing"),
1055 cfg!(feature = "unicode-width")
1056 ) {
1057 (true, true) => 5, (true, false) => 4, (false, true) => 14, (false, false) => 13, }
1062 );
1063}
1064
1065#[test]
1066#[cfg(all(feature = "unicode-width", feature = "ansi-parsing"))]
1067fn test_truncate_str() {
1068 let s = format!("foo {}", style("bar").red().force_styling(true));
1069 assert_eq!(
1070 &truncate_str(&s, 5, ""),
1071 &format!("foo {}", style("b").red().force_styling(true))
1072 );
1073 let s = format!("foo {}", style("bar").red().force_styling(true));
1074 assert_eq!(
1075 &truncate_str(&s, 5, "!"),
1076 &format!("foo {}", style("!").red().force_styling(true))
1077 );
1078 let s = format!("foo {} baz", style("bar").red().force_styling(true));
1079 assert_eq!(
1080 &truncate_str(&s, 10, "..."),
1081 &format!("foo {}...", style("bar").red().force_styling(true))
1082 );
1083 let s = format!("foo {}", style("バー").red().force_styling(true));
1084 assert_eq!(
1085 &truncate_str(&s, 5, ""),
1086 &format!("foo {}", style("").red().force_styling(true))
1087 );
1088 let s = format!("foo {}", style("バー").red().force_styling(true));
1089 assert_eq!(
1090 &truncate_str(&s, 6, ""),
1091 &format!("foo {}", style("バ").red().force_styling(true))
1092 );
1093 let s = format!("foo {}", style("バー").red().force_styling(true));
1094 assert_eq!(
1095 &truncate_str(&s, 2, "!!!"),
1096 &format!("!!!{}", style("").red().force_styling(true))
1097 );
1098}
1099
1100#[test]
1101fn test_truncate_str_no_ansi() {
1102 assert_eq!(&truncate_str("foo bar", 7, "!"), "foo bar");
1103 assert_eq!(&truncate_str("foo bar", 5, ""), "foo b");
1104 assert_eq!(&truncate_str("foo bar", 5, "!"), "foo !");
1105 assert_eq!(&truncate_str("foo bar baz", 10, "..."), "foo bar...");
1106 assert_eq!(&truncate_str("foo bar", 0, ""), "");
1107 assert_eq!(&truncate_str("foo bar", 0, "!"), "!");
1108 assert_eq!(&truncate_str("foo bar", 2, "!!!"), "!!!");
1109 assert_eq!(&truncate_str("ab", 2, "!!!"), "ab");
1110}
1111
1112#[test]
1113fn test_pad_str() {
1114 assert_eq!(pad_str("foo", 7, Alignment::Center, None), " foo ");
1115 assert_eq!(pad_str("foo", 7, Alignment::Left, None), "foo ");
1116 assert_eq!(pad_str("foo", 7, Alignment::Right, None), " foo");
1117 assert_eq!(pad_str("foo", 3, Alignment::Left, None), "foo");
1118 assert_eq!(pad_str("foobar", 3, Alignment::Left, None), "foobar");
1119 assert_eq!(pad_str("foobar", 3, Alignment::Left, Some("")), "foo");
1120 assert_eq!(
1121 pad_str("foobarbaz", 6, Alignment::Left, Some("...")),
1122 "foo..."
1123 );
1124}
1125
1126#[test]
1127fn test_pad_str_with() {
1128 assert_eq!(
1129 pad_str_with("foo", 7, Alignment::Center, None, '#'),
1130 "##foo##"
1131 );
1132 assert_eq!(
1133 pad_str_with("foo", 7, Alignment::Left, None, '#'),
1134 "foo####"
1135 );
1136 assert_eq!(
1137 pad_str_with("foo", 7, Alignment::Right, None, '#'),
1138 "####foo"
1139 );
1140 assert_eq!(pad_str_with("foo", 3, Alignment::Left, None, '#'), "foo");
1141 assert_eq!(
1142 pad_str_with("foobar", 3, Alignment::Left, None, '#'),
1143 "foobar"
1144 );
1145 assert_eq!(
1146 pad_str_with("foobar", 3, Alignment::Left, Some(""), '#'),
1147 "foo"
1148 );
1149 assert_eq!(
1150 pad_str_with("foobarbaz", 6, Alignment::Left, Some("..."), '#'),
1151 "foo..."
1152 );
1153}
1154
1155#[test]
1156fn test_attributes_single() {
1157 for attr in Attribute::MAP {
1158 let attrs = Attributes::new().insert(attr);
1159 assert_eq!(attrs.bits().collect::<Vec<_>>(), [attr as u16]);
1160 assert_eq!(attrs.attrs().collect::<Vec<_>>(), [attr]);
1161 assert_eq!(format!("{attrs:?}"), format!("{{{:?}}}", attr));
1162 }
1163}
1164
1165#[test]
1166fn test_attributes_many() {
1167 let tests: [&[Attribute]; 3] = [
1168 &[
1169 Attribute::Bold,
1170 Attribute::Underlined,
1171 Attribute::BlinkFast,
1172 Attribute::Hidden,
1173 ],
1174 &[
1175 Attribute::Dim,
1176 Attribute::Italic,
1177 Attribute::Blink,
1178 Attribute::Reverse,
1179 Attribute::StrikeThrough,
1180 ],
1181 &Attribute::MAP,
1182 ];
1183 for test_attrs in tests {
1184 let mut attrs = Attributes::new();
1185 for attr in test_attrs {
1186 attrs = attrs.insert(*attr);
1187 }
1188 assert_eq!(
1189 attrs.bits().collect::<Vec<_>>(),
1190 test_attrs
1191 .iter()
1192 .map(|attr| *attr as u16)
1193 .collect::<Vec<_>>()
1194 );
1195 assert_eq!(&attrs.attrs().collect::<Vec<_>>(), test_attrs);
1196 }
1197}