Skip to content

Commit 4cbdcbf

Browse files
Expand tabs => configurable number of spaces to fix #150 (#423)
* move message/prefix into the state It makes more sense here anyway, plus it gets rid of the mem::swap stuff * Implement customizable tab expansion (#150) * add render tests for tab handling
1 parent 88d87d4 commit 4cbdcbf

File tree

4 files changed

+223
-51
lines changed

4 files changed

+223
-51
lines changed

src/progress_bar.rs

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ use std::borrow::Cow;
33
use std::sync::atomic::{AtomicBool, Ordering};
44
use std::sync::{Arc, Condvar, Mutex, MutexGuard, Weak};
55
use std::time::{Duration, Instant};
6-
use std::{fmt, io, mem, thread};
6+
use std::{fmt, io, thread};
77

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

1111
use crate::draw_target::ProgressDrawTarget;
12-
use crate::state::{AtomicPosition, BarState, ProgressFinish, Reset};
12+
use crate::state::{AtomicPosition, BarState, ProgressFinish, Reset, TabExpandedString};
1313
use crate::style::ProgressStyle;
1414
use crate::{ProgressBarIter, ProgressIterator, ProgressState};
1515

@@ -69,15 +69,25 @@ impl ProgressBar {
6969
self
7070
}
7171

72+
/// A convenience builder-like function for a progress bar with a given tab width
73+
pub fn with_tab_width(self, tab_width: usize) -> ProgressBar {
74+
self.state().set_tab_width(tab_width);
75+
self
76+
}
77+
7278
/// A convenience builder-like function for a progress bar with a given prefix
7379
pub fn with_prefix(self, prefix: impl Into<Cow<'static, str>>) -> ProgressBar {
74-
self.state().style.prefix = prefix.into();
80+
let mut state = self.state();
81+
state.state.prefix = TabExpandedString::new(prefix.into(), state.tab_width);
82+
drop(state);
7583
self
7684
}
7785

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

@@ -121,11 +131,15 @@ impl ProgressBar {
121131
/// Overrides the stored style
122132
///
123133
/// This does not redraw the bar. Call [`ProgressBar::tick()`] to force it.
124-
pub fn set_style(&self, mut style: ProgressStyle) {
134+
pub fn set_style(&self, style: ProgressStyle) {
135+
self.state().set_style(style);
136+
}
137+
138+
/// Sets the tab width (default: 8). All tabs will be expanded to this many spaces.
139+
pub fn set_tab_width(&mut self, tab_width: usize) {
125140
let mut state = self.state();
126-
mem::swap(&mut state.style.message, &mut style.message);
127-
mem::swap(&mut state.style.prefix, &mut style.prefix);
128-
state.style = style;
141+
state.set_tab_width(tab_width);
142+
state.draw(true, Instant::now()).unwrap();
129143
}
130144

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

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

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

538+
/// Current message
539+
pub fn message(&self) -> String {
540+
self.state().state.message.expanded().to_string()
541+
}
542+
543+
/// Current prefix
544+
pub fn prefix(&self) -> String {
545+
self.state().state.prefix.expanded().to_string()
546+
}
547+
520548
#[inline]
521549
pub(crate) fn state(&self) -> MutexGuard<'_, BarState> {
522550
self.state.lock().unwrap()

src/state.rs

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub(crate) struct BarState {
1212
pub(crate) on_finish: ProgressFinish,
1313
pub(crate) style: ProgressStyle,
1414
pub(crate) state: ProgressState,
15+
pub(crate) tab_width: usize,
1516
}
1617

1718
impl BarState {
@@ -25,6 +26,7 @@ impl BarState {
2526
on_finish: ProgressFinish::default(),
2627
style: ProgressStyle::default_bar(),
2728
state: ProgressState::new(len, pos),
29+
tab_width: DEFAULT_TAB_WIDTH,
2830
}
2931
}
3032

@@ -42,7 +44,7 @@ impl BarState {
4244
if let Some(len) = self.state.len {
4345
self.state.pos.set(len);
4446
}
45-
self.style.message = msg;
47+
self.state.message = TabExpandedString::new(msg, self.tab_width);
4648
}
4749
ProgressFinish::AndClear => {
4850
if let Some(len) = self.state.len {
@@ -51,7 +53,9 @@ impl BarState {
5153
self.state.status = Status::DoneHidden;
5254
}
5355
ProgressFinish::Abandon => {}
54-
ProgressFinish::AbandonWithMessage(msg) => self.style.message = msg,
56+
ProgressFinish::AbandonWithMessage(msg) => {
57+
self.state.message = TabExpandedString::new(msg, self.tab_width)
58+
}
5559
}
5660

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

95-
pub(crate) fn set_message(&mut self, now: Instant, msg: Cow<'static, str>) {
96-
self.style.message = msg;
97-
self.update_estimate_and_draw(now);
99+
pub(crate) fn set_tab_width(&mut self, tab_width: usize) {
100+
self.tab_width = tab_width;
101+
self.state.message.set_tab_width(tab_width);
102+
self.state.prefix.set_tab_width(tab_width);
103+
self.style.set_tab_width(tab_width);
98104
}
99105

100-
pub(crate) fn set_prefix(&mut self, now: Instant, prefix: Cow<'static, str>) {
101-
self.style.prefix = prefix;
102-
self.update_estimate_and_draw(now);
106+
pub(crate) fn set_style(&mut self, style: ProgressStyle) {
107+
self.style = style;
108+
self.style.set_tab_width(self.tab_width);
103109
}
104110

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

110-
fn update_estimate_and_draw(&mut self, now: Instant) {
116+
pub(crate) fn update_estimate_and_draw(&mut self, now: Instant) {
111117
let pos = self.state.pos.pos.load(Ordering::Relaxed);
112118
self.state.est.record(pos, now);
113119
let _ = self.draw(false, now);
@@ -190,6 +196,8 @@ pub struct ProgressState {
190196
pub(crate) started: Instant,
191197
status: Status,
192198
est: Estimator,
199+
pub(crate) message: TabExpandedString,
200+
pub(crate) prefix: TabExpandedString,
193201
}
194202

195203
impl ProgressState {
@@ -201,6 +209,8 @@ impl ProgressState {
201209
status: Status::InProgress,
202210
started: Instant::now(),
203211
est: Estimator::new(Instant::now()),
212+
message: TabExpandedString::NoTabs("".into()),
213+
prefix: TabExpandedString::NoTabs("".into()),
204214
}
205215
}
206216

@@ -285,6 +295,55 @@ impl ProgressState {
285295
}
286296
}
287297

298+
#[derive(Debug, PartialEq, Eq, Clone)]
299+
pub(crate) enum TabExpandedString {
300+
NoTabs(Cow<'static, str>),
301+
WithTabs {
302+
original: Cow<'static, str>,
303+
expanded: String,
304+
tab_width: usize,
305+
},
306+
}
307+
308+
impl TabExpandedString {
309+
pub(crate) fn new(s: Cow<'static, str>, tab_width: usize) -> Self {
310+
let expanded = s.replace('\t', &" ".repeat(tab_width));
311+
if s == expanded {
312+
Self::NoTabs(s)
313+
} else {
314+
Self::WithTabs {
315+
original: s,
316+
expanded,
317+
tab_width,
318+
}
319+
}
320+
}
321+
322+
pub(crate) fn expanded(&self) -> &str {
323+
match &self {
324+
Self::NoTabs(s) => {
325+
debug_assert!(!s.contains('\t'));
326+
s
327+
}
328+
Self::WithTabs { expanded, .. } => expanded,
329+
}
330+
}
331+
332+
pub(crate) fn set_tab_width(&mut self, new_tab_width: usize) {
333+
if let TabExpandedString::WithTabs {
334+
original,
335+
expanded,
336+
tab_width,
337+
} = self
338+
{
339+
if *tab_width != new_tab_width {
340+
*tab_width = new_tab_width;
341+
*expanded = original.replace('\t', &" ".repeat(new_tab_width));
342+
}
343+
}
344+
}
345+
}
346+
288347
/// Estimate the number of seconds per step
289348
///
290349
/// Ring buffer with constant capacity. Used by `ProgressBar`s to display `{eta}`,
@@ -482,6 +541,8 @@ pub(crate) enum Status {
482541
DoneHidden,
483542
}
484543

544+
pub(crate) const DEFAULT_TAB_WIDTH: usize = 8;
545+
485546
#[cfg(test)]
486547
mod tests {
487548
use super::*;

0 commit comments

Comments
 (0)