|
2 | 2 | use std::io::{Error, ErrorKind, Write}; |
3 | 3 | use std::str; |
4 | 4 |
|
| 5 | +use regex::Regex; |
5 | 6 | use unicode_width::UnicodeWidthStr; |
6 | 7 |
|
7 | 8 | use super::format::Alignment; |
@@ -76,30 +77,20 @@ pub fn print_align<T: Write + ?Sized>(out: &mut T, |
76 | 77 | } |
77 | 78 |
|
78 | 79 | /// Return the display width of a unicode string. |
79 | | -/// This functions takes ANSI-escaped color codes into account. |
| 80 | +/// This functions takes ANSI-escaped color codes and hyperlinks into account. |
80 | 81 | pub fn display_width(text: &str) -> usize { |
81 | 82 | let width = UnicodeWidthStr::width(text); |
82 | | - let mut state = 0; |
83 | 83 | let mut hidden = 0; |
84 | 84 |
|
85 | | - for c in text.chars() { |
86 | | - state = match (state, c) { |
87 | | - (0, '\u{1b}') => 1, |
88 | | - (1, '[') => 2, |
89 | | - (1, _) => 0, |
90 | | - (2, 'm') => 3, |
91 | | - _ => state, |
92 | | - }; |
93 | | - |
94 | | - // We don't count escape characters as hidden as |
95 | | - // UnicodeWidthStr::width already considers them. |
96 | | - if state > 1 { |
97 | | - hidden += 1; |
98 | | - } |
99 | | - |
100 | | - if state == 3 { |
101 | | - state = 0; |
102 | | - } |
| 85 | + lazy_static! { |
| 86 | + static ref COLOR_RE: Regex = Regex::new(r"\u{1b}(?P<colors>\[[^m]+?)m").unwrap(); |
| 87 | + static ref HYPERLINK_RE: Regex = Regex::new(r"\u{1b}]8;;(?P<url>[^\u{1b}]+?)\u{1b}\\(?P<text>[^\u{1b}]+?)\u{1b}]8;;\u{1b}\\").unwrap(); |
| 88 | + } |
| 89 | + for caps in COLOR_RE.captures_iter(text) { |
| 90 | + hidden += UnicodeWidthStr::width(&caps["colors"]) |
| 91 | + } |
| 92 | + for caps in HYPERLINK_RE.captures_iter(text) { |
| 93 | + hidden += 10 + UnicodeWidthStr::width(&caps["url"]) |
103 | 94 | } |
104 | 95 |
|
105 | 96 | width - hidden |
@@ -159,6 +150,17 @@ mod tests { |
159 | 150 | assert_eq!(out.as_string(), "foo"); |
160 | 151 | } |
161 | 152 |
|
| 153 | + #[test] |
| 154 | + fn ansi_escapes() { |
| 155 | + let mut out = StringWriter::new(); |
| 156 | + print_align(&mut out, Alignment::LEFT, "\u{1b}[31;40mred\u{1b}[0m", ' ', 10, false).unwrap(); |
| 157 | + assert_eq!(display_width(out.as_string()), 10); |
| 158 | + |
| 159 | + let mut out = StringWriter::new(); |
| 160 | + print_align(&mut out, Alignment::LEFT, "\u{1b}]8;;http://example.com\u{1b}\\example\u{1b}]8;;\u{1b}\\", ' ', 10, false).unwrap(); |
| 161 | + assert_eq!(display_width(out.as_string()), 10); |
| 162 | + } |
| 163 | + |
162 | 164 | #[test] |
163 | 165 | fn utf8_error() { |
164 | 166 | let mut out = StringWriter::new(); |
|
0 commit comments