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

Unicode awareness for progress_chars #174

Merged
merged 4 commits into from
Jun 10, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ regex = { version = "1.3.1", default-features = false, features = ["std"] }
lazy_static = "1.0"
number_prefix = "0.3"
console = ">=0.9.1, <1.0.0"
unicode-segmentation = { version = "1.6.0", optional = true }
unicode-width = { version = "0.1.7", optional = true }
rayon = { version = "1.0", optional = true }

[dev-dependencies]
Expand All @@ -25,4 +27,7 @@ tokio-core = "0.1"

[features]
default = []
improved_unicode = ["unicode-segmentation", "unicode-width", "console/unicode-width"]

# Legacy alias for `rayon`
with_rayon = ["rayon"]
2 changes: 1 addition & 1 deletion examples/fastbar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ fn many_units_of_easy_work(n: u64, label: &str, draw_delta: Option<u64>) {
}

fn main() {
const N: u64 = (1 << 20);
const N: u64 = 1 << 20;

// Perform a long sequence of many simple computations monitored by a
// default progress bar.
Expand Down
2 changes: 1 addition & 1 deletion src/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ impl<S, T: Iterator<Item = S>> ProgressIterator for T {
}
}

#[cfg(feature = "with_rayon")]
#[cfg(feature = "rayon")]
pub mod rayon_support {
use super::*;
use rayon::iter::{
Expand Down
11 changes: 8 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,11 @@
//! methods to configure the number of elements in the iterator or change
//! the progress bar style. Indicatif also has optional support for parallel
//! iterators with [Rayon](https://github.com/rayon-rs/rayon). In your
//! `cargo.toml`, use the "with_rayon" feature:
//! `cargo.toml`, use the "rayon" feature:
//!
//! ```toml
//! [dependencies]
//! indicatif = {version = "*", features = ["with_rayon"]}
//! indicatif = {version = "*", features = ["rayon"]}
//! ```
//!
//! And then use it like this:
Expand Down Expand Up @@ -178,6 +178,11 @@
//! println!("The file is {} large", HumanBytes(file.size));
//! println!("The script took {}", HumanDuration(started.elapsed()));
//! ```
//!
//! # Feature Flags
//!
//! * `rayon`: adds rayon support
//! * `improved_unicode`: adds improved unicode support (graphemes, better width calculation)

mod format;
mod iter;
Expand All @@ -190,5 +195,5 @@ pub use crate::iter::{ProgressBarIter, ProgressIterator};
pub use crate::progress::{MultiProgress, ProgressBar, ProgressBarWrap, ProgressDrawTarget};
pub use crate::style::ProgressStyle;

#[cfg(feature = "with_rayon")]
#[cfg(feature = "rayon")]
pub use iter::rayon_support::{ParProgressBarIter, ParallelProgressIterator};
97 changes: 75 additions & 22 deletions src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,54 +8,105 @@ use crate::format::{BinaryBytes, DecimalBytes, FormattedDuration, HumanBytes, Hu
use crate::progress::ProgressState;
use crate::utils::{expand_template, pad_str};

#[cfg(feature = "improved_unicode")]
use unicode_segmentation::UnicodeSegmentation;

/// Controls the rendering style of progress bars.
#[derive(Clone, Debug)]
pub struct ProgressStyle {
tick_strings: Vec<String>,
progress_chars: Vec<char>,
tick_strings: Vec<Box<str>>,
progress_chars: Vec<Box<str>>,
template: Cow<'static, str>,
// how unicode-big each char in progress_chars is
char_width: usize,
}

#[cfg(feature = "improved_unicode")]
fn segment(s: &str) -> Vec<Box<str>> {
UnicodeSegmentation::graphemes(s, true)
.map(|s| s.into())
.collect()
}

#[cfg(not(feature = "improved_unicode"))]
fn segment(s: &str) -> Vec<Box<str>> {
s.chars().map(|x| x.to_string().into()).collect()
}

#[cfg(feature = "improved_unicode")]
fn measure(s: &str) -> usize {
unicode_width::UnicodeWidthStr::width(s)
}

#[cfg(not(feature = "improved_unicode"))]
fn measure(s: &str) -> usize {
s.chars().count()
}

/// finds the unicode-aware width of the passed grapheme cluters
/// panics on an empty parameter, or if the characters are not equal-width
fn width(c: &[Box<str>]) -> usize {
c.iter()
.map(|s| measure(s.as_ref()))
.fold(None, |acc, new| {
match acc {
None => return Some(new),
Some(old) => assert_eq!(old, new, "got passed un-equal width progress characters"),
}
acc
})
.unwrap()
}

impl ProgressStyle {
/// Returns the default progress bar style for bars.
pub fn default_bar() -> ProgressStyle {
let progress_chars = segment("█░");
let char_width = width(&progress_chars);
ProgressStyle {
tick_strings: "⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈ "
.chars()
.map(|c| c.to_string())
.map(|c| c.to_string().into())
.collect(),
progress_chars: "█░".chars().collect(),
progress_chars,
char_width,
template: Cow::Borrowed("{wide_bar} {pos}/{len}"),
}
}

/// Returns the default progress bar style for spinners.
pub fn default_spinner() -> ProgressStyle {
let progress_chars = segment("█░");
let char_width = width(&progress_chars);
ProgressStyle {
tick_strings: "⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈ "
.chars()
.map(|c| c.to_string())
.map(|c| c.to_string().into())
.collect(),
progress_chars: "█░".chars().collect(),
progress_chars,
char_width,
template: Cow::Borrowed("{spinner} {msg}"),
}
}

/// Sets the tick character sequence for spinners.
pub fn tick_chars(mut self, s: &str) -> ProgressStyle {
self.tick_strings = s.chars().map(|c| c.to_string()).collect();
self.tick_strings = s.chars().map(|c| c.to_string().into()).collect();
self
}

/// Sets the tick string sequence for spinners.
pub fn tick_strings(mut self, s: &[&str]) -> ProgressStyle {
self.tick_strings = s.iter().map(|s| s.to_string()).collect();
self.tick_strings = s.iter().map(|s| s.to_string().into()).collect();
self
}

/// Sets the three progress characters `(filled, current, to do)`.
/// Sets the progress characters `(filled, current, to do)`.
/// You can pass more then three for a more detailed display.
/// All passed grapheme clusters need to be of equal width.
pub fn progress_chars(mut self, s: &str) -> ProgressStyle {
self.progress_chars = s.chars().collect();
self.progress_chars = segment(s);
self.char_width = width(&self.progress_chars);
self
}

Expand Down Expand Up @@ -93,32 +144,34 @@ impl ProgressStyle {
width: usize,
alt_style: Option<&Style>,
) -> String {
// todo: this code could really use some comments
let width = width / state.style.char_width;
let pct = state.fraction();
let fill = pct * width as f32;
let head = if pct > 0.0 && (fill as usize) < width {
1
} else {
0
};
let fill = fill as usize;
let head = if pct > 0.0 && fill < width { 1 } else { 0 };

let pb: String = repeat(&state.style.progress_chars[0])
.take(fill)
.map(|b| b.as_ref())
.collect();

let pb = repeat(state.style.progress_chars[0])
.take(fill as usize)
.collect::<String>();
let cur = if head == 1 {
let n = state.style.progress_chars.len().saturating_sub(2);
let cur_char = if n == 0 {
1
} else {
n.saturating_sub((fill * n as f32) as usize % n)
n.saturating_sub((fill * n) % n)
};
state.style.progress_chars[cur_char].to_string()
} else {
"".into()
};
let bg = width.saturating_sub(fill as usize).saturating_sub(head);
let rest = repeat(state.style.progress_chars.last().unwrap())
let bg = width.saturating_sub(fill).saturating_sub(head);
let rest: String = repeat(state.style.progress_chars.last().unwrap())
.take(bg)
.collect::<String>();
.map(|b| b.as_ref())
.collect();
format!(
"{}{}{}",
pb,
Expand Down