From 7b32d227c82cacbeaed69d847eea69411a3bcaca Mon Sep 17 00:00:00 2001 From: Peter Salvatore Date: Fri, 21 Dec 2018 01:33:30 -0500 Subject: [PATCH] Support ANSI-escaped color codes and hyperlinks. --- Cargo.toml | 1 + README.md | 8 ++++++++ src/utils.rs | 42 ++++++++++++++++++++++-------------------- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b86067654a..4a6612ab89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,4 +40,5 @@ term = "0.6" lazy_static = "1" atty = "0.2" encode_unicode = "0.3" +regex = "1" csv = { version = "1", optional = true } diff --git a/README.md b/README.md index d1fa0eaa29..fc68e7e26b 100644 --- a/README.md +++ b/README.md @@ -241,6 +241,14 @@ Uppercase letters stand for **bright** counterparts of the above colors: * **B** : Bright Blue * ... and so on ... +## ANSI hyperlinks + +In most modern terminal emulators, it is possible to embed hyperlinks using ANSI escape codes. The following string field would display as a clickable link: + +```rust +"\u{1b}]8;;http://example.com\u{1b}\\example.com\u{1b}]8;;\u{1b}\\" +``` + ## Slicing Tables can be sliced into immutable borrowed subtables. diff --git a/src/utils.rs b/src/utils.rs index a3ce2ef8a3..f3130260c1 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,6 +3,7 @@ use std::fmt; use std::io::{Error, ErrorKind, Write}; use std::str; +use regex::Regex; use unicode_width::UnicodeWidthStr; use super::format::Alignment; @@ -77,30 +78,20 @@ pub fn print_align(out: &mut T, } /// Return the display width of a unicode string. -/// This functions takes ANSI-escaped color codes into account. +/// This functions takes ANSI-escaped color codes and hyperlinks into account. pub fn display_width(text: &str) -> usize { let width = UnicodeWidthStr::width(text); - let mut state = 0; let mut hidden = 0; - for c in text.chars() { - state = match (state, c) { - (0, '\u{1b}') => 1, - (1, '[') => 2, - (1, _) => 0, - (2, 'm') => 3, - _ => state, - }; - - // We don't count escape characters as hidden as - // UnicodeWidthStr::width already considers them. - if state > 1 { - hidden += 1; - } - - if state == 3 { - state = 0; - } + lazy_static! { + static ref COLOR_RE: Regex = Regex::new(r"\u{1b}(?P\[[^m]+?)m").unwrap(); + static ref HYPERLINK_RE: Regex = Regex::new(r"\u{1b}]8;;(?P[^\u{1b}]+?)\u{1b}\\(?P[^\u{1b}]+?)\u{1b}]8;;\u{1b}\\").unwrap(); + } + for caps in COLOR_RE.captures_iter(text) { + hidden += UnicodeWidthStr::width(&caps["colors"]) + } + for caps in HYPERLINK_RE.captures_iter(text) { + hidden += 10 + UnicodeWidthStr::width(&caps["url"]) } width - hidden @@ -197,6 +188,17 @@ mod tests { assert_eq!(out.as_string(), "foo"); } + #[test] + fn ansi_escapes() { + let mut out = StringWriter::new(); + print_align(&mut out, Alignment::LEFT, "\u{1b}[31;40mred\u{1b}[0m", ' ', 10, false).unwrap(); + assert_eq!(display_width(out.as_string()), 10); + + let mut out = StringWriter::new(); + print_align(&mut out, Alignment::LEFT, "\u{1b}]8;;http://example.com\u{1b}\\example\u{1b}]8;;\u{1b}\\", ' ', 10, false).unwrap(); + assert_eq!(display_width(out.as_string()), 10); + } + #[test] fn utf8_error() { let mut out = StringWriter::new();