Skip to content

Commit b93870c

Browse files
mgeislerkbknapp
authored andcommitted
feat: use textwrap crate for wrapping help texts
The textwrap crate uses a simpler linear-time algorithm for wrapping the text. The current algorithm in wrap_help uses several O(n) calls to String::insert and String::remove, which makes it potentially quadratic in complexity. Comparing the 05_ripgrep benchmark at commits textwrap~2 and textwrap gives this result on my machine: name before ns/iter after ns/iter diff ns/iter diff % build_app_long 22,101 21,099 -1,002 -4.53% build_app_short 22,138 21,205 -933 -4.21% build_help_long 514,265 284,467 -229,798 -44.68% build_help_short 85,720 85,693 -27 -0.03% parse_clean 23,471 22,859 -612 -2.61% parse_complex 29,535 28,919 -616 -2.09% parse_lots 422,815 414,577 -8,238 -1.95% As part of this commit, the wrapping_newline_chars test was updated. The old algorithm had a subtle bug where it would break lines too early. That is, it wrapped the text like ARGS: <mode> x, max, maximum 20 characters, contains symbols. l, long Copy-friendly, 14 characters, contains symbols. m, med, medium Copy-friendly, 8 characters, contains symbols."; when it should really have wrapped it like ARGS: <mode> x, max, maximum 20 characters, contains symbols. l, long Copy-friendly, 14 characters, contains symbols. m, med, medium Copy-friendly, 8 characters, contains symbols."; Notice how the word "14" was incorrectly moved to the next line. There is clearly room for the word on the line with the "l, long" option since there is room for "contains" just above it. I'm not sure why this is, but the algorithm in textwrap handles this case correctly.
1 parent 1365304 commit b93870c

File tree

4 files changed

+11
-41
lines changed

4 files changed

+11
-41
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ bitflags = "0.8.0"
2020
vec_map = "0.8"
2121
unicode-width = "0.1.4"
2222
unicode-segmentation = "~1.1.0" # 1.2.0 requires Rust 1.13.0
23+
textwrap = "0.6.0"
2324
strsim = { version = "0.6.0", optional = true }
2425
ansi_term = { version = "0.9.0", optional = true }
2526
term_size = { version = "0.3.0", optional = true }

src/app/help.rs

+7-39
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use app::usage;
1717
use unicode_width::UnicodeWidthStr;
1818
#[cfg(feature = "wrap_help")]
1919
use term_size;
20-
use unicode_segmentation::UnicodeSegmentation;
20+
use textwrap;
2121
use vec_map::VecMap;
2222

2323
#[cfg(not(feature = "wrap_help"))]
@@ -955,45 +955,13 @@ impl<'a> Help<'a> {
955955
}
956956

957957
fn wrap_help(help: &mut String, longest_w: usize, avail_chars: usize) {
958-
debugln!("Help::wrap_help: longest_w={}, avail_chars={}",
959-
longest_w,
960-
avail_chars);
961-
debug!("Help::wrap_help: Enough space to wrap...");
958+
// Keep previous behavior of not wrapping at all if one of the
959+
// words would overflow the line.
962960
if longest_w < avail_chars {
963-
sdebugln!("Yes");
964-
let mut prev_space = 0;
965-
let mut j = 0;
966-
for (idx, g) in (&*help.clone()).grapheme_indices(true) {
967-
debugln!("Help::wrap_help:iter: idx={}, g={}", idx, g);
968-
if g == "\n" {
969-
debugln!("Help::wrap_help:iter: Newline found...");
970-
debugln!("Help::wrap_help:iter: Still space...{:?}",
971-
str_width(&help[j..idx]) < avail_chars);
972-
if str_width(&help[j..idx]) < avail_chars {
973-
j = idx;
974-
continue;
975-
}
976-
} else if g != " " {
977-
if idx != help.len() - 1 || str_width(&help[j..idx]) < avail_chars {
978-
continue;
979-
}
980-
debugln!("Help::wrap_help:iter: Reached the end of the line and we're over...");
981-
} else if str_width(&help[j..idx]) <= avail_chars {
982-
debugln!("Help::wrap_help:iter: Space found with room...");
983-
prev_space = idx;
984-
continue;
985-
}
986-
debugln!("Help::wrap_help:iter: Adding Newline...");
987-
j = prev_space;
988-
debugln!("Help::wrap_help:iter: prev_space={}, j={}", prev_space, j);
989-
debugln!("Help::wrap_help:iter: Removing...{}", j);
990-
debugln!("Help::wrap_help:iter: Char at {}: {:?}", j, &help[j..j + 1]);
991-
help.remove(j);
992-
help.insert(j, '\n');
993-
prev_space = idx;
994-
}
995-
} else {
996-
sdebugln!("No");
961+
*help = help.lines()
962+
.map(|line| textwrap::fill(line, avail_chars))
963+
.collect::<Vec<String>>()
964+
.join("\n");
997965
}
998966
}
999967

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,7 @@ extern crate bitflags;
541541
extern crate vec_map;
542542
#[cfg(feature = "wrap_help")]
543543
extern crate term_size;
544+
extern crate textwrap;
544545
extern crate unicode_segmentation;
545546
#[cfg(feature = "color")]
546547
extern crate atty;

tests/help.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,8 @@ FLAGS:
253253
ARGS:
254254
<mode> x, max, maximum 20 characters, contains
255255
symbols.
256-
l, long Copy-friendly,
257-
14 characters, contains symbols.
256+
l, long Copy-friendly, 14
257+
characters, contains symbols.
258258
m, med, medium Copy-friendly, 8
259259
characters, contains symbols.";
260260

0 commit comments

Comments
 (0)