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 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#[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#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
181#[repr(u16)]
182pub enum Attribute {
183 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#[derive(Copy, Clone, Debug, PartialEq, Eq)]
275pub enum Alignment {
276 Left,
277 Center,
278 Right,
279}
280
281#[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 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 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 pub fn apply_to<D>(&self, val: D) -> StyledObject<D> {
401 StyledObject {
402 style: self.clone(),
403 val,
404 }
405 }
406
407 #[inline]
411 pub const fn force_styling(mut self, value: bool) -> Self {
412 self.force = Some(value);
413 self
414 }
415
416 #[inline]
418 pub const fn for_stderr(mut self) -> Self {
419 self.for_stderr = true;
420 self
421 }
422
423 #[inline]
427 pub const fn for_stdout(mut self) -> Self {
428 self.for_stderr = false;
429 self
430 }
431
432 #[inline]
434 pub const fn fg(mut self, color: Color) -> Self {
435 self.fg = Some(color);
436 self
437 }
438
439 #[inline]
441 pub const fn bg(mut self, color: Color) -> Self {
442 self.bg = Some(color);
443 self
444 }
445
446 #[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
585pub fn style<D>(val: D) -> StyledObject<D> {
602 Style::new().apply_to(val)
603}
604
605#[derive(Clone)]
607pub struct StyledObject<D> {
608 style: Style,
609 val: D,
610}
611
612impl<D> StyledObject<D> {
613 #[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 #[inline]
624 pub fn for_stderr(mut self) -> StyledObject<D> {
625 self.style = self.style.for_stderr();
626 self
627 }
628
629 #[inline]
633 pub const fn for_stdout(mut self) -> StyledObject<D> {
634 self.style = self.style.for_stdout();
635 self
636 }
637
638 #[inline]
640 pub const fn fg(mut self, color: Color) -> StyledObject<D> {
641 self.style = self.style.fg(color);
642 self
643 }
644
645 #[inline]
647 pub const fn bg(mut self, color: Color) -> StyledObject<D> {
648 self.style = self.style.bg(color);
649 self
650 }
651
652 #[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#[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
896pub(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
910pub 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 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
999pub 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}
1013pub 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, (true, false) => 4, (false, true) => 14, (false, false) => 13, }
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 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 let fg = "#€€";
1244 assert_eq!(fg.len(), 7);
1245
1246 let parsed_style = Style::from_dotted_str(fg);
1247
1248 assert_eq!(parsed_style, Style::default());
1250 }
1251
1252 #[test]
1253 fn test_style_from_non_ascii_bg() {
1254 let bg = "on_#€€";
1256 assert_eq!(bg.len(), 10);
1257
1258 let parsed_style = Style::from_dotted_str(bg);
1259
1260 assert_eq!(parsed_style, Style::default());
1262 }
1263
1264 #[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}"; 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 assert_eq!(&truncate_str(s, 4, "..."), "...");
1275 assert_eq!(&truncate_str(s, 1, "..."), "...");
1276 assert_eq!(&truncate_str("ab\u{4f60}cd", 4, ""), "ab\u{4f60}");
1278 }
1279
1280 #[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 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}