fix(Help Message): fixes bug with wrapping in the middle of a unicode sequence

Closes #456
This commit is contained in:
Kevin K 2016-03-27 14:11:20 -04:00
parent 6933b8491c
commit 05365ddcc2
5 changed files with 50 additions and 13 deletions

View file

@ -19,13 +19,14 @@ ansi_term = { version = "~0.7.2", optional = true }
strsim = { version = "~0.4.0", optional = true } strsim = { version = "~0.4.0", optional = true }
yaml-rust = { version = "~0.3", optional = true } yaml-rust = { version = "~0.3", optional = true }
clippy = { version = "=0.0.55", optional = true } clippy = { version = "=0.0.55", optional = true }
unicode-width = { version = "~0.1.3", optional = true }
[features] [features]
default = ["suggestions", "color", "wrap_help"] default = ["suggestions", "color", "wrap_help"]
suggestions = ["strsim"] suggestions = ["strsim"]
color = ["ansi_term"] color = ["ansi_term"]
yaml = ["yaml-rust"] yaml = ["yaml-rust"]
wrap_help = ["libc"] wrap_help = ["libc", "unicode-width"]
lints = ["clippy", "nightly"] lints = ["clippy", "nightly"]
nightly = [] # for building with nightly and unstable features nightly = [] # for building with nightly and unstable features
unstable = [] # for building with unstable features on stable Rust unstable = [] # for building with unstable features on stable Rust

View file

@ -457,7 +457,7 @@ The following is a list of optional `clap` features:
* **"suggestions"**: Turns on the `Did you mean '--myoption' ?` feature for when users make typos. (builds dependency `strsim`) * **"suggestions"**: Turns on the `Did you mean '--myoption' ?` feature for when users make typos. (builds dependency `strsim`)
* **"color"**: Turns on colored error messages. This feature only works on non-Windows OSs. (builds dependency `ansi-term`) * **"color"**: Turns on colored error messages. This feature only works on non-Windows OSs. (builds dependency `ansi-term`)
* **"wrap_help"**: Automatically detects terminal width and wraps long help text lines with proper indentation alignment (builds dependency `libc`) * **"wrap_help"**: Automatically detects terminal width and wraps long help text lines with proper indentation alignment (builds dependency `libc` and 'unicode-width')
* **"lints"**: This is **not** included by default and should only be used while developing to run basic lints against changes. This can only be used on Rust nightly. (builds dependency `clippy`) * **"lints"**: This is **not** included by default and should only be used while developing to run basic lints against changes. This can only be used on Rust nightly. (builds dependency `clippy`)
* **"debug"**: This is **not** included by default and should only be used while developing to display debugging information. * **"debug"**: This is **not** included by default and should only be used while developing to display debugging information.
* **"yaml"**: This is **not** included by default. Enables building CLIs from YAML documents. (builds dependency `yaml-rust`) * **"yaml"**: This is **not** included by default. Enables building CLIs from YAML documents. (builds dependency `yaml-rust`)

View file

