Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v0.8.0 Fix wrapped filenames with hyperlinks #223

Merged
merged 16 commits into from Sep 26, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -35,6 +35,7 @@ papergrid = "0.5.1"
unicode-width = "0.1.9"
tabled_derive = { version = "0.4.0", optional = true }
ansi-str = { version = "0.3.0", optional = true }
vte-ansi-iterator = { git = "https://github.com/dandavison/vte-ansi-iterator.git", branch = "main" }

[dev-dependencies]
owo-colors = "3.4.0"
Expand Down
72 changes: 72 additions & 0 deletions examples/hyperlink_keep_words.rs
@@ -0,0 +1,72 @@
//! To run this example:
//! `cargo run --features=color --example hyperlink`

use std::iter::FromIterator;

use tabled::{object::Segment, Alignment, ModifyObject, Style, Table, Tabled, Width};

#[derive(Tabled)]
struct Distribution {
name: String,
is_hyperlink: bool,
}

fn format_osc8_hyperlink(url: &str, text: &str) -> String {
format!(
"{osc}8;;{url}{st}{text}{osc}8;;{st}",
url = url,
text = text,
osc = "\x1b]",
st = "\x1b\\"
)
}

fn main() {
// Use cfg macro to check that 'color' feature is enabled
assert!(
cfg!(feature = "color"),
"This example requires the 'color' feature"
);

let multicolored_debian = "\x1b[30mDebian\x1b[0m\
\x1b[31mDebian\x1b[0m\
\x1b[32mDebian\x1b[0m\
\x1b[33mDebian\x1b[0m\
\x1b[34mDebian\x1b[0m\
\x1b[35mDebian\x1b[0m\
\x1b[36mDebian\x1b[0m\
\x1b[37mDebian\x1b[0m\
\x1b[40mDebian\x1b[0m\
\x1b[41mDebian\x1b[0m\
\x1b[42mDebian\x1b[0m\
\x1b[43mDebian\x1b[0m\
\x1b[44mDebian\x1b[0m";

let data = [
Distribution {
name: format_osc8_hyperlink("https://www.debian.org/", "Debian"),
is_hyperlink: true,
},
Distribution {
name: "Debian".into(),
is_hyperlink: false,
},
Distribution {
name: format_osc8_hyperlink("https://www.debian.org/", multicolored_debian),
is_hyperlink: true,
},
Distribution {
name: "DebianDebianDebianDebianDebianDebianDebianDebianDebianDebianDebianDebianDebianDebian".into(),
is_hyperlink: false,
},
];

let table = Table::from_iter(&data).with(Style::ascii_rounded()).with(
Segment::all()
.modify()
.with(Width::wrap(30).keep_words())
.with(Alignment::left()),
);

println!("{}", table);
}
56 changes: 45 additions & 11 deletions src/width.rs
Expand Up @@ -37,6 +37,7 @@ use std::{borrow::Cow, collections::HashMap, marker::PhantomData};
use papergrid::{
count_borders_in_range, cut_str, string_width, string_width_multiline, Grid, Settings,
};
use vte_ansi_iterator::osc_partition;

use crate::{object::Entity, CellOption, TableOption};

Expand Down Expand Up @@ -1075,23 +1076,26 @@ where
pub(crate) fn wrap_text(text: &str, width: usize, keep_words: bool) -> String {
if width == 0 {
String::new()
} else if keep_words {
split_keeping_words(text, width, "\n")
} else {
split(text, width, "\n")
let (prefix, text, suffix) = osc_partition(text).unwrap_or(("", text, ""));
zhiburt marked this conversation as resolved.
Show resolved Hide resolved
if keep_words {
split_keeping_words(text, width, prefix, suffix, "\n")
} else {
split(text, width, prefix, suffix, "\n")
}
}
}

