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
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 pub fn apply_to<D>(&self, val: D) -> StyledObject<D> {
395 StyledObject {
396 style: self.clone(),
397 val,
398 }
399 }
400
401 #[inline]
405 pub const fn force_styling(mut self, value: bool) -> Self {
406 self.force = Some(value);
407 self
408 }
409
410 #[inline]
412 pub const fn for_stderr(mut self) -> Self {
413 self.for_stderr = true;
414 self
415 }
416
417 #[inline]
421 pub const fn for_stdout(mut self) -> Self {
422 self.for_stderr = false;
423 self
424 }
425
426 #[inline]
428 pub const fn fg(mut self, color: Color) -> Self {
429 self.fg = Some(color);
430 self
431 }
432
433 #[inline]
435 pub const fn bg(mut self, color: Color) -> Self {
436 self.bg = Some(color);
437 self
438 }
439
440 #[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
579pub fn style<D>(val: D) -> StyledObject<D> {
596 Style::new().apply_to(val)
597}
598
599#[derive(Clone)]
601pub struct StyledObject<D> {
602 style: Style,
603 val: D,
604}
605
606impl<D> StyledObject<D> {
607 #[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 #[inline]
618 pub fn for_stderr(mut self) -> StyledObject<D> {
619 self.style = self.style.for_stderr();
620 self
621 }
622
623 #[inline]
627 pub const fn for_stdout(mut self) -> StyledObject<D> {
628 self.style = self.style.for_stdout();
629 self
630 }
631
632 #[inline]
634 pub const fn fg(mut self, color: Color) -> StyledObject<D> {
635 self.style = self.style.fg(color);
636 self
637 }
638
639 #[inline]
641 pub const fn bg(mut self, color: Color) -> StyledObject<D> {
642 self.style = self.style.bg(color);
643 self
644 }
645
646 #[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#[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
909pub 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
984pub 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}
998pub 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, (true, false) => 4, (false, true) => 14, (false, false) => 13, }
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 let fg = "#€€";
1211 assert_eq!(fg.len(), 7);
1212
1213 let parsed_style = Style::from_dotted_str(fg);
1214
1215 assert_eq!(parsed_style, Style::default());
1217}
1218
1219#[test]
1220fn test_style_from_non_ascii_bg() {
1221 let bg = "on_#€€";
1223 assert_eq!(bg.len(), 10);
1224
1225 let parsed_style = Style::from_dotted_str(bg);
1226
1227 assert_eq!(parsed_style, Style::default());
1229}