fix(explain): align table correctly (#1482)

* fix(explain): align table correctly

* iterate over lines directly

* calculate desc_width with the actual space available

* custom unicode-aware textwrapping

* fix clippy error

* better width estimination

* explain +6

* move padding width into a constant
This commit is contained in:
David Knaack 2020-08-05 19:16:59 +02:00 committed by GitHub
parent 0be9ffc0a1
commit 8b0f589486
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 62 additions and 37 deletions

12
Cargo.lock generated
View File

@ -195,7 +195,7 @@ dependencies = [
"atty",
"bitflags",
"strsim",
"textwrap 0.11.0",
"textwrap",
"unicode-width",
"vec_map",
]
@ -1110,7 +1110,6 @@ dependencies = [
"sysinfo",
"tempfile",
"term_size",
"textwrap 0.12.1",
"toml",
"unicode-segmentation",
"unicode-width",
@ -1202,15 +1201,6 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "textwrap"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789"
dependencies = [
"unicode-width",
]
[[package]]
name = "thread_local"
version = "1.0.1"

View File

@ -56,7 +56,6 @@ os_info = "2.0.7"
urlencoding = "1.1.1"
open = "1.4.0"
unicode-width = "0.1.8"
textwrap = "0.12.1"
term_size = "0.3.2"
quick-xml = "0.18.1"

View File

@ -4,6 +4,7 @@ use rayon::prelude::*;
use std::collections::BTreeSet;
use std::fmt::{self, Debug, Write as FmtWrite};
use std::io::{self, Write};
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthChar;
use crate::configs::PROMPT_ORDER;
@ -104,50 +105,72 @@ pub fn explain(args: ArgMatches) {
let value = module.get_segments().join("");
ModuleInfo {
value: ansi_term::ANSIStrings(&module.ansi_strings()).to_string(),
value_len: value.chars().count() + count_wide_chars(&value),
value_len: better_width(value.as_str()),
desc: module.get_description().to_owned(),
}
})
.collect::<Vec<ModuleInfo>>();
let mut max_ansi_module_width = 0;
let mut max_module_width = 0;
let max_module_width = modules.iter().map(|i| i.value_len).max().unwrap_or(0);
for info in &modules {
max_ansi_module_width = std::cmp::max(
max_ansi_module_width,
info.value.chars().count() + count_wide_chars(&info.value),
);
max_module_width = std::cmp::max(max_module_width, info.value_len);
}
// In addition to the module width itself there are also 6 padding characters in each line.
// Overall a line looks like this: " {module name} - {description}".
const PADDING_WIDTH: usize = 6;
let desc_width = term_size::dimensions()
.map(|(w, _)| w)
.map(|width| width - std::cmp::min(width, max_ansi_module_width));
// Add padding length to module length to avoid text overflow. This line also assures desc_width >= 0.
.map(|width| width - std::cmp::min(width, max_module_width + PADDING_WIDTH));
println!("\n Here's a breakdown of your prompt:");
for info in modules {
let wide_chars = count_wide_chars(&info.value);
if let Some(desc_width) = desc_width {
let wrapped = textwrap::fill(&info.desc, desc_width);
let mut lines = wrapped.split('\n');
println!(
" {:width$} - {}",
// Custom Textwrapping!
let mut current_pos = 0;
let mut escaping = false;
// Print info
print!(
" {}{} - ",
info.value,
lines.next().unwrap(),
width = max_ansi_module_width - wide_chars
" ".repeat(max_module_width - info.value_len)
);
for g in info.desc.graphemes(true) {
// Handle ANSI escape sequnces
if g == "\x1B" {
escaping = true;
}
if escaping {
print!("{}", g);
escaping = !("a" <= g && "z" >= g || "A" <= g && "Z" >= g);
continue;
}
for line in lines {
println!("{}{}", " ".repeat(max_module_width + 6), line.trim());
// Handle normal wrapping
current_pos += grapheme_width(g);
// Wrap when hitting max width or newline
if g == "\n" || current_pos > desc_width {
// trim spaces on linebreak
if g == " " && desc_width > 1 {
continue;
}
print!("\n{}", " ".repeat(max_module_width + PADDING_WIDTH));
if g == "\n" {
current_pos = 0;
continue;
}
current_pos = 1;
}
print!("{}", g);
}
println!();
} else {
println!(
" {:width$} - {}",
" {}{} - {}",
info.value,
" ".repeat(max_module_width - info.value_len),
info.desc,
width = max_ansi_module_width - wide_chars
);
};
}
@ -266,6 +289,19 @@ fn should_add_implicit_custom_module(
.unwrap_or(false)
}
fn count_wide_chars(value: &str) -> usize {
value.chars().filter(|c| c.width().unwrap_or(0) > 1).count()
fn better_width(s: &str) -> usize {
s.graphemes(true).map(grapheme_width).sum()
}
// Assume that graphemes have width of the first character in the grapheme
fn grapheme_width(g: &str) -> usize {
g.chars().next().and_then(|i| i.width()).unwrap_or(0)
}
#[test]
fn test_grapheme_aware_better_width() {
// UnicodeWidthStr::width would return 8
assert_eq!(2, better_width("👩‍👩‍👦‍👦"));
assert_eq!(1, better_width(""));
assert_eq!(11, better_width("normal text"));
}