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

Expand tabs => configurable number of spaces to fix #150 #423

Merged
merged 3 commits into from Jul 6, 2022
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
48 changes: 38 additions & 10 deletions src/progress_bar.rs
Expand Up @@ -3,13 +3,13 @@ use std::borrow::Cow;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Condvar, Mutex, MutexGuard, Weak};
use std::time::{Duration, Instant};
use std::{fmt, io, mem, thread};
use std::{fmt, io, thread};

#[cfg(test)]
use once_cell::sync::Lazy;

use crate::draw_target::ProgressDrawTarget;
use crate::state::{AtomicPosition, BarState, ProgressFinish, Reset};
use crate::state::{AtomicPosition, BarState, ProgressFinish, Reset, TabExpandedString};
use crate::style::ProgressStyle;
use crate::{ProgressBarIter, ProgressIterator, ProgressState};

Expand Down Expand Up @@ -69,15 +69,25 @@ impl ProgressBar {
self
}

/// A convenience builder-like function for a progress bar with a given tab width
pub fn with_tab_width(self, tab_width: usize) -> ProgressBar {
self.state().set_tab_width(tab_width);
self
}

/// A convenience builder-like function for a progress bar with a given prefix
pub fn with_prefix(self, prefix: impl Into<Cow<'static, str>>) -> ProgressBar {
self.state().style.prefix = prefix.into();
let mut state = self.state();
state.state.prefix = TabExpandedString::new(prefix.into(), state.tab_width);
drop(state);
self
}

/// A convenience builder-like function for a progress bar with a given message
pub fn with_message(self, message: impl Into<Cow<'static, str>>) -> ProgressBar {
self.state().style.message = message.into();
let mut state = self.state();
state.state.message = TabExpandedString::new(message.into(), state.tab_width);
drop(state);
self
}

