|
1 | 1 | use std::io::{Result, Write};
|
2 | 2 |
|
3 | 3 | use termcolor::{ColorSpec, WriteColor};
|
4 |
| -use unicode_width::UnicodeWidthStr; |
5 | 4 |
|
6 | 5 | use crate::{
|
7 | 6 | buffers::Buffers,
|
8 | 7 | table::{Dimension as TableDimension, HorizontalLine, TableFormat, VerticalLine},
|
9 | 8 | };
|
10 | 9 |
|
11 |
| -/// NOTE: `display_width()` is ported from https://github.com/phsym/prettytable-rs |
| 10 | +const ESC: char = '\x1b'; |
| 11 | + |
| 12 | +/// NOTE: `display_width()` is ported from https://docs.rs/ansi-width/0.1.0/src/ansi_width/lib.rs.html#9-55 |
12 | 13 | ///
|
13 | 14 | /// Return the display width of a unicode string.
|
14 | 15 | /// This functions takes ANSI-escaped color codes into account.
|
15 | 16 | pub(crate) fn display_width(text: &str) -> usize {
|
16 |
| - let width = UnicodeWidthStr::width(text); |
17 |
| - |
18 |
| - let mut state = 0; |
19 |
| - let mut hidden = 0; |
20 |
| - |
21 |
| - for c in text.chars() { |
22 |
| - state = match (state, c) { |
23 |
| - (0, '\u{1b}') => 1, |
24 |
| - (1, '[') => 2, |
25 |
| - (1, _) => 0, |
26 |
| - (2, 'm') => 3, |
27 |
| - _ => state, |
28 |
| - }; |
29 |
| - |
30 |
| - // We don't count escape characters as hidden as |
31 |
| - // UnicodeWidthStr::width already considers them. |
32 |
| - if state > 1 { |
33 |
| - hidden += 1; |
34 |
| - } |
35 |
| - |
36 |
| - if state == 3 { |
37 |
| - state = 0; |
| 17 | + let mut width = 0; |
| 18 | + let mut chars = text.chars(); |
| 19 | + |
| 20 | + // This lint is a false positive, because we use the iterator later, leading to |
| 21 | + // ownership issues if we follow the lint. |
| 22 | + #[allow(clippy::while_let_on_iterator)] |
| 23 | + while let Some(c) = chars.next() { |
| 24 | + // ESC starts escape sequences, so we need to take characters until the |
| 25 | + // end of the escape sequence. |
| 26 | + if c == ESC { |
| 27 | + let Some(c) = chars.next() else { |
| 28 | + break; |
| 29 | + }; |
| 30 | + match c { |
| 31 | + // String terminator character: ends other sequences |
| 32 | + // We probably won't encounter this but it's here for completeness. |
| 33 | + // Or for if we get passed invalid codes. |
| 34 | + '\\' => { |
| 35 | + // ignore |
| 36 | + } |
| 37 | + // Control Sequence Introducer: continue until `\x40-\x7C` |
| 38 | + '[' => while !matches!(chars.next(), Some('\x40'..='\x7C') | None) {}, |
| 39 | + // Operating System Command: continue until ST |
| 40 | + ']' => { |
| 41 | + let mut last = c; |
| 42 | + while let Some(new) = chars.next() { |
| 43 | + if new == '\x07' || (new == '\\' && last == ESC) { |
| 44 | + break; |
| 45 | + } |
| 46 | + last = new; |
| 47 | + } |
| 48 | + } |
| 49 | + // We don't know what character it is, best bet is to fall back to unicode width |
| 50 | + // The ESC is assumed to have 0 width in this case. |
| 51 | + _ => { |
| 52 | + width += unicode_width::UnicodeWidthChar::width(c).unwrap_or(0); |
| 53 | + } |
| 54 | + } |
| 55 | + } else { |
| 56 | + // If it's a normal character outside an escape sequence, use the |
| 57 | + // unicode width. |
| 58 | + width += unicode_width::UnicodeWidthChar::width(c).unwrap_or(0); |
38 | 59 | }
|
39 | 60 | }
|
40 |
| - |
41 |
| - width - hidden |
| 61 | + width |
42 | 62 | }
|
43 | 63 |
|
44 | 64 | pub fn transpose<T>(v: Vec<Vec<T>>) -> Vec<Vec<T>> {
|
@@ -104,6 +124,8 @@ pub(crate) fn print_horizontal_line(
|
104 | 124 | }
|
105 | 125 | }
|
106 | 126 | }
|
| 127 | + |
| 128 | + println(buffers)?; |
107 | 129 | }
|
108 | 130 |
|
109 | 131 | Ok(())
|
|
0 commit comments