mirror of
https://github.com/clap-rs/clap
synced 2024-12-13 22:32:33 +00:00
feat: remove direct unicode-width dependency
This removes the direct dependency on unicode-width and delegates the complexity of computing the displayed width of text to the Textwrap crate. The `display_width` function handles characters like “æøå” (Danish), “äöü” (German), and “😂✨😍” (emojis) – even if the unicode-width Cargo feature is disabled. This is an improvement of the former `str_width` function which would over-estimate the width of emojis and non-ASCII characters (since they are several bytes wide).
This commit is contained in:
parent
80cbc1e695
commit
df7a8c1282
2 changed files with 35 additions and 22 deletions
|
@ -67,8 +67,7 @@ maintenance = {status = "actively-developed"}
|
|||
[dependencies]
|
||||
clap_derive = { path = "./clap_derive", version = "3.0.0-beta.2", optional = true }
|
||||
bitflags = "1.2"
|
||||
textwrap = { version = "0.13", default-features = false, features = [] }
|
||||
unicode-width = { version = "0.1", optional = true }
|
||||
textwrap = { version = "0.13.3", default-features = false, features = [] }
|
||||
indexmap = "1.0"
|
||||
os_str_bytes = { version = "2.4", features = ["raw"] }
|
||||
vec_map = "0.8"
|
||||
|
@ -92,7 +91,7 @@ std = [] # support for no_std in a backwards-compatible way
|
|||
suggestions = ["strsim"]
|
||||
color = ["atty", "termcolor"]
|
||||
wrap_help = ["terminal_size", "textwrap/terminal_size"]
|
||||
unicode_help= ["unicode-width", "textwrap/unicode-width"] # Enable if any unicode in help message
|
||||
unicode_help= ["textwrap/unicode-width"] # Enable if any unicode in help message
|
||||
derive = ["clap_derive", "lazy_static"]
|
||||
yaml = ["yaml-rust"]
|
||||
cargo = ["lazy_static"] # Disable if you're not using Cargo, enables Cargo-env-var-dependent macros
|
||||
|
|
|
@ -18,6 +18,7 @@ use crate::{
|
|||
|
||||
// Third party
|
||||
use indexmap::IndexSet;
|
||||
use textwrap::core::display_width;
|
||||
|
||||
pub(crate) fn dimensions() -> Option<(usize, usize)> {
|
||||
#[cfg(not(feature = "wrap_help"))]
|
||||
|
@ -27,14 +28,6 @@ pub(crate) fn dimensions() -> Option<(usize, usize)> {
|
|||
terminal_size::terminal_size().map(|(w, h)| (w.0.into(), h.0.into()))
|
||||
}
|
||||
|
||||
fn str_width(s: &str) -> usize {
|
||||
#[cfg(not(feature = "unicode_help"))]
|
||||
return s.len();
|
||||
|
||||
#[cfg(feature = "unicode_help")]
|
||||
unicode_width::UnicodeWidthStr::width(s)
|
||||
}
|
||||
|
||||
const TAB: &str = " ";
|
||||
|
||||
pub(crate) enum HelpWriter<'writer> {
|
||||
|
@ -182,7 +175,7 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
.filter(|arg| should_show_arg(self.use_long, *arg))
|
||||
{
|
||||
if arg.longest_filter() {
|
||||
longest = longest.max(str_width(arg.to_string().as_str()));
|
||||
longest = longest.max(display_width(arg.to_string().as_str()));
|
||||
}
|
||||
arg_v.push(arg)
|
||||
}
|
||||
|
@ -212,7 +205,7 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
}) {
|
||||
if arg.longest_filter() {
|
||||
debug!("Help::write_args: Current Longest...{}", longest);
|
||||
longest = longest.max(str_width(arg.to_string().as_str()));
|
||||
longest = longest.max(display_width(arg.to_string().as_str()));
|
||||
debug!("Help::write_args: New Longest...{}", longest);
|
||||
}
|
||||
let btm = ord_m.entry(arg.disp_ord).or_insert(BTreeMap::new());
|
||||
|
@ -370,7 +363,7 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
debug!("Yes");
|
||||
debug!("Help::val: nlh...{:?}", next_line_help);
|
||||
if !next_line_help {
|
||||
let self_len = str_width(arg.to_string().as_str());
|
||||
let self_len = display_width(arg.to_string().as_str());
|
||||
// subtract ourself
|
||||
let mut spcs = longest - self_len;
|
||||
// Since we're writing spaces from the tab point we first need to know if we
|
||||
|
@ -387,7 +380,7 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
}
|
||||
} else if !next_line_help {
|
||||
debug!("No, and not next_line");
|
||||
self.spaces(longest + 4 - str_width(&arg.to_string()))?;
|
||||
self.spaces(longest + 4 - display_width(&arg.to_string()))?;
|
||||
} else {
|
||||
debug!("No");
|
||||
}
|
||||
|
@ -447,7 +440,7 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
longest + 12
|
||||
};
|
||||
|
||||
let too_long = spaces + str_width(about) + str_width(spec_vals) >= self.term_w;
|
||||
let too_long = spaces + display_width(about) + display_width(spec_vals) >= self.term_w;
|
||||
|
||||
// Is help on next line, if so then indent
|
||||
if next_line_help {
|
||||
|
@ -458,7 +451,7 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
if too_long && spaces <= self.term_w {
|
||||
debug!("Yes");
|
||||
debug!("Help::help: help...{}", help);
|
||||
debug!("Help::help: help width...{}", str_width(&help));
|
||||
debug!("Help::help: help width...{}", display_width(&help));
|
||||
// Determine how many newlines we need to insert
|
||||
let avail_chars = self.term_w - spaces;
|
||||
debug!("Help::help: Usable space...{}", avail_chars);
|
||||
|
@ -522,7 +515,7 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
} else {
|
||||
// force_next_line
|
||||
let h = arg.about.unwrap_or("");
|
||||
let h_w = str_width(h) + str_width(spec_vals);
|
||||
let h_w = display_width(h) + display_width(spec_vals);
|
||||
let taken = longest + 12;
|
||||
self.term_w >= taken
|
||||
&& (taken as f32 / self.term_w as f32) > 0.40
|
||||
|
@ -726,7 +719,7 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
} else {
|
||||
// force_next_line
|
||||
let h = app.about.unwrap_or("");
|
||||
let h_w = str_width(h) + str_width(spec_vals);
|
||||
let h_w = display_width(h) + display_width(spec_vals);
|
||||
let taken = longest + 12;
|
||||
self.term_w >= taken
|
||||
&& (taken as f32 / self.term_w as f32) > 0.40
|
||||
|
@ -739,7 +732,7 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
self.none(TAB)?;
|
||||
self.good(sc_str)?;
|
||||
if !next_line_help {
|
||||
let width = str_width(sc_str);
|
||||
let width = display_width(sc_str);
|
||||
self.spaces(width.max(longest + 4) - width)?;
|
||||
}
|
||||
Ok(())
|
||||
|
@ -895,7 +888,7 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
.map_or(String::new(), |c| format!("--{}, ", c)),
|
||||
);
|
||||
sc_str.push_str(&subcommand.name);
|
||||
longest = longest.max(str_width(&sc_str));
|
||||
longest = longest.max(display_width(&sc_str));
|
||||
btm.insert(sc_str, subcommand.clone());
|
||||
}
|
||||
|
||||
|
@ -1073,11 +1066,32 @@ fn text_wrapper(help: &str, width: usize) -> String {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::text_wrapper;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn wrap_help_last_word() {
|
||||
let help = String::from("foo bar baz");
|
||||
assert_eq!(text_wrapper(&help, 5), "foo\nbar\nbaz");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_width_handles_non_ascii() {
|
||||
// Popular Danish tounge-twister, the name of a fruit dessert.
|
||||
let text = "rødgrød med fløde";
|
||||
assert_eq!(display_width(text), 17);
|
||||
// Note that the string width is smaller than the string
|
||||
// length. This is due to the precomposed non-ASCII letters:
|
||||
assert_eq!(text.len(), 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_width_handles_emojis() {
|
||||
let text = "😂";
|
||||
// There is a single `char`...
|
||||
assert_eq!(text.chars().count(), 1);
|
||||
// but it is double-width:
|
||||
assert_eq!(display_width(text), 2);
|
||||
// This is much less than the byte length:
|
||||
assert_eq!(text.len(), 4);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue