1use alloc::borrow::Cow;
2use core::{
3 fmt::{self, Debug, Formatter},
4 sync::atomic::{AtomicBool, Ordering},
5};
6use std::env;
7
8use once_cell::sync::Lazy;
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
21static STDOUT_COLORS: Lazy<AtomicBool> =
22 Lazy::new(|| AtomicBool::new(default_colors_enabled(&Term::stdout())));
23static STDERR_COLORS: Lazy<AtomicBool> =
24 Lazy::new(|| AtomicBool::new(default_colors_enabled(&Term::stderr())));
25
26#[inline]
34pub fn colors_enabled() -> bool {
35 STDOUT_COLORS.load(Ordering::Relaxed)
36}
37
38#[inline]
43pub fn set_colors_enabled(val: bool) {
44 STDOUT_COLORS.store(val, Ordering::Relaxed)
45}
46
47#[inline]
55pub fn colors_enabled_stderr() -> bool {
56 STDERR_COLORS.load(Ordering::Relaxed)
57}
58
59#[inline]
64pub fn set_colors_enabled_stderr(val: bool) {
65 STDERR_COLORS.store(val, Ordering::Relaxed)
66}
67
68pub fn measure_text_width(s: &str) -> usize {
70 #[cfg(feature = "ansi-parsing")]
71 {
72 AnsiCodeIterator::new(s)
73 .filter_map(|(s, is_ansi)| match is_ansi {
74 false => Some(str_width(s)),
75 true => None,
76 })
77 .sum()
78 }
79 #[cfg(not(feature = "ansi-parsing"))]
80 {
81 str_width(s)
82 }
83}
84
85#[derive(Copy, Clone, Debug, PartialEq, Eq)]
87pub enum Color {
88 Black,
89 Red,
90 Green,
91 Yellow,
92 Blue,
93 Magenta,
94 Cyan,
95 White,
96 Color256(u8),
97}
98
99impl Color {
100 #[inline]
101 fn ansi_num(self) -> usize {
102 match self {
103 Color::Black => 0,
104 Color::Red => 1,
105 Color::Green => 2,
106 Color::Yellow => 3,
107 Color::Blue => 4,
108 Color::Magenta => 5,
109 Color::Cyan => 6,
110 Color::White => 7,
111 Color::Color256(x) => x as usize,
112 }
113 }
114
115 #[inline]
116 fn is_color256(self) -> bool {
117 #[allow(clippy::match_like_matches_macro)]
118 match self {
119 Color::Color256(_) => true,
120 _ => false,
121 }
122 }
123}
124
125#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
127#[repr(u16)]
128pub enum Attribute {
129 Bold = 0,
132 Dim = 1,
133 Italic = 2,
134 Underlined = 3,
135 Blink = 4,
136 BlinkFast = 5,
137 Reverse = 6,
138 Hidden = 7,
139 StrikeThrough = 8,
140}
141
142impl Attribute {
143 const MAP: [Attribute; 9] = [
144 Attribute::Bold,
145 Attribute::Dim,
146 Attribute::Italic,
147 Attribute::Underlined,
148 Attribute::Blink,
149 Attribute::BlinkFast,
150 Attribute::Reverse,
151 Attribute::Hidden,
152 Attribute::StrikeThrough,
153 ];
154}
155
156#[derive(Clone, Copy, PartialEq, Eq)]
157struct Attributes(u16);
158
159impl Attributes {
160 #[inline]
161 const fn new() -> Self {
162 Self(0)
163 }
164
165 #[inline]
166 #[must_use]
167 const fn insert(mut self, attr: Attribute) -> Self {
168 let bit = attr as u16;
169 self.0 |= 1 << bit;
170 self
171 }
172
173 #[inline]
174 const fn bits(self) -> BitsIter {
175 BitsIter(self.0)
176 }
177
178 #[inline]
179 fn attrs(self) -> impl Iterator<Item = Attribute> {
180 self.bits().map(|bit| Attribute::MAP[bit as usize])
181 }
182
183 #[inline]
184 fn is_empty(self) -> bool {
185 self.0 == 0
186 }
187}
188
189impl fmt::Display for Attributes {
190 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
191 for ansi in self.bits().map(|bit| bit + 1) {
192 write!(f, "\x1b[{ansi}m")?;
193 }
194 Ok(())
195 }
196}
197
198struct BitsIter(u16);
199
200impl Iterator for BitsIter {
201 type Item = u16;
202
203 fn next(&mut self) -> Option<Self::Item> {
204 if self.0 == 0 {
205 return None;
206 }
207 let bit = self.0.trailing_zeros();
208 self.0 ^= (1 << bit) as u16;
209 Some(bit as u16)
210 }
211}
212
213impl Debug for Attributes {
214 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
215 f.debug_set().entries(self.attrs()).finish()
216 }
217}
218
219#[derive(Copy, Clone, Debug, PartialEq, Eq)]
221pub enum Alignment {
222 Left,
223 Center,
224 Right,
225}
226
227#[derive(Clone, Debug, PartialEq, Eq)]
229pub struct Style {
230 fg: Option<Color>,
231 bg: Option<Color>,
232 fg_bright: bool,
233 bg_bright: bool,
234 attrs: Attributes,
235 force: Option<bool>,
236 for_stderr: bool,
237}
238
239impl Default for Style {
240 fn default() -> Self {
241 Self::new()
242 }
243}
244
245impl Style {
246 pub const fn new() -> Self {
248 Self {
249 fg: None,
250 bg: None,
251 fg_bright: false,
252 bg_bright: false,
253 attrs: Attributes::new(),
254 force: None,
255 for_stderr: false,
256 }
257 }
258
259 pub fn from_dotted_str(s: &str) -> Self {
267 let mut rv = Self::new();
268 for part in s.split('.') {
269 rv = match part {
270 "black" => rv.black(),
271 "red" => rv.red(),
272 "green" => rv.green(),
273 "yellow" => rv.yellow(),
274 "blue" => rv.blue(),
275 "magenta" => rv.magenta(),
276 "cyan" => rv.cyan(),
277 "white" => rv.white(),
278 "bright" => rv.bright(),
279 "on_black" => rv.on_black(),
280 "on_red" => rv.on_red(),
281 "on_green" => rv.on_green(),
282 "on_yellow" => rv.on_yellow(),
283 "on_blue" => rv.on_blue(),
284 "on_magenta" => rv.on_magenta(),
285 "on_cyan" => rv.on_cyan(),
286 "on_white" => rv.on_white(),
287 "on_bright" => rv.on_bright(),
288 "bold" => rv.bold(),
289 "dim" => rv.dim(),
290 "underlined" => rv.underlined(),
291 "blink" => rv.blink(),
292 "blink_fast" => rv.blink_fast(),
293 "reverse" => rv.reverse(),
294 "hidden" => rv.hidden(),
295 "strikethrough" => rv.strikethrough(),
296 on_c if on_c.starts_with("on_") => {
297 if let Ok(n) = on_c[3..].parse::<u8>() {
298 rv.on_color256(n)
299 } else {
300 continue;
301 }
302 }
303 c => {
304 if let Ok(n) = c.parse::<u8>() {
305 rv.color256(n)
306 } else {
307 continue;
308 }
309 }
310 };
311 }
312 rv
313 }
314
315 pub fn apply_to<D>(&self, val: D) -> StyledObject<D> {
317 StyledObject {
318 style: self.clone(),
319 val,
320 }
321 }
322
323 #[inline]
327 pub const fn force_styling(mut self, value: bool) -> Self {
328 self.force = Some(value);
329 self
330 }
331
332 #[inline]
334 pub const fn for_stderr(mut self) -> Self {
335 self.for_stderr = true;
336 self
337 }
338
339 #[inline]
343 pub const fn for_stdout(mut self) -> Self {
344 self.for_stderr = false;
345 self
346 }
347
348 #[inline]
350 pub const fn fg(mut self, color: Color) -> Self {
351 self.fg = Some(color);
352 self
353 }
354
355 #[inline]
357 pub const fn bg(mut self, color: Color) -> Self {
358 self.bg = Some(color);
359 self
360 }
361
362 #[inline]
364 pub const fn attr(mut self, attr: Attribute) -> Self {
365 self.attrs = self.attrs.insert(attr);
366 self
367 }
368
369 #[inline]
370 pub const fn black(self) -> Self {
371 self.fg(Color::Black)
372 }
373 #[inline]
374 pub const fn red(self) -> Self {
375 self.fg(Color::Red)
376 }
377 #[inline]
378 pub const fn green(self) -> Self {
379 self.fg(Color::Green)
380 }
381 #[inline]
382 pub const fn yellow(self) -> Self {
383 self.fg(Color::Yellow)
384 }
385 #[inline]
386 pub const fn blue(self) -> Self {
387 self.fg(Color::Blue)
388 }
389 #[inline]
390 pub const fn magenta(self) -> Self {
391 self.fg(Color::Magenta)
392 }
393 #[inline]
394 pub const fn cyan(self) -> Self {
395 self.fg(Color::Cyan)
396 }
397 #[inline]
398 pub const fn white(self) -> Self {
399 self.fg(Color::White)
400 }
401 #[inline]
402 pub const fn color256(self, color: u8) -> Self {
403 self.fg(Color::Color256(color))
404 }
405
406 #[inline]
407 pub const fn bright(mut self) -> Self {
408 self.fg_bright = true;
409 self
410 }
411
412 #[inline]
413 pub const fn on_black(self) -> Self {
414 self.bg(Color::Black)
415 }
416 #[inline]
417 pub const fn on_red(self) -> Self {
418 self.bg(Color::Red)
419 }
420 #[inline]
421 pub const fn on_green(self) -> Self {
422 self.bg(Color::Green)
423 }
424 #[inline]
425 pub const fn on_yellow(self) -> Self {
426 self.bg(Color::Yellow)
427 }
428 #[inline]
429 pub const fn on_blue(self) -> Self {
430 self.bg(Color::Blue)
431 }
432 #[inline]
433 pub const fn on_magenta(self) -> Self {
434 self.bg(Color::Magenta)
435 }
436 #[inline]
437 pub const fn on_cyan(self) -> Self {
438 self.bg(Color::Cyan)
439 }
440 #[inline]
441 pub const fn on_white(self) -> Self {
442 self.bg(Color::White)
443 }
444 #[inline]
445 pub const fn on_color256(self, color: u8) -> Self {
446 self.bg(Color::Color256(color))
447 }
448
449 #[inline]
450 pub const fn on_bright(mut self) -> Self {
451 self.bg_bright = true;
452 self
453 }
454
455 #[inline]
456 pub const fn bold(self) -> Self {
457 self.attr(Attribute::Bold)
458 }
459 #[inline]
460 pub const fn dim(self) -> Self {
461 self.attr(Attribute::Dim)
462 }
463 #[inline]
464 pub const fn italic(self) -> Self {
465 self.attr(Attribute::Italic)
466 }
467 #[inline]
468 pub const fn underlined(self) -> Self {
469 self.attr(Attribute::Underlined)
470 }
471 #[inline]
472 pub const fn blink(self) -> Self {
473 self.attr(Attribute::Blink)
474 }
475 #[inline]
476 pub const fn blink_fast(self) -> Self {
477 self.attr(Attribute::BlinkFast)
478 }
479 #[inline]
480 pub const fn reverse(self) -> Self {
481 self.attr(Attribute::Reverse)
482 }
483 #[inline]
484 pub const fn hidden(self) -> Self {
485 self.attr(Attribute::Hidden)
486 }
487 #[inline]
488 pub const fn strikethrough(self) -> Self {
489 self.attr(Attribute::StrikeThrough)
490 }
491}
492
493pub fn style<D>(val: D) -> StyledObject<D> {
510 Style::new().apply_to(val)
511}
512
513#[derive(Clone)]
515pub struct StyledObject<D> {
516 style: Style,
517 val: D,
518}
519
520impl<D> StyledObject<D> {
521 #[inline]
525 pub fn force_styling(mut self, value: bool) -> StyledObject<D> {
526 self.style = self.style.force_styling(value);
527 self
528 }
529
530 #[inline]
532 pub fn for_stderr(mut self) -> StyledObject<D> {
533 self.style = self.style.for_stderr();
534 self
535 }
536
537 #[inline]
541 pub const fn for_stdout(mut self) -> StyledObject<D> {
542 self.style = self.style.for_stdout();
543 self
544 }
545
546 #[inline]
548 pub const fn fg(mut self, color: Color) -> StyledObject<D> {
549 self.style = self.style.fg(color);
550 self
551 }
552
553 #[inline]
555 pub const fn bg(mut self, color: Color) -> StyledObject<D> {
556 self.style = self.style.bg(color);
557 self
558 }
559
560 #[inline]
562 pub const fn attr(mut self, attr: Attribute) -> StyledObject<D> {
563 self.style = self.style.attr(attr);
564 self
565 }
566
567 #[inline]
568 pub const fn black(self) -> StyledObject<D> {
569 self.fg(Color::Black)
570 }
571 #[inline]
572 pub const fn red(self) -> StyledObject<D> {
573 self.fg(Color::Red)
574 }
575 #[inline]
576 pub const fn green(self) -> StyledObject<D> {
577 self.fg(Color::Green)
578 }
579 #[inline]
580 pub const fn yellow(self) -> StyledObject<D> {
581 self.fg(Color::Yellow)
582 }
583 #[inline]
584 pub const fn blue(self) -> StyledObject<D> {
585 self.fg(Color::Blue)
586 }
587 #[inline]
588 pub const fn magenta(self) -> StyledObject<D> {
589 self.fg(Color::Magenta)
590 }
591 #[inline]
592 pub const fn cyan(self) -> StyledObject<D> {
593 self.fg(Color::Cyan)
594 }
595 #[inline]
596 pub const fn white(self) -> StyledObject<D> {
597 self.fg(Color::White)
598 }
599 #[inline]
600 pub const fn color256(self, color: u8) -> StyledObject<D> {
601 self.fg(Color::Color256(color))
602 }
603
604 #[inline]
605 pub const fn bright(mut self) -> StyledObject<D> {
606 self.style = self.style.bright();
607 self
608 }
609
610 #[inline]
611 pub const fn on_black(self) -> StyledObject<D> {
612 self.bg(Color::Black)
613 }
614 #[inline]
615 pub const fn on_red(self) -> StyledObject<D> {
616 self.bg(Color::Red)
617 }
618 #[inline]
619 pub const fn on_green(self) -> StyledObject<D> {
620 self.bg(Color::Green)
621 }
622 #[inline]
623 pub const fn on_yellow(self) -> StyledObject<D> {
624 self.bg(Color::Yellow)
625 }
626 #[inline]
627 pub const fn on_blue(self) -> StyledObject<D> {
628 self.bg(Color::Blue)
629 }
630 #[inline]
631 pub const fn on_magenta(self) -> StyledObject<D> {
632 self.bg(Color::Magenta)
633 }
634 #[inline]
635 pub const fn on_cyan(self) -> StyledObject<D> {
636 self.bg(Color::Cyan)
637 }
638 #[inline]
639 pub const fn on_white(self) -> StyledObject<D> {
640 self.bg(Color::White)
641 }
642 #[inline]
643 pub const fn on_color256(self, color: u8) -> StyledObject<D> {
644 self.bg(Color::Color256(color))
645 }
646
647 #[inline]
648 pub const fn on_bright(mut self) -> StyledObject<D> {
649 self.style = self.style.on_bright();
650 self
651 }
652
653 #[inline]
654 pub const fn bold(self) -> StyledObject<D> {
655 self.attr(Attribute::Bold)
656 }
657 #[inline]
658 pub const fn dim(self) -> StyledObject<D> {
659 self.attr(Attribute::Dim)
660 }
661 #[inline]
662 pub const fn italic(self) -> StyledObject<D> {
663 self.attr(Attribute::Italic)
664 }
665 #[inline]
666 pub const fn underlined(self) -> StyledObject<D> {
667 self.attr(Attribute::Underlined)
668 }
669 #[inline]
670 pub const fn blink(self) -> StyledObject<D> {
671 self.attr(Attribute::Blink)
672 }
673 #[inline]
674 pub const fn blink_fast(self) -> StyledObject<D> {
675 self.attr(Attribute::BlinkFast)
676 }
677 #[inline]
678 pub const fn reverse(self) -> StyledObject<D> {
679 self.attr(Attribute::Reverse)
680 }
681 #[inline]
682 pub const fn hidden(self) -> StyledObject<D> {
683 self.attr(Attribute::Hidden)
684 }
685 #[inline]
686 pub const fn strikethrough(self) -> StyledObject<D> {
687 self.attr(Attribute::StrikeThrough)
688 }
689}
690
691macro_rules! impl_fmt {
692 ($name:ident) => {
693 impl<D: fmt::$name> fmt::$name for StyledObject<D> {
694 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
695 let mut reset = false;
696 if self
697 .style
698 .force
699 .unwrap_or_else(|| match self.style.for_stderr {
700 true => colors_enabled_stderr(),
701 false => colors_enabled(),
702 })
703 {
704 if let Some(fg) = self.style.fg {
705 if fg.is_color256() {
706 write!(f, "\x1b[38;5;{}m", fg.ansi_num())?;
707 } else if self.style.fg_bright {
708 write!(f, "\x1b[38;5;{}m", fg.ansi_num() + 8)?;
709 } else {
710 write!(f, "\x1b[{}m", fg.ansi_num() + 30)?;
711 }
712 reset = true;
713 }
714 if let Some(bg) = self.style.bg {
715 if bg.is_color256() {
716 write!(f, "\x1b[48;5;{}m", bg.ansi_num())?;
717 } else if self.style.bg_bright {
718 write!(f, "\x1b[48;5;{}m", bg.ansi_num() + 8)?;
719 } else {
720 write!(f, "\x1b[{}m", bg.ansi_num() + 40)?;
721 }
722 reset = true;
723 }
724 if !self.style.attrs.is_empty() {
725 write!(f, "{}", self.style.attrs)?;
726 reset = true;
727 }
728 }
729 fmt::$name::fmt(&self.val, f)?;
730 if reset {
731 write!(f, "\x1b[0m")?;
732 }
733 Ok(())
734 }
735 }
736 };
737}
738
739impl_fmt!(Binary);
740impl_fmt!(Debug);
741impl_fmt!(Display);
742impl_fmt!(LowerExp);
743impl_fmt!(LowerHex);
744impl_fmt!(Octal);
745impl_fmt!(Pointer);
746impl_fmt!(UpperExp);
747impl_fmt!(UpperHex);
748
749#[derive(Copy, Clone)]
762pub struct Emoji<'a, 'b>(pub &'a str, pub &'b str);
763
764impl<'a, 'b> Emoji<'a, 'b> {
765 pub fn new(emoji: &'a str, fallback: &'b str) -> Emoji<'a, 'b> {
766 Emoji(emoji, fallback)
767 }
768}
769
770impl fmt::Display for Emoji<'_, '_> {
771 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
772 if wants_emoji() {
773 write!(f, "{}", self.0)
774 } else {
775 write!(f, "{}", self.1)
776 }
777 }
778}
779
780fn str_width(s: &str) -> usize {
781 #[cfg(feature = "unicode-width")]
782 {
783 use unicode_width::UnicodeWidthStr;
784 s.width()
785 }
786 #[cfg(not(feature = "unicode-width"))]
787 {
788 s.chars().count()
789 }
790}
791
792#[cfg(feature = "ansi-parsing")]
793pub(crate) fn char_width(c: char) -> usize {
794 #[cfg(feature = "unicode-width")]
795 {
796 use unicode_width::UnicodeWidthChar;
797 c.width().unwrap_or(0)
798 }
799 #[cfg(not(feature = "unicode-width"))]
800 {
801 let _c = c;
802 1
803 }
804}
805
806#[cfg(not(feature = "ansi-parsing"))]
807pub(crate) fn char_width(_c: char) -> usize {
808 1
809}
810
811pub fn truncate_str<'a>(s: &'a str, width: usize, tail: &str) -> Cow<'a, str> {
818 if measure_text_width(s) <= width {
819 return Cow::Borrowed(s);
820 }
821
822 #[cfg(feature = "ansi-parsing")]
823 {
824 use core::cmp::Ordering;
825 let mut iter = AnsiCodeIterator::new(s);
826 let mut length = 0;
827 let mut rv = None;
828
829 while let Some(item) = iter.next() {
830 match item {
831 (s, false) => {
832 if rv.is_none() {
833 if str_width(s) + length > width.saturating_sub(str_width(tail)) {
834 let ts = iter.current_slice();
835
836 let mut s_byte = 0;
837 let mut s_width = 0;
838 let rest_width =
839 width.saturating_sub(str_width(tail)).saturating_sub(length);
840 for c in s.chars() {
841 s_byte += c.len_utf8();
842 s_width += char_width(c);
843 match s_width.cmp(&rest_width) {
844 Ordering::Equal => break,
845 Ordering::Greater => {
846 s_byte -= c.len_utf8();
847 break;
848 }
849 Ordering::Less => continue,
850 }
851 }
852
853 let idx = ts.len() - s.len() + s_byte;
854 let mut buf = ts[..idx].to_string();
855 buf.push_str(tail);
856 rv = Some(buf);
857 }
858 length += str_width(s);
859 }
860 }
861 (s, true) => {
862 if let Some(ref mut rv) = rv {
863 rv.push_str(s);
864 }
865 }
866 }
867 }
868
869 if let Some(buf) = rv {
870 Cow::Owned(buf)
871 } else {
872 Cow::Borrowed(s)
873 }
874 }
875
876 #[cfg(not(feature = "ansi-parsing"))]
877 {
878 Cow::Owned(format!(
879 "{}{}",
880 &s[..width.saturating_sub(tail.len())],
881 tail
882 ))
883 }
884}
885
886pub fn pad_str<'a>(
893 s: &'a str,
894 width: usize,
895 align: Alignment,
896 truncate: Option<&str>,
897) -> Cow<'a, str> {
898 pad_str_with(s, width, align, truncate, ' ')
899}
900pub fn pad_str_with<'a>(
907 s: &'a str,
908 width: usize,
909 align: Alignment,
910 truncate: Option<&str>,
911 pad: char,
912) -> Cow<'a, str> {
913 let cols = measure_text_width(s);
914
915 if cols >= width {
916 return match truncate {
917 None => Cow::Borrowed(s),
918 Some(tail) => truncate_str(s, width, tail),
919 };
920 }
921
922 let diff = width - cols;
923
924 let (left_pad, right_pad) = match align {
925 Alignment::Left => (0, diff),
926 Alignment::Right => (diff, 0),
927 Alignment::Center => (diff / 2, diff - diff / 2),
928 };
929
930 let mut rv = String::new();
931 for _ in 0..left_pad {
932 rv.push(pad);
933 }
934 rv.push_str(s);
935 for _ in 0..right_pad {
936 rv.push(pad);
937 }
938 Cow::Owned(rv)
939}
940
941#[test]
942fn test_text_width() {
943 let s = style("foo")
944 .red()
945 .on_black()
946 .bold()
947 .force_styling(true)
948 .to_string();
949
950 assert_eq!(
951 measure_text_width(&s),
952 if cfg!(feature = "ansi-parsing") {
953 3
954 } else {
955 21
956 }
957 );
958
959 let s = style("🐶 <3").red().force_styling(true).to_string();
960
961 assert_eq!(
962 measure_text_width(&s),
963 match (
964 cfg!(feature = "ansi-parsing"),
965 cfg!(feature = "unicode-width")
966 ) {
967 (true, true) => 5, (true, false) => 4, (false, true) => 14, (false, false) => 13, }
972 );
973}
974
975#[test]
976#[cfg(all(feature = "unicode-width", feature = "ansi-parsing"))]
977fn test_truncate_str() {
978 let s = format!("foo {}", style("bar").red().force_styling(true));
979 assert_eq!(
980 &truncate_str(&s, 5, ""),
981 &format!("foo {}", style("b").red().force_styling(true))
982 );
983 let s = format!("foo {}", style("bar").red().force_styling(true));
984 assert_eq!(
985 &truncate_str(&s, 5, "!"),
986 &format!("foo {}", style("!").red().force_styling(true))
987 );
988 let s = format!("foo {} baz", style("bar").red().force_styling(true));
989 assert_eq!(
990 &truncate_str(&s, 10, "..."),
991 &format!("foo {}...", style("bar").red().force_styling(true))
992 );
993 let s = format!("foo {}", style("バー").red().force_styling(true));
994 assert_eq!(
995 &truncate_str(&s, 5, ""),
996 &format!("foo {}", style("").red().force_styling(true))
997 );
998 let s = format!("foo {}", style("バー").red().force_styling(true));
999 assert_eq!(
1000 &truncate_str(&s, 6, ""),
1001 &format!("foo {}", style("バ").red().force_styling(true))
1002 );
1003 let s = format!("foo {}", style("バー").red().force_styling(true));
1004 assert_eq!(
1005 &truncate_str(&s, 2, "!!!"),
1006 &format!("!!!{}", style("").red().force_styling(true))
1007 );
1008}
1009
1010#[test]
1011fn test_truncate_str_no_ansi() {
1012 assert_eq!(&truncate_str("foo bar", 7, "!"), "foo bar");
1013 assert_eq!(&truncate_str("foo bar", 5, ""), "foo b");
1014 assert_eq!(&truncate_str("foo bar", 5, "!"), "foo !");
1015 assert_eq!(&truncate_str("foo bar baz", 10, "..."), "foo bar...");
1016 assert_eq!(&truncate_str("foo bar", 0, ""), "");
1017 assert_eq!(&truncate_str("foo bar", 0, "!"), "!");
1018 assert_eq!(&truncate_str("foo bar", 2, "!!!"), "!!!");
1019 assert_eq!(&truncate_str("ab", 2, "!!!"), "ab");
1020}
1021
1022#[test]
1023fn test_pad_str() {
1024 assert_eq!(pad_str("foo", 7, Alignment::Center, None), " foo ");
1025 assert_eq!(pad_str("foo", 7, Alignment::Left, None), "foo ");
1026 assert_eq!(pad_str("foo", 7, Alignment::Right, None), " foo");
1027 assert_eq!(pad_str("foo", 3, Alignment::Left, None), "foo");
1028 assert_eq!(pad_str("foobar", 3, Alignment::Left, None), "foobar");
1029 assert_eq!(pad_str("foobar", 3, Alignment::Left, Some("")), "foo");
1030 assert_eq!(
1031 pad_str("foobarbaz", 6, Alignment::Left, Some("...")),
1032 "foo..."
1033 );
1034}
1035
1036#[test]
1037fn test_pad_str_with() {
1038 assert_eq!(
1039 pad_str_with("foo", 7, Alignment::Center, None, '#'),
1040 "##foo##"
1041 );
1042 assert_eq!(
1043 pad_str_with("foo", 7, Alignment::Left, None, '#'),
1044 "foo####"
1045 );
1046 assert_eq!(
1047 pad_str_with("foo", 7, Alignment::Right, None, '#'),
1048 "####foo"
1049 );
1050 assert_eq!(pad_str_with("foo", 3, Alignment::Left, None, '#'), "foo");
1051 assert_eq!(
1052 pad_str_with("foobar", 3, Alignment::Left, None, '#'),
1053 "foobar"
1054 );
1055 assert_eq!(
1056 pad_str_with("foobar", 3, Alignment::Left, Some(""), '#'),
1057 "foo"
1058 );
1059 assert_eq!(
1060 pad_str_with("foobarbaz", 6, Alignment::Left, Some("..."), '#'),
1061 "foo..."
1062 );
1063}
1064
1065#[test]
1066fn test_attributes_single() {
1067 for attr in Attribute::MAP {
1068 let attrs = Attributes::new().insert(attr);
1069 assert_eq!(attrs.bits().collect::<Vec<_>>(), [attr as u16]);
1070 assert_eq!(attrs.attrs().collect::<Vec<_>>(), [attr]);
1071 assert_eq!(format!("{attrs:?}"), format!("{{{:?}}}", attr));
1072 }
1073}
1074
1075#[test]
1076fn test_attributes_many() {
1077 let tests: [&[Attribute]; 3] = [
1078 &[
1079 Attribute::Bold,
1080 Attribute::Underlined,
1081 Attribute::BlinkFast,
1082 Attribute::Hidden,
1083 ],
1084 &[
1085 Attribute::Dim,
1086 Attribute::Italic,
1087 Attribute::Blink,
1088 Attribute::Reverse,
1089 Attribute::StrikeThrough,
1090 ],
1091 &Attribute::MAP,
1092 ];
1093 for test_attrs in tests {
1094 let mut attrs = Attributes::new();
1095 for attr in test_attrs {
1096 attrs = attrs.insert(*attr);
1097 }
1098 assert_eq!(
1099 attrs.bits().collect::<Vec<_>>(),
1100 test_attrs
1101 .iter()
1102 .map(|attr| *attr as u16)
1103 .collect::<Vec<_>>()
1104 );
1105 assert_eq!(&attrs.attrs().collect::<Vec<_>>(), test_attrs);
1106 }
1107}