Expand Down Expand Up @@ -121,11 +131,15 @@ impl ProgressBar {
/// Overrides the stored style
///
/// This does not redraw the bar. Call [`ProgressBar::tick()`] to force it.
pub fn set_style(&self, mut style: ProgressStyle) {
pub fn set_style(&self, style: ProgressStyle) {
self.state().set_style(style);
}

/// Sets the tab width (default: 8). All tabs will be expanded to this many spaces.
pub fn set_tab_width(&mut self, tab_width: usize) {
let mut state = self.state();
mem::swap(&mut state.style.message, &mut style.message);
mem::swap(&mut state.style.prefix, &mut style.prefix);
state.style = style;
state.set_tab_width(tab_width);
state.draw(true, Instant::now()).unwrap();
}

/// Spawns a background thread to tick the progress bar
Expand Down Expand Up @@ -246,15 +260,19 @@ impl ProgressBar {
/// For the prefix to be visible, the `{prefix}` placeholder must be present in the template
/// (see [`ProgressStyle`]).
pub fn set_prefix(&self, prefix: impl Into<Cow<'static, str>>) {
self.state().set_prefix(Instant::now(), prefix.into());
let mut state = self.state();
state.state.prefix = TabExpandedString::new(prefix.into(), state.tab_width);
state.update_estimate_and_draw(Instant::now());
}

/// Sets the current message of the progress bar
///
/// For the message to be visible, the `{msg}` placeholder must be present in the template (see
/// [`ProgressStyle`]).
pub fn set_message(&self, msg: impl Into<Cow<'static, str>>) {
self.state().set_message(Instant::now(), msg.into())
let mut state = self.state();
state.state.message = TabExpandedString::new(msg.into(), state.tab_width);
state.update_estimate_and_draw(Instant::now());
}

/// Creates a new weak reference to this `ProgressBar`
Expand Down Expand Up @@ -517,6 +535,16 @@ impl ProgressBar {
self.state().draw_target.remote().map(|(_, idx)| idx)
}

/// Current message
chris-laplante marked this conversation as resolved.
Show resolved Hide resolved
pub fn message(&self) -> String {
self.state().state.message.expanded().to_string()
}

/// Current prefix
pub fn prefix(&self) -> String {
self.state().state.prefix.expanded().to_string()
}

#[inline]
pub(crate) fn state(&self) -> MutexGuard<'_, BarState> {
self.state.lock().unwrap()
Expand Down
79 changes: 70 additions & 9 deletions src/state.rs
Expand Up @@ -12,6 +12,7 @@ pub(crate) struct BarState {
pub(crate) on_finish: ProgressFinish,
pub(crate) style: ProgressStyle,
pub(crate) state: ProgressState,
pub(crate) tab_width: usize,
}

impl BarState {
Expand All @@ -25,6 +26,7 @@ impl BarState {
on_finish: ProgressFinish::default(),
style: ProgressStyle::default_bar(),
state: ProgressState::new(len, pos),
tab_width: DEFAULT_TAB_WIDTH,
}
}

Expand All @@ -42,7 +44,7 @@ impl BarState {
if let Some(len) = self.state.len {
self.state.pos.set(len);
}
self.style.message = msg;
self.state.message = TabExpandedString::new(msg, self.tab_width);
}
ProgressFinish::AndClear => {
if let Some(len) = self.state.len {
Expand All @@ -51,7 +53,9 @@ impl BarState {
self.state.status = Status::DoneHidden;
}
ProgressFinish::Abandon => {}
ProgressFinish::AbandonWithMessage(msg) => self.style.message = msg,
ProgressFinish::AbandonWithMessage(msg) => {
self.state.message = TabExpandedString::new(msg, self.tab_width)
}
}

// There's no need to update the estimate here; once the `status` is no longer
Expand Down Expand Up @@ -92,22 +96,24 @@ impl BarState {
self.update_estimate_and_draw(now);
}

pub(crate) fn set_message(&mut self, now: Instant, msg: Cow<'static, str>) {
self.style.message = msg;
self.update_estimate_and_draw(now);
pub(crate) fn set_tab_width(&mut self, tab_width: usize) {
self.tab_width = tab_width;
self.state.message.set_tab_width(tab_width);
self.state.prefix.set_tab_width(tab_width);
self.style.set_tab_width(tab_width);
}

pub(crate) fn set_prefix(&mut self, now: Instant, prefix: Cow<'static, str>) {
self.style.prefix = prefix;
self.update_estimate_and_draw(now);
pub(crate) fn set_style(&mut self, style: ProgressStyle) {
self.style = style;
self.style.set_tab_width(self.tab_width);
}

pub(crate) fn tick(&mut self, now: Instant) {
self.state.tick = self.state.tick.saturating_add(1);
self.update_estimate_and_draw(now);
}

fn update_estimate_and_draw(&mut self, now: Instant) {
pub(crate) fn update_estimate_and_draw(&mut self, now: Instant) {
let pos = self.state.pos.pos.load(Ordering::Relaxed);
self.state.est.record(pos, now);
let _ = self.draw(false, now);
Expand Down Expand Up @@ -190,6 +196,8 @@ pub struct ProgressState {
pub(crate) started: Instant,
status: Status,
est: Estimator,
pub(crate) message: TabExpandedString,
pub(crate) prefix: TabExpandedString,
}

impl ProgressState {
Expand All @@ -201,6 +209,8 @@ impl ProgressState {
status: Status::InProgress,
started: Instant::now(),
est: Estimator::new(Instant::now()),
message: TabExpandedString::NoTabs("".into()),
prefix: TabExpandedString::NoTabs("".into()),
}
}

Expand Down Expand Up @@ -285,6 +295,55 @@ impl ProgressState {
}
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub(crate) enum TabExpandedString {
NoTabs(Cow<'static, str>),
WithTabs {
original: Cow<'static, str>,
expanded: String,
tab_width: usize,
},
}

impl TabExpandedString {
pub(crate) fn new(s: Cow<'static, str>, tab_width: usize) -> Self {
let expanded = s.replace('\t', &" ".repeat(tab_width));
if s == expanded {
Self::NoTabs(s)
} else {
Self::WithTabs {
original: s,
expanded,
tab_width,
}
}
}

pub(crate) fn expanded(&self) -> &str {
match &self {
Self::NoTabs(s) => {
debug_assert!(!s.contains('\t'));
s
}
Self::WithTabs { expanded, .. } => expanded,
}
}

pub(crate) fn set_tab_width(&mut self, new_tab_width: usize) {
if let TabExpandedString::WithTabs {
original,
expanded,
tab_width,
} = self
{
if *tab_width != new_tab_width {
*tab_width = new_tab_width;
*expanded = original.replace('\t', &" ".repeat(new_tab_width));
}
}
}
}

/// Estimate the number of seconds per step
///
/// Ring buffer with constant capacity. Used by `ProgressBar`s to display `{eta}`,
Expand Down Expand Up @@ -482,6 +541,8 @@ pub(crate) enum Status {
DoneHidden,
}

pub(crate) const DEFAULT_TAB_WIDTH: usize = 8;

#[cfg(test)]
mod tests {
use super::*;
Expand Down