fn split(s: &str, width: usize, sep: &str) -> String {
fn split(s: &str, width: usize, prefix: &str, suffix: &str, sep: &str) -> String {
if width == 0 {
return String::new();
}

chunks(s, width).join(sep)
chunks(s, width, prefix, suffix).join(sep)
}

#[cfg(not(feature = "color"))]
fn chunks(s: &str, width: usize) -> Vec<String> {
fn chunks(s: &str, width: usize, prefix: &str, suffix: &str) -> Vec<String> {
const REPLACEMENT: char = '\u{FFFD}';

let mut buf = String::with_capacity(width);
Expand All @@ -1101,9 +1105,11 @@ fn chunks(s: &str, width: usize) -> Vec<String> {
let c_width = unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
if i + c_width > width {
let count_unknowns = width - i;
buf.push_str(suffix);
buf.extend(std::iter::repeat(REPLACEMENT).take(count_unknowns));
i += count_unknowns;
} else {
buf.push_str(prefix);
buf.push(c);
i += c_width;
}
Expand All @@ -1123,7 +1129,7 @@ fn chunks(s: &str, width: usize) -> Vec<String> {
}

#[cfg(feature = "color")]
fn chunks(s: &str, width: usize) -> Vec<String> {
fn chunks(s: &str, width: usize, prefix: &str, suffix: &str) -> Vec<String> {
use std::fmt::Write;

if width == 0 {
Expand All @@ -1139,6 +1145,7 @@ fn chunks(s: &str, width: usize) -> Vec<String> {
continue;
}

line.push_str(prefix);
zhiburt marked this conversation as resolved.
Show resolved Hide resolved
let _ = write!(&mut line, "{}", b.start());

let mut part = b.text();
Expand All @@ -1152,8 +1159,10 @@ fn chunks(s: &str, width: usize) -> Vec<String> {

if line_width == available_space {
let _ = write!(&mut line, "{}", b.end());
line.push_str(suffix);
list.push(line);
line = String::with_capacity(width);
line.push_str(prefix);
line_width = 0;
let _ = write!(&mut line, "{}", b.start());
}
Expand All @@ -1169,13 +1178,16 @@ fn chunks(s: &str, width: usize) -> Vec<String> {
line_width += unicode_width::UnicodeWidthStr::width(lhs);

const REPLACEMENT: char = '\u{FFFD}';
line.push_str(suffix);
line.extend(std::iter::repeat(REPLACEMENT).take(unknowns));
line_width += unknowns;

if line_width == width {
let _ = write!(&mut line, "{}", b.end());
line.push_str(suffix);
list.push(line);
line = String::with_capacity(width);
line.push_str(prefix);
line_width = 0;
let _ = write!(&mut line, "{}", b.start());
}
Expand All @@ -1187,14 +1199,15 @@ fn chunks(s: &str, width: usize) -> Vec<String> {
}

if line_width > 0 {
line.push_str(suffix);
list.push(line);
}

list
}

#[cfg(not(feature = "color"))]
fn split_keeping_words(s: &str, width: usize, sep: &str) -> String {
fn split_keeping_words(s: &str, width: usize, prefix: &str, suffix: &str, sep: &str) -> String {
const REPLACEMENT: char = '\u{FFFD}';

let mut lines = Vec::new();
Expand All @@ -1214,6 +1227,7 @@ fn split_keeping_words(s: &str, width: usize, sep: &str) -> String {
}

if is_first_word {
line.push_str(prefix);
is_first_word = false;
}

Expand All @@ -1229,15 +1243,17 @@ fn split_keeping_words(s: &str, width: usize, sep: &str) -> String {
if word_width <= width {
// the word can be fit to 'width' so we put it on new line

line.push_str(suffix);
line.extend(std::iter::repeat(' ').take(width - line_width));
lines.push(line);

line = String::with_capacity(width);
line_width = 0;

line.push_str(prefix);
line.push_str(word);
line_width += word_width;
is_first_word = true;
is_first_word = false;
} else {
// the word is too long any way so we split it

Expand All @@ -1251,6 +1267,7 @@ fn split_keeping_words(s: &str, width: usize, sep: &str) -> String {
line_width += unicode_width::UnicodeWidthStr::width(lhs) + unknowns;

line.push_str(lhs);
line.push_str(suffix);
line.extend(std::iter::repeat(REPLACEMENT).take(unknowns));

if line_width == width {
Expand All @@ -1264,6 +1281,7 @@ fn split_keeping_words(s: &str, width: usize, sep: &str) -> String {
}

if line_width > 0 {
line.push_str(suffix);
line.extend(std::iter::repeat(' ').take(width - line_width));
lines.push(line);
}
Expand All @@ -1272,7 +1290,7 @@ fn split_keeping_words(s: &str, width: usize, sep: &str) -> String {
}

#[cfg(feature = "color")]
fn split_keeping_words(s: &str, width: usize, sep: &str) -> String {
fn split_keeping_words(s: &str, width: usize, prefix: &str, suffix: &str, sep: &str) -> String {
use std::fmt::Write;

let mut lines = Vec::new();
Expand All @@ -1297,6 +1315,7 @@ fn split_keeping_words(s: &str, width: usize, sep: &str) -> String {
}
}
if is_first_word {
line.push_str(prefix);
is_first_word = false;
}

Expand All @@ -1313,11 +1332,13 @@ fn split_keeping_words(s: &str, width: usize, sep: &str) -> String {
// the word can be fit to 'width' so we put it on new line

let _ = write!(&mut line, "{}", b.end());
line.push_str(suffix);
line.extend(std::iter::repeat(' ').take(width - line_width));
lines.push(line);

line = String::with_capacity(width);

line.push_str(prefix);
let _ = write!(&mut line, "{}", b.start());
line.push_str(word);
line_width = word_width;
Expand All @@ -1335,6 +1356,7 @@ fn split_keeping_words(s: &str, width: usize, sep: &str) -> String {
line_width += unicode_width::UnicodeWidthStr::width(lhs) + unknowns;

let _ = write!(&mut line, "{}", lhs);
line.push_str(suffix);
const REPLACEMENT: char = '\u{FFFD}';
line.extend(std::iter::repeat(REPLACEMENT).take(unknowns));

Expand All @@ -1343,6 +1365,7 @@ fn split_keeping_words(s: &str, width: usize, sep: &str) -> String {

lines.push(line);
line = String::with_capacity(width);
line.push_str(prefix);
line_width = 0;
is_first_word = true;
let _ = write!(&mut line, "{}", b.start());
Expand Down Expand Up @@ -1376,10 +1399,21 @@ fn split_string_at(text: &str, at: usize) -> (&str, &str, (usize, usize)) {
#[cfg(feature = "color")]
#[cfg(test)]
mod tests {
use super::*;
use owo_colors::{colors::Yellow, OwoColorize};
use papergrid::cut_str;

fn split_keeping_words(s: &str, width: usize, sep: &str) -> String {
super::split_keeping_words(s, width, "", "", sep)
}

fn split(s: &str, width: usize, sep: &str) -> String {
super::split(s, width, "", "", sep)
}

fn chunks(s: &str, width: usize) -> Vec<String> {
super::chunks(s, width, "", "")
}

zhiburt marked this conversation as resolved.
Show resolved Hide resolved
#[test]
fn test_color_strip() {
let s = "Collored string"
Expand Down