@ -1,9 +1,23 @@
use std::io; use std::io;
use std::fmt::Display; use std::fmt::Display;
#[cfg(all(feature = "wrap_help", not(target_os = "windows")))]
use unicode_width::UnicodeWidthStr;
use args::AnyArg; use args::AnyArg;
use args::settings::ArgSettings; use args::settings::ArgSettings;
use term; use term;
use strext::_StrExt;
#[cfg(any(not(feature = "wrap_help"), target_os = "windows"))]
fn str_width(s: &str) -> usize {
s.len()
}
#[cfg(all(feature = "wrap_help", not(target_os = "windows")))]
fn str_width(s: &str) -> usize {
UnicodeWidthStr::width(s)
}
const TAB: &'static str = " "; const TAB: &'static str = " ";
@ -139,7 +153,7 @@ impl<'a, 'n, 'e, A> HelpWriter<'a, A> where A: AnyArg<'n, 'e> + Display {
// determine if our help fits or needs to wrap // determine if our help fits or needs to wrap
let width = self.term_w.unwrap_or(0); let width = self.term_w.unwrap_or(0);
debugln!("Term width...{}", width); debugln!("Term width...{}", width);
let too_long = self.term_w.is_some() && (spcs + h.len() + spec_vals.len() >= width); let too_long = self.term_w.is_some() && (spcs + str_width(h) + str_width(&*spec_vals) >= width);
debugln!("Too long...{:?}", too_long); debugln!("Too long...{:?}", too_long);
// Is help on next line, if so newline + 2x tab // Is help on next line, if so newline + 2x tab
@ -153,13 +167,13 @@ impl<'a, 'n, 'e, A> HelpWriter<'a, A> where A: AnyArg<'n, 'e> + Display {
help.push_str(h); help.push_str(h);
help.push_str(&*spec_vals); help.push_str(&*spec_vals);
debugln!("help: {}", help); debugln!("help: {}", help);
debugln!("help len: {}", help.len()); debugln!("help width: {}", str_width(help));
// Determine how many newlines we need to insert // Determine how many newlines we need to insert
let avail_chars = width - spcs; let avail_chars = width - spcs;
debugln!("Usable space: {}", avail_chars); debugln!("Usable space: {}", avail_chars);
let longest_w = { let longest_w = {
let mut lw = 0; let mut lw = 0;
for l in help.split(' ').map(|s| s.len()) { for l in help.split(' ').map(|s| str_width(s)) {
if l > lw { if l > lw {
lw = l; lw = l;
} }
@ -167,7 +181,7 @@ impl<'a, 'n, 'e, A> HelpWriter<'a, A> where A: AnyArg<'n, 'e> + Display {
lw lw
}; };
debugln!("Longest word...{}", longest_w); debugln!("Longest word...{}", longest_w);
debug!("Enough space..."); debug!("Enough space to wrap...");
if longest_w < avail_chars { if longest_w < avail_chars {
sdebugln!("Yes"); sdebugln!("Yes");
let mut indices = vec![]; let mut indices = vec![];
@ -182,13 +196,13 @@ impl<'a, 'n, 'e, A> HelpWriter<'a, A> where A: AnyArg<'n, 'e> + Display {
debugln!("Adding idx: {}", idx); debugln!("Adding idx: {}", idx);
debugln!("At {}: {:?}", idx, help.chars().nth(idx)); debugln!("At {}: {:?}", idx, help.chars().nth(idx));
indices.push(idx); indices.push(idx);
if &help[idx..].len() <= &avail_chars { if str_width(&help[idx..]) <= avail_chars {
break; break;
} }
} }
for (i, idx) in indices.iter().enumerate() { for (i, idx) in indices.iter().enumerate() {
debugln!("iter;i={},idx={}", i, idx); debugln!("iter;i={},idx={}", i, idx);
let j = idx+(2*i); let j = idx + (2 * i);
debugln!("removing: {}", j); debugln!("removing: {}", j);
debugln!("at {}: {:?}", j, help.chars().nth(j)); debugln!("at {}: {:?}", j, help.chars().nth(j));
help.remove(j); help.remove(j);
@ -252,15 +266,20 @@ impl<'a, 'n, 'e, A> HelpWriter<'a, A> where A: AnyArg<'n, 'e> + Display {
} }
} }
fn find_idx_of_space(full: &str, start: usize) -> usize { fn find_idx_of_space(full: &str, mut start: usize) -> usize {
debugln!("fn=find_idx_of_space;"); debugln!("fn=find_idx_of_space;");
let haystack = &full[..start]; let haystack = if full._is_char_boundary(start) {
&full[..start]
} else {
while !full._is_char_boundary(start) { start -= 1; }
&full[..start]
};
debugln!("haystack: {}", haystack); debugln!("haystack: {}", haystack);
for (i, c) in haystack.chars().rev().enumerate() { for (i, c) in haystack.chars().rev().enumerate() {
debugln!("iter;c={},i={}", c, i); debugln!("iter;c={},i={}", c, i);
if c == ' ' { if c == ' ' {
debugln!("Found space returning start-i...{}", start - (i+1)); debugln!("Found space returning start-i...{}", start - (i + 1));
return start - (i+1); return start - (i + 1);
} }
} }
0 0

View file

@ -406,8 +406,10 @@ extern crate strsim;
extern crate ansi_term; extern crate ansi_term;
#[cfg(feature = "yaml")] #[cfg(feature = "yaml")]
extern crate yaml_rust; extern crate yaml_rust;
#[cfg(feature = "wrap_help")] #[cfg(all(feature = "wrap_help", not(target_os = "windows")))]
extern crate libc; extern crate libc;
#[cfg(all(feature = "wrap_help", not(target_os = "windows")))]
extern crate unicode_width;
#[macro_use] #[macro_use]
extern crate bitflags; extern crate bitflags;
extern crate vec_map; extern crate vec_map;
@ -429,6 +431,7 @@ mod suggestions;
mod errors; mod errors;
mod osstringext; mod osstringext;
mod term; mod term;
mod strext;
const INTERNAL_ERROR_MSG: &'static str = "Fatal internal error. Please consider filing a bug \ const INTERNAL_ERROR_MSG: &'static str = "Fatal internal error. Please consider filing a bug \
report at https://github.com/kbknapp/clap-rs/issues"; report at https://github.com/kbknapp/clap-rs/issues";

14
src/strext.rs Normal file
View file

@ -0,0 +1,14 @@
pub trait _StrExt {
fn _is_char_boundary(&self, index: usize) -> bool;
}
impl _StrExt for str {
#[inline]
fn _is_char_boundary(&self, index: usize) -> bool {
if index == self.len() { return true; }
match self.as_bytes().get(index) {
None => false,
Some(&b) => b < 128 || b >= 192,
}
}
}