Skip to content

Commit

Permalink
Support ANSI-escaped color codes and hyperlinks.
Browse files Browse the repository at this point in the history
  • Loading branch information
psftw committed May 28, 2019
1 parent fa70ca6 commit 5c17616
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 20 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,5 @@ term = "^0.5"
lazy_static = "1"
atty = "^0.2"
encode_unicode = "^0.3"
regex = "1"
csv = { version = "1", optional = true }
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,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.
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ extern crate atty;
#[macro_use]
extern crate lazy_static;
extern crate encode_unicode;
extern crate regex;

use std::io::{self, Write, Error};
use std::fmt;
Expand Down
42 changes: 22 additions & 20 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
use std::io::{Error, ErrorKind, Write};
use std::str;

use regex::Regex;
use unicode_width::UnicodeWidthStr;

use super::format::Alignment;
Expand Down Expand Up @@ -76,30 +77,20 @@ pub fn print_align<T: Write + ?Sized>(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<colors>\[[^m]+?)m").unwrap();
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();
}
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
Expand Down Expand Up @@ -159,6 +150,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();
Expand Down

0 comments on commit 5c17616

Please sign in to comment.