Skip to content

Commit 01957a7

Browse files
glehmanndjc
authored andcommitted
Fix wide_msg truncation with a colored message
ANSI control sequences were not taken into account.
1 parent e836112 commit 01957a7

File tree

1 file changed

+96
-10
lines changed

1 file changed

+96
-10
lines changed

src/style.rs

Lines changed: 96 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
use std::collections::HashMap;
2-
use std::fmt::{self, Write};
2+
use std::fmt::{self, Formatter, Write};
33
use std::mem;
44
#[cfg(not(target_arch = "wasm32"))]
55
use std::time::Instant;
6+
#[cfg(feature = "unicode-width")]
7+
use unicode_width::UnicodeWidthChar;
68

7-
use console::{measure_text_width, Style};
9+
use console::{measure_text_width, AnsiCodeIterator, Style};
810
#[cfg(feature = "unicode-segmentation")]
911
use unicode_segmentation::UnicodeSegmentation;
1012
#[cfg(target_arch = "wasm32")]
@@ -742,15 +744,11 @@ impl fmt::Display for PaddedStringDisplay<'_> {
742744
return f.write_str(self.str);
743745
} else if excess > 0 {
744746
let (start, end) = match self.align {
745-
Alignment::Left => (0, self.str.len() - excess),
746-
Alignment::Right => (excess, self.str.len()),
747-
Alignment::Center => (
748-
excess / 2,
749-
self.str.len() - excess.saturating_sub(excess / 2),
750-
),
747+
Alignment::Left => (0, cols - excess),
748+
Alignment::Right => (excess, cols),
749+
Alignment::Center => (excess / 2, cols - excess.saturating_sub(excess / 2)),
751750
};
752-
753-
return f.write_str(self.str.get(start..end).unwrap_or(self.str));
751+
return write_ansi_range(f, self.str, start, end);
754752
}
755753

756754
let diff = self.width.saturating_sub(cols);
@@ -771,6 +769,38 @@ impl fmt::Display for PaddedStringDisplay<'_> {
771769
}
772770
}
773771

772+
/// Write the visible text between start and end. The ansi escape
773+
/// sequences are written unchanged.
774+
pub fn write_ansi_range(
775+
formatter: &mut Formatter,
776+
text: &str,
777+
start: usize,
778+
end: usize,
779+
) -> fmt::Result {
780+
let mut pos = 0;
781+
for (s, is_ansi) in AnsiCodeIterator::new(text) {
782+
if is_ansi {
783+
formatter.write_str(s)?;
784+
} else if pos < end {
785+
for c in s.chars() {
786+
#[cfg(feature = "unicode-width")]
787+
let c_width = c.width().unwrap_or(0);
788+
#[cfg(not(feature = "unicode-width"))]
789+
let c_width = 1;
790+
if start <= pos && pos + c_width <= end {
791+
formatter.write_char(c)?;
792+
}
793+
pos += c_width;
794+
if pos > end {
795+
// no need to iterate over the rest of s
796+
break;
797+
}
798+
}
799+
}
800+
}
801+
Ok(())
802+
}
803+
774804
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
775805
enum Alignment {
776806
Left,
@@ -987,6 +1017,62 @@ mod tests {
9871017
assert_eq!(&buf[0], "fghijklmno");
9881018
}
9891019

1020+
#[test]
1021+
fn combinining_diacritical_truncation() {
1022+
const WIDTH: u16 = 10;
1023+
let pos = Arc::new(AtomicPosition::new());
1024+
let mut state = ProgressState::new(Some(10), pos);
1025+
let mut buf = Vec::new();
1026+
1027+
let style = ProgressStyle::with_template("{wide_msg}").unwrap();
1028+
state.message = TabExpandedString::NoTabs("abcdefghij\u{0308}klmnopqrst".into());
1029+
style.format_state(&state, &mut buf, WIDTH);
1030+
assert_eq!(&buf[0], "abcdefghij\u{0308}");
1031+
}
1032+
1033+
#[test]
1034+
fn color_align_truncation() {
1035+
let red = "\x1b[31m";
1036+
let green = "\x1b[32m";
1037+
let blue = "\x1b[34m";
1038+
let yellow = "\x1b[33m";
1039+
let magenta = "\x1b[35m";
1040+
let cyan = "\x1b[36m";
1041+
let white = "\x1b[37m";
1042+
1043+
let bold = "\x1b[1m";
1044+
let underline = "\x1b[4m";
1045+
let reset = "\x1b[0m";
1046+
let message = format!(
1047+
"{bold}{red}Hello,{reset} {green}{underline}Rustacean!{reset} {yellow}This {blue}is {magenta}a {cyan}multi-colored {white}string.{reset}"
1048+
);
1049+
1050+
const WIDTH: u16 = 10;
1051+
let pos = Arc::new(AtomicPosition::new());
1052+
let mut state = ProgressState::new(Some(10), pos);
1053+
let mut buf = Vec::new();
1054+
1055+
let style = ProgressStyle::with_template("{wide_msg}").unwrap();
1056+
state.message = TabExpandedString::NoTabs(message.clone().into());
1057+
style.format_state(&state, &mut buf, WIDTH);
1058+
assert_eq!(
1059+
&buf[0],
1060+
format!("{bold}{red}Hello,{reset} {green}{underline}Rus{reset}{yellow}{blue}{magenta}{cyan}{white}{reset}").as_str()
1061+
);
1062+
1063+
buf.clear();
1064+
let style = ProgressStyle::with_template("{wide_msg:>}").unwrap();
1065+
state.message = TabExpandedString::NoTabs(message.clone().into());
1066+
style.format_state(&state, &mut buf, WIDTH);
1067+
assert_eq!(&buf[0], format!("{bold}{red}{reset}{green}{underline}{reset}{yellow}{blue}{magenta}{cyan}ed {white}string.{reset}").as_str());
1068+
1069+
buf.clear();
1070+
let style = ProgressStyle::with_template("{wide_msg:^}").unwrap();
1071+
state.message = TabExpandedString::NoTabs(message.clone().into());
1072+
style.format_state(&state, &mut buf, WIDTH);
1073+
assert_eq!(&buf[0], format!("{bold}{red}{reset}{green}{underline}{reset}{yellow}his {blue}is {magenta}a {cyan}m{white}{reset}").as_str());
1074+
}
1075+
9901076
#[test]
9911077
fn multicolor_without_current_style() {
9921078
set_colors_enabled(true);

0 commit comments

Comments
 (0)