diff options
| author | salmonik <salmonik@tuta.io> | 2026-10-29 17:25:50 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-10-29 17:25:50 +0100 |
| commit | eb68b1266ac833a00ee5a11b3e2d9a9d516d7a3c (patch) | |
| tree | 0ab54d0a37e48676425fe419f1e427bce6455cc2 | |
| parent | v2.1.1: color-code roulette numbers red and green (diff) | |
| download | zagan-bet-eb68b1266ac833a00ee5a11b3e2d9a9d516d7a3c.tar.gz zagan-bet-eb68b1266ac833a00ee5a11b3e2d9a9d516d7a3c.zip | |
v2.1.2: ignore ANSI when measuring board height
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | src/client/ui.rs | 24 | ||||
| -rw-r--r-- | tests/client_ui.rs | 17 |
3 files changed, 41 insertions, 2 deletions
| @@ -1,6 +1,6 @@ | |||
| 1 | [package] | 1 | [package] |
| 2 | name = "zagan-bet" | 2 | name = "zagan-bet" |
| 3 | version = "2.1.1" | 3 | version = "2.1.2" |
| 4 | edition = "2024" | 4 | edition = "2024" |
| 5 | 5 | ||
| 6 | [dependencies] | 6 | [dependencies] |
diff --git a/src/client/ui.rs b/src/client/ui.rs index 11cedc7..1a35db1 100644 --- a/src/client/ui.rs +++ b/src/client/ui.rs | |||
| @@ -159,7 +159,29 @@ pub fn parse_bet_arg(raw: &str) -> Result<u64, String> { | |||
| 159 | /// ASCII, Latin diacritics, combining marks, CJK, and the common emoji ranges | 159 | /// ASCII, Latin diacritics, combining marks, CJK, and the common emoji ranges |
| 160 | /// without adding a dependency. | 160 | /// without adding a dependency. |
| 161 | pub fn display_width(text: &str) -> usize { | 161 | pub fn display_width(text: &str) -> usize { |
| 162 | text.chars().map(char_width).sum() | 162 | let mut width = 0; |
| 163 | let mut chars = text.chars().peekable(); | ||
| 164 | |||
| 165 | while let Some(character) = chars.next() { | ||
| 166 | if character == '\x1b' { | ||
| 167 | if chars.peek() == Some(&'[') { | ||
| 168 | chars.next(); | ||
| 169 | // Skip CSI parameters until the final byte (@ through ~). | ||
| 170 | for character in chars.by_ref() { | ||
| 171 | if ('@'..='~').contains(&character) { | ||
| 172 | break; | ||
| 173 | } | ||
| 174 | } | ||
| 175 | } else { | ||
| 176 | // Two-character escape sequence. | ||
| 177 | chars.next(); | ||
| 178 | } | ||
| 179 | continue; | ||
| 180 | } | ||
| 181 | width += char_width(character); | ||
| 182 | } | ||
| 183 | |||
| 184 | width | ||
| 163 | } | 185 | } |
| 164 | 186 | ||
| 165 | fn char_width(c: char) -> usize { | 187 | fn char_width(c: char) -> usize { |
diff --git a/tests/client_ui.rs b/tests/client_ui.rs index ea432e0..f5e3c0e 100644 --- a/tests/client_ui.rs +++ b/tests/client_ui.rs | |||
| @@ -99,6 +99,23 @@ fn redraw_moves_up_by_line_count_minus_one() { | |||
| 99 | } | 99 | } |
| 100 | 100 | ||
| 101 | #[test] | 101 | #[test] |
| 102 | fn display_width_ignores_ansi_sequences() { | ||
| 103 | assert_eq!(display_width("\x1b[31m18\x1b[0m"), 2); | ||
| 104 | assert_eq!(display_width("\x1b[1;31m[ 18 ]\x1b[0m"), 6); | ||
| 105 | assert_eq!(display_width("plain"), 5); | ||
| 106 | } | ||
| 107 | |||
| 108 | #[test] | ||
| 109 | fn visual_line_count_ignores_ansi_sequences() { | ||
| 110 | let styled = format!("\x1b[31m{}\x1b[0m", "x".repeat(75)); | ||
| 111 | assert_eq!(visual_line_count(&[styled], 72), 2); | ||
| 112 | assert_eq!( | ||
| 113 | visual_line_count(&["\x1b[31m18\x1b[0m".to_string()], 80), | ||
| 114 | 1 | ||
| 115 | ); | ||
| 116 | } | ||
| 117 | |||
| 118 | #[test] | ||
| 102 | fn visual_line_count_accounts_for_terminal_wrapping() { | 119 | fn visual_line_count_accounts_for_terminal_wrapping() { |
| 103 | let lines = vec