From 8f34af9efe07d62ffa3fa43a0ec973c7711558fa Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 2 Feb 2022 11:32:01 +0100 Subject: [PATCH 01/11] Move terminal manipulation into draw_to_term() --- src/draw_target.rs | 71 +++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/src/draw_target.rs b/src/draw_target.rs index 3a7b2f17..328b519e 100644 --- a/src/draw_target.rs +++ b/src/draw_target.rs @@ -140,56 +140,31 @@ impl ProgressDrawTarget { } /// Apply the given draw state (draws it). - pub(crate) fn apply_draw_state(&mut self, draw_state: ProgressDrawState) -> io::Result<()> { - let (term, last_line_count) = match self.kind { + pub(crate) fn apply_draw_state(&mut self, mut draw_state: ProgressDrawState) -> io::Result<()> { + match self.kind { ProgressDrawTargetKind::Term { ref term, ref mut last_line_count, leaky_bucket: None, - } => (term, last_line_count), + } => draw_state.draw_to_term(term, last_line_count), ProgressDrawTargetKind::Term { ref term, ref mut last_line_count, leaky_bucket: Some(ref mut leaky_bucket), } => { - if draw_state.force_draw || leaky_bucket.try_add_work() { - (term, last_line_count) - } else { - // rate limited - return Ok(()); + match draw_state.force_draw || leaky_bucket.try_add_work() { + true => draw_state.draw_to_term(term, last_line_count), + false => Ok(()), // rate limited } } - ProgressDrawTargetKind::Remote { idx, ref state, .. } => { - return state - .write() - .unwrap() - .draw(idx, draw_state) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e)); - } + ProgressDrawTargetKind::Remote { idx, ref state, .. } => state + .write() + .unwrap() + .draw(idx, draw_state) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e)), // Hidden, finished, or no need to refresh yet - _ => return Ok(()), - }; - - if !draw_state.lines.is_empty() && draw_state.move_cursor { - term.move_cursor_up(*last_line_count)?; - } else { - clear_last_lines(term, *last_line_count)?; + _ => Ok(()), } - - let shift = match draw_state.alignment { - MultiProgressAlignment::Bottom if draw_state.lines.len() < *last_line_count => { - let shift = *last_line_count - draw_state.lines.len(); - for _ in 0..shift { - term.write_line("")?; - } - shift - } - _ => 0, - }; - draw_state.draw_to_term(term)?; - term.flush()?; - *last_line_count = draw_state.lines.len() - draw_state.orphan_lines + shift; - Ok(()) } /// Properly disconnects from the draw target @@ -388,7 +363,24 @@ impl ProgressDrawState { } } - pub fn draw_to_term(&self, term: &Term) -> io::Result<()> { + fn draw_to_term(&mut self, term: &Term, last_line_count: &mut usize) -> io::Result<()> { + if !self.lines.is_empty() && self.move_cursor { + term.move_cursor_up(*last_line_count)?; + } else { + clear_last_lines(term, *last_line_count)?; + } + + let shift = match self.alignment { + MultiProgressAlignment::Bottom if self.lines.len() < *last_line_count => { + let shift = *last_line_count - self.lines.len(); + for _ in 0..shift { + term.write_line("")?; + } + shift + } + _ => 0, + }; + let len = self.lines.len(); for (idx, line) in self.lines.iter().enumerate() { if idx + 1 != len { @@ -402,6 +394,9 @@ impl ProgressDrawState { term.write_str(&" ".repeat(usize::from(term.size().1) - line_width))?; } } + + term.flush()?; + *last_line_count = self.lines.len() - self.orphan_lines + shift; Ok(()) } } From 8bf1176ce7b3e31942ddd9f395ac137aaabccbee Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 2 Feb 2022 11:43:26 +0100 Subject: [PATCH 02/11] Simplify match in apply_draw_state() --- src/draw_target.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/draw_target.rs b/src/draw_target.rs index 328b519e..b039ce9d 100644 --- a/src/draw_target.rs +++ b/src/draw_target.rs @@ -141,26 +141,26 @@ impl ProgressDrawTarget { /// Apply the given draw state (draws it). pub(crate) fn apply_draw_state(&mut self, mut draw_state: ProgressDrawState) -> io::Result<()> { - match self.kind { - ProgressDrawTargetKind::Term { - ref term, - ref mut last_line_count, - leaky_bucket: None, - } => draw_state.draw_to_term(term, last_line_count), + match &mut self.kind { ProgressDrawTargetKind::Term { - ref term, - ref mut last_line_count, - leaky_bucket: Some(ref mut leaky_bucket), + term, + last_line_count, + leaky_bucket, } => { - match draw_state.force_draw || leaky_bucket.try_add_work() { + let has_capacity = leaky_bucket + .as_mut() + .map(|b| b.try_add_work()) + .unwrap_or(true); + + match draw_state.force_draw || has_capacity { true => draw_state.draw_to_term(term, last_line_count), false => Ok(()), // rate limited } } - ProgressDrawTargetKind::Remote { idx, ref state, .. } => state + ProgressDrawTargetKind::Remote { idx, state, .. } => state .write() .unwrap() - .draw(idx, draw_state) + .draw(*idx, draw_state) .map_err(|e| io::Error::new(io::ErrorKind::Other, e)), // Hidden, finished, or no need to refresh yet _ => Ok(()), From f41f2d95088956341da110a42ad85db88cad60aa Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 2 Feb 2022 11:48:42 +0100 Subject: [PATCH 03/11] Move initialization of MultiProgressState into new() method --- src/draw_target.rs | 11 +++++++++++ src/progress_bar.rs | 9 +-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/draw_target.rs b/src/draw_target.rs index b039ce9d..18b7b905 100644 --- a/src/draw_target.rs +++ b/src/draw_target.rs @@ -231,6 +231,17 @@ pub(crate) struct MultiProgressState { } impl MultiProgressState { + pub(crate) fn new(draw_target: ProgressDrawTarget) -> Self { + Self { + draw_states: vec![], + free_set: vec![], + ordering: vec![], + draw_target, + move_cursor: false, + alignment: Default::default(), + } + } + fn width(&self) -> usize { self.draw_target.width() } diff --git a/src/progress_bar.rs b/src/progress_bar.rs index 3f9f5708..b2498e0d 100644 --- a/src/progress_bar.rs +++ b/src/progress_bar.rs @@ -598,14 +598,7 @@ impl MultiProgress { /// Creates a new multi progress object with the given draw target. pub fn with_draw_target(draw_target: ProgressDrawTarget) -> MultiProgress { MultiProgress { - state: Arc::new(RwLock::new(MultiProgressState { - draw_states: Vec::new(), - free_set: Vec::new(), - ordering: vec![], - draw_target, - move_cursor: false, - alignment: Default::default(), - })), + state: Arc::new(RwLock::new(MultiProgressState::new(draw_target))), } } From b60249482a400132eb0fa3cd81a32fd016abaa79 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 2 Feb 2022 11:51:01 +0100 Subject: [PATCH 04/11] Reuse orphan_lines across draws --- src/draw_target.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/draw_target.rs b/src/draw_target.rs index 18b7b905..5ae9011c 100644 --- a/src/draw_target.rs +++ b/src/draw_target.rs @@ -228,6 +228,8 @@ pub(crate) struct MultiProgressState { pub(crate) move_cursor: bool, /// Controls how the multi progress is aligned if some of its progress bars get removed, default is `Top` pub(crate) alignment: MultiProgressAlignment, + /// Orphaned lines are carried over across draw operations + orphan_lines: Vec, } impl MultiProgressState { @@ -239,6 +241,7 @@ impl MultiProgressState { draw_target, move_cursor: false, alignment: Default::default(), + orphan_lines: Vec::new(), } } @@ -248,12 +251,11 @@ impl MultiProgressState { pub(crate) fn draw(&mut self, idx: usize, draw_state: ProgressDrawState) -> io::Result<()> { let force_draw = draw_state.force_draw; - let mut orphan_lines = vec![]; // Split orphan lines out of the draw state, if any let lines = if draw_state.orphan_lines > 0 { let split = draw_state.lines.split_at(draw_state.orphan_lines); - orphan_lines.extend_from_slice(split.0); + self.orphan_lines.extend_from_slice(split.0); split.1.to_vec() } else { draw_state.lines @@ -276,8 +278,8 @@ impl MultiProgressState { // Make orphaned lines appear at the top, so they can be properly // forgotten. - let orphan_lines_count = orphan_lines.len(); - lines.append(&mut orphan_lines); + let orphan_lines_count = self.orphan_lines.len(); + lines.append(&mut self.orphan_lines); for index in self.ordering.iter() { let draw_state = &self.draw_states[*index]; From 20bf9d2c9c2537f0b6a4adaa3bc6a720a529c55b Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 2 Feb 2022 16:04:14 +0100 Subject: [PATCH 05/11] Avoid copying orphan lines in multi --- src/draw_target.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/draw_target.rs b/src/draw_target.rs index 5ae9011c..f7fb4449 100644 --- a/src/draw_target.rs +++ b/src/draw_target.rs @@ -249,20 +249,15 @@ impl MultiProgressState { self.draw_target.width() } - pub(crate) fn draw(&mut self, idx: usize, draw_state: ProgressDrawState) -> io::Result<()> { + pub(crate) fn draw(&mut self, idx: usize, mut draw_state: ProgressDrawState) -> io::Result<()> { let force_draw = draw_state.force_draw; // Split orphan lines out of the draw state, if any - let lines = if draw_state.orphan_lines > 0 { - let split = draw_state.lines.split_at(draw_state.orphan_lines); - self.orphan_lines.extend_from_slice(split.0); - split.1.to_vec() - } else { - draw_state.lines - }; + self.orphan_lines + .extend(draw_state.lines.drain(..draw_state.orphan_lines)); let draw_state = ProgressDrawState { - lines, + lines: draw_state.lines, orphan_lines: 0, ..draw_state }; From 9d740dbb764d07118e9894f1dca588ebd0d654fd Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 2 Feb 2022 15:04:50 +0100 Subject: [PATCH 06/11] Avoid dependency on draw target in ProgressStyle --- src/progress_bar.rs | 2 +- src/state.rs | 7 +------ src/style.rs | 35 +++++++++++++++++++++-------------- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/progress_bar.rs b/src/progress_bar.rs index b2498e0d..72b2a11e 100644 --- a/src/progress_bar.rs +++ b/src/progress_bar.rs @@ -245,7 +245,7 @@ impl ProgressBar { let mut lines: Vec = msg.as_ref().lines().map(Into::into).collect(); let orphan_lines = lines.len(); if state.should_render() && !state.draw_target.is_hidden() { - lines.extend(state.style.format_state(&*state)); + lines.extend(state.style.format_state(&*state, state.draw_target.width())); } let draw_state = ProgressDrawState { diff --git a/src/state.rs b/src/state.rs index 53e8e59d..1eab8472 100644 --- a/src/state.rs +++ b/src/state.rs @@ -88,11 +88,6 @@ impl ProgressState { &self.prefix } - /// The entire draw width - pub fn width(&self) -> usize { - self.draw_target.width() - } - /// The expected ETA pub fn eta(&self) -> Duration { if self.len == !0 || self.is_finished() { @@ -235,7 +230,7 @@ impl ProgressState { } let lines = match self.should_render() { - true => self.style.format_state(self), + true => self.style.format_state(self, self.draw_target.width()), false => Vec::new(), }; diff --git a/src/style.rs b/src/style.rs index 20219641..4ffea58d 100644 --- a/src/style.rs +++ b/src/style.rs @@ -227,7 +227,7 @@ impl ProgressStyle { } } - pub(crate) fn format_state(&self, state: &ProgressState) -> Vec { + pub(crate) fn format_state(&self, state: &ProgressState, target_width: usize) -> Vec { let mut cur = String::new(); let mut buf = String::new(); let mut rv = vec![]; @@ -360,7 +360,9 @@ impl ProgressStyle { } TemplatePart::Literal(s) => cur.push_str(s), TemplatePart::NewLine => rv.push(match wide { - Some(inner) => inner.expand(mem::take(&mut cur), self, state, &mut buf), + Some(inner) => { + inner.expand(mem::take(&mut cur), self, state, &mut buf, target_width) + } None => mem::take(&mut cur), }), } @@ -368,7 +370,9 @@ impl ProgressStyle { if !cur.is_empty() { rv.push(match wide { - Some(inner) => inner.expand(mem::take(&mut cur), self, state, &mut buf), + Some(inner) => { + inner.expand(mem::take(&mut cur), self, state, &mut buf, target_width) + } None => mem::take(&mut cur), }) } @@ -389,10 +393,9 @@ impl<'a> WideElement<'a> { style: &ProgressStyle, state: &ProgressState, buf: &mut String, + width: usize, ) -> String { - let left = state - .width() - .saturating_sub(measure_text_width(&*cur.replace("\x00", ""))); + let left = width.saturating_sub(measure_text_width(&*cur.replace("\x00", ""))); match self { Self::Bar { alt_style } => cur.replace( "\x00", @@ -713,14 +716,16 @@ mod tests { let mut style = ProgressStyle::default_bar(); style.format_map.0.insert("foo", |_| "FOO".into()); style.format_map.0.insert("bar", |_| "BAR".into()); - let state = ProgressState::new(10, ProgressDrawTarget::stdout()); + let draw_target = ProgressDrawTarget::stdout(); + let width = draw_target.width(); + let state = ProgressState::new(10, draw_target); style.template = Template::from_str("{{ {foo} {bar} }}"); - let rv = style.format_state(&state); + let rv = style.format_state(&state, width); assert_eq!(&rv[0], "{ FOO BAR }"); style.template = Template::from_str(r#"{ "foo": "{foo}", "bar": {bar} }"#); - let rv = style.format_state(&state); + let rv = style.format_state(&state, width); assert_eq!(&rv[0], r#"{ "foo": "FOO", "bar": BAR }"#); } @@ -730,22 +735,24 @@ mod tests { set_colors_enabled(true); let mut style = ProgressStyle::default_bar(); style.format_map.0.insert("foo", |_| "XXX".into()); - let state = ProgressState::new(10, ProgressDrawTarget::stdout()); + let draw_target = ProgressDrawTarget::stdout(); + let width = draw_target.width(); + let state = ProgressState::new(10, draw_target); style.template = Template::from_str("{foo:5}"); - let rv = style.format_state(&state); + let rv = style.format_state(&state, width); assert_eq!(&rv[0], "XXX "); style.template = Template::from_str("{foo:.red.on_blue}"); - let rv = style.format_state(&state); + let rv = style.format_state(&state, width); assert_eq!(&rv[0], "\u{1b}[31m\u{1b}[44mXXX\u{1b}[0m"); style.template = Template::from_str("{foo:^5.red.on_blue}"); - let rv = style.format_state(&state); + let rv = style.format_state(&state, width); assert_eq!(&rv[0], "\u{1b}[31m\u{1b}[44m XXX \u{1b}[0m"); style.template = Template::from_str("{foo:^5.red.on_blue/green.on_cyan}"); - let rv = style.format_state(&state); + let rv = style.format_state(&state, width); assert_eq!(&rv[0], "\u{1b}[31m\u{1b}[44m XXX \u{1b}[0m"); } } From 8d2c1e6861c371c3760562faf82765abfdc4ba80 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 2 Feb 2022 15:17:02 +0100 Subject: [PATCH 07/11] Isolate draw target from other state --- src/progress_bar.rs | 83 ++++++++------- src/state.rs | 248 +++++++++++++++++++++++--------------------- src/style.rs | 4 +- 3 files changed, 176 insertions(+), 159 deletions(-) diff --git a/src/progress_bar.rs b/src/progress_bar.rs index 72b2a11e..f7d43236 100644 --- a/src/progress_bar.rs +++ b/src/progress_bar.rs @@ -9,7 +9,7 @@ use std::time::{Duration, Instant}; use crate::draw_target::{ MultiProgressAlignment, MultiProgressState, ProgressDrawState, ProgressDrawTarget, }; -use crate::state::{ProgressState, Status}; +use crate::state::{BarState, ProgressState, Status}; use crate::style::ProgressStyle; use crate::{ProgressBarIter, ProgressIterator}; @@ -19,7 +19,7 @@ use crate::{ProgressBarIter, ProgressIterator}; /// just increments the refcount (so the original and its clone share the same state). #[derive(Clone)] pub struct ProgressBar { - state: Arc>, + state: Arc>, } impl fmt::Debug for ProgressBar { @@ -47,39 +47,42 @@ impl ProgressBar { } /// Creates a new progress bar with a given length and draw target - pub fn with_draw_target(len: u64, target: ProgressDrawTarget) -> ProgressBar { + pub fn with_draw_target(len: u64, draw_target: ProgressDrawTarget) -> ProgressBar { ProgressBar { - state: Arc::new(Mutex::new(ProgressState::new(len, target))), + state: Arc::new(Mutex::new(BarState { + draw_target, + state: ProgressState::new(len), + })), } } /// A convenience builder-like function for a progress bar with a given style pub fn with_style(self, style: ProgressStyle) -> ProgressBar { - self.state.lock().unwrap().style = style; + self.state.lock().unwrap().state.style = style; self } /// A convenience builder-like function for a progress bar with a given prefix pub fn with_prefix(self, prefix: impl Into>) -> ProgressBar { - self.state.lock().unwrap().prefix = prefix.into(); + self.state.lock().unwrap().state.prefix = prefix.into(); self } /// A convenience builder-like function for a progress bar with a given message pub fn with_message(self, message: impl Into>) -> ProgressBar { - self.state.lock().unwrap().message = message.into(); + self.state.lock().unwrap().state.message = message.into(); self } /// A convenience builder-like function for a progress bar with a given position pub fn with_position(self, pos: u64) -> ProgressBar { - self.state.lock().unwrap().pos = pos; + self.state.lock().unwrap().state.pos = pos; self } /// A convenience builder-like function for a progress bar with a given elapsed time pub fn with_elapsed(self, elapsed: Duration) -> ProgressBar { - self.state.lock().unwrap().started = Instant::now() - elapsed; + self.state.lock().unwrap().state.started = Instant::now() - elapsed; self } @@ -96,7 +99,7 @@ impl ProgressBar { /// /// This does not redraw the bar. Call [`ProgressBar::tick()`] to force it. pub fn set_style(&self, style: ProgressStyle) { - self.state.lock().unwrap().style = style; + self.state.lock().unwrap().state.style = style; } /// Spawns a background thread to tick the progress bar @@ -109,34 +112,34 @@ impl ProgressBar { /// have any effect. pub fn enable_steady_tick(&self, ms: u64) { let mut state = self.state.lock().unwrap(); - state.steady_tick = ms; - if state.tick_thread.is_some() { + state.state.steady_tick = ms; + if state.state.tick_thread.is_some() { return; } // Using a weak pointer is required to prevent a potential deadlock. See issue #133 let state_arc = Arc::downgrade(&self.state); - state.tick_thread = Some(thread::spawn(move || Self::steady_tick(state_arc, ms))); + state.state.tick_thread = Some(thread::spawn(move || Self::steady_tick(state_arc, ms))); ::std::mem::drop(state); // use the side effect of tick to force the bar to tick. self.tick(); } - fn steady_tick(state_arc: Weak>, mut ms: u64) { + fn steady_tick(state_arc: Weak>, mut ms: u64) { loop { thread::sleep(Duration::from_millis(ms)); if let Some(state_arc) = state_arc.upgrade() { let mut state = state_arc.lock().unwrap(); - if state.is_finished() || state.steady_tick == 0 { - state.steady_tick = 0; - state.tick_thread = None; + if state.state.is_finished() || state.state.steady_tick == 0 { + state.state.steady_tick = 0; + state.state.tick_thread = None; break; } - if state.tick != 0 { - state.tick = state.tick.saturating_add(1); + if state.state.tick != 0 { + state.state.tick = state.state.tick.saturating_add(1); } - ms = state.steady_tick; + ms = state.state.steady_tick; state.draw(false).ok(); } else { @@ -171,8 +174,8 @@ impl ProgressBar { /// Note that `ProgressDrawTarget` may impose additional buffering of redraws. pub fn set_draw_delta(&self, n: u64) { let mut state = self.state.lock().unwrap(); - state.draw_delta = n; - state.draw_next = state.pos.saturating_add(state.draw_delta); + state.state.draw_delta = n; + state.state.draw_next = state.state.pos.saturating_add(state.state.draw_delta); } /// Sets the refresh rate of progress bar to `n` updates per seconds @@ -192,10 +195,11 @@ impl ProgressBar { /// Note that the [`ProgressDrawTarget`] may impose additional buffering of redraws. pub fn set_draw_rate(&self, n: u64) { let mut state = self.state.lock().unwrap(); - state.draw_rate = n; - state.draw_next = state + state.state.draw_rate = n; + state.state.draw_next = state + .state .pos - .saturating_add((state.per_sec() / n as f64) as u64); + .saturating_add((state.state.per_sec() / n as f64) as u64); } /// Manually ticks the spinner or progress bar @@ -226,7 +230,7 @@ impl ProgressBar { /// Indicates that the progress bar finished pub fn is_finished(&self) -> bool { - self.state.lock().unwrap().is_finished() + self.state.lock().unwrap().state.is_finished() } /// Print a log line above the progress bar @@ -244,8 +248,13 @@ impl ProgressBar { let mut lines: Vec = msg.as_ref().lines().map(Into::into).collect(); let orphan_lines = lines.len(); - if state.should_render() && !state.draw_target.is_hidden() { - lines.extend(state.style.format_state(&*state, state.draw_target.width())); + if state.state.should_render() && !state.draw_target.is_hidden() { + lines.extend( + state + .state + .style + .format_state(&state.state, state.draw_target.width()), + ); } let draw_state = ProgressDrawState { @@ -544,32 +553,32 @@ impl ProgressBar { /// Returns the current position pub fn position(&self) -> u64 { - self.state.lock().unwrap().pos + self.state.lock().unwrap().state.pos } /// Returns the current length pub fn length(&self) -> u64 { - self.state.lock().unwrap().len + self.state.lock().unwrap().state.len } /// Returns the current ETA pub fn eta(&self) -> Duration { - self.state.lock().unwrap().eta() + self.state.lock().unwrap().state.eta() } /// Returns the current rate of progress pub fn per_sec(&self) -> f64 { - self.state.lock().unwrap().per_sec() + self.state.lock().unwrap().state.per_sec() } /// Returns the current expected duration pub fn duration(&self) -> Duration { - self.state.lock().unwrap().duration() + self.state.lock().unwrap().state.duration() } /// Returns the current elapsed time pub fn elapsed(&self) -> Duration { - self.state.lock().unwrap().started.elapsed() + self.state.lock().unwrap().state.started.elapsed() } } @@ -762,7 +771,7 @@ impl MultiProgress { /// Useful for creating custom steady tick implementations #[derive(Clone, Default)] pub struct WeakProgressBar { - state: Weak>, + state: Weak>, } impl WeakProgressBar { @@ -798,14 +807,14 @@ mod tests { #[test] fn test_pbar_zero() { let pb = ProgressBar::new(0); - assert_eq!(pb.state.lock().unwrap().fraction(), 1.0); + assert_eq!(pb.state.lock().unwrap().state.fraction(), 1.0); } #[allow(clippy::float_cmp)] #[test] fn test_pbar_maxu64() { let pb = ProgressBar::new(!0); - assert_eq!(pb.state.lock().unwrap().fraction(), 0.0); + assert_eq!(pb.state.lock().unwrap().state.fraction(), 0.0); } #[test] diff --git a/src/state.rs b/src/state.rs index 1eab8472..8ddd210c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -7,6 +7,132 @@ use std::time::{Duration, Instant}; use crate::draw_target::{ProgressDrawState, ProgressDrawTarget}; use crate::style::{ProgressFinish, ProgressStyle}; +pub(crate) struct BarState { + pub(crate) draw_target: ProgressDrawTarget, + pub(crate) state: ProgressState, +} + +impl BarState { + /// Finishes the progress bar using the [`ProgressFinish`] behavior stored + /// in the [`ProgressStyle`]. + pub(crate) fn finish_using_style(&mut self) { + match self.state.style.get_on_finish() { + ProgressFinish::AndLeave => self.finish(), + ProgressFinish::AtCurrentPos => self.finish_at_current_pos(), + ProgressFinish::WithMessage(msg) => { + // Equivalent to `self.finish_with_message` but avoids borrow checker error + self.state.message.clone_from(msg); + self.finish(); + } + ProgressFinish::AndClear => self.finish_and_clear(), + ProgressFinish::Abandon => self.abandon(), + ProgressFinish::AbandonWithMessage(msg) => { + // Equivalent to `self.abandon_with_message` but avoids borrow checker error + self.state.message.clone_from(msg); + self.abandon(); + } + } + } + + /// Finishes the progress bar and leaves the current message. + pub(crate) fn finish(&mut self) { + self.update_and_force_draw(|state| { + state.pos = state.len; + state.status = Status::DoneVisible; + }); + } + + /// Finishes the progress bar at current position and leaves the current message. + pub(crate) fn finish_at_current_pos(&mut self) { + self.update_and_force_draw(|state| { + state.status = Status::DoneVisible; + }); + } + + /// Finishes the progress bar and sets a message. + pub(crate) fn finish_with_message(&mut self, msg: impl Into>) { + let msg = msg.into(); + self.update_and_force_draw(|state| { + state.message = msg; + state.pos = state.len; + state.status = Status::DoneVisible; + }); + } + + /// Finishes the progress bar and completely clears it. + pub(crate) fn finish_and_clear(&mut self) { + self.update_and_force_draw(|state| { + state.pos = state.len; + state.status = Status::DoneHidden; + }); + } + + /// Finishes the progress bar and leaves the current message and progress. + pub(crate) fn abandon(&mut self) { + self.update_and_force_draw(|state| { + state.status = Status::DoneVisible; + }); + } + + /// Finishes the progress bar and sets a message, and leaves the current progress. + pub(crate) fn abandon_with_message(&mut self, msg: impl Into>) { + let msg = msg.into(); + self.update_and_force_draw(|state| { + state.message = msg; + state.status = Status::DoneVisible; + }); + } + + /// Call the provided `FnOnce` to update the state. Then redraw the + /// progress bar if the state has changed. + pub(crate) fn update_and_draw(&mut self, f: F) { + if self.state.update(f) { + self.draw(false).ok(); + } + } + + /// Call the provided `FnOnce` to update the state. Then unconditionally redraw the + /// progress bar. + pub(crate) fn update_and_force_draw(&mut self, f: F) { + self.state.update(|state| { + state.draw_next = state.pos; + f(state); + }); + self.draw(true).ok(); + } + + pub(crate) fn draw(&mut self, force_draw: bool) -> io::Result<()> { + // we can bail early if the draw target is hidden. + if self.draw_target.is_hidden() { + return Ok(()); + } + + let lines = match self.state.should_render() { + true => self + .state + .style + .format_state(&self.state, self.draw_target.width()), + false => Vec::new(), + }; + + // `|| self.is_finished()` should not be needed here, but we used to always for draw for + // finished progress bar, so it's kept as to not cause compatibility issues in weird cases. + let draw_state = ProgressDrawState::new(lines, force_draw || self.state.is_finished()); + self.draw_target.apply_draw_state(draw_state) + } +} + +impl Drop for BarState { + fn drop(&mut self) { + // Progress bar is already finished. Do not need to do anything. + if self.state.is_finished() { + return; + } + + self.finish_using_style(); + } +} + /// The state of a progress bar at a moment in time. pub struct ProgressState { pub(crate) style: ProgressStyle, @@ -14,7 +140,6 @@ pub struct ProgressState { pub len: u64, pub(crate) tick: u64, pub(crate) started: Instant, - pub(crate) draw_target: ProgressDrawTarget, pub(crate) message: Cow<'static, str>, pub(crate) prefix: Cow<'static, str>, pub(crate) draw_delta: u64, @@ -27,10 +152,9 @@ pub struct ProgressState { } impl ProgressState { - pub(crate) fn new(len: u64, draw_target: ProgressDrawTarget) -> Self { - ProgressState { + pub(crate) fn new(len: u64) -> Self { + Self { style: ProgressStyle::default_bar(), - draw_target, message: "".into(), prefix: "".into(), pos: 0, @@ -115,24 +239,6 @@ impl ProgressState { } } - /// Call the provided `FnOnce` to update the state. Then redraw the - /// progress bar if the state has changed. - pub fn update_and_draw(&mut self, f: F) { - if self.update(f) { - self.draw(false).ok(); - } - } - - /// Call the provided `FnOnce` to update the state. Then unconditionally redraw the - /// progress bar. - pub fn update_and_force_draw(&mut self, f: F) { - self.update(|state| { - state.draw_next = state.pos; - f(state); - }); - self.draw(true).ok(); - } - /// Call the provided `FnOnce` to update the state. If a draw should be run, returns `true`. pub fn update(&mut self, f: F) -> bool { let old_pos = self.pos; @@ -152,104 +258,6 @@ impl ProgressState { false } } - - /// Finishes the progress bar and leaves the current message. - pub fn finish(&mut self) { - self.update_and_force_draw(|state| { - state.pos = state.len; - state.status = Status::DoneVisible; - }); - } - - /// Finishes the progress bar at current position and leaves the current message. - pub fn finish_at_current_pos(&mut self) { - self.update_and_force_draw(|state| { - state.status = Status::DoneVisible; - }); - } - - /// Finishes the progress bar and sets a message. - pub fn finish_with_message(&mut self, msg: impl Into>) { - let msg = msg.into(); - self.update_and_force_draw(|state| { - state.message = msg; - state.pos = state.len; - state.status = Status::DoneVisible; - }); - } - - /// Finishes the progress bar and completely clears it. - pub fn finish_and_clear(&mut self) { - self.update_and_force_draw(|state| { - state.pos = state.len; - state.status = Status::DoneHidden; - }); - } - - /// Finishes the progress bar and leaves the current message and progress. - pub fn abandon(&mut self) { - self.update_and_force_draw(|state| { - state.status = Status::DoneVisible; - }); - } - - /// Finishes the progress bar and sets a message, and leaves the current progress. - pub fn abandon_with_message(&mut self, msg: impl Into>) { - let msg = msg.into(); - self.update_and_force_draw(|state| { - state.message = msg; - state.status = Status::DoneVisible; - }); - } - - /// Finishes the progress bar using the [`ProgressFinish`] behavior stored - /// in the [`ProgressStyle`]. - pub fn finish_using_style(&mut self) { - match self.style.get_on_finish() { - ProgressFinish::AndLeave => self.finish(), - ProgressFinish::AtCurrentPos => self.finish_at_current_pos(), - ProgressFinish::WithMessage(msg) => { - // Equivalent to `self.finish_with_message` but avoids borrow checker error - self.message.clone_from(msg); - self.finish(); - } - ProgressFinish::AndClear => self.finish_and_clear(), - ProgressFinish::Abandon => self.abandon(), - ProgressFinish::AbandonWithMessage(msg) => { - // Equivalent to `self.abandon_with_message` but avoids borrow checker error - self.message.clone_from(msg); - self.abandon(); - } - } - } - - pub(crate) fn draw(&mut self, force_draw: bool) -> io::Result<()> { - // we can bail early if the draw target is hidden. - if self.draw_target.is_hidden() { - return Ok(()); - } - - let lines = match self.should_render() { - true => self.style.format_state(self, self.draw_target.width()), - false => Vec::new(), - }; - - // `|| self.is_finished()` should not be needed here, but we used to always for draw for - // finished progress bar, so it's kept as to not cause compatibility issues in weird cases. - let draw_state = ProgressDrawState::new(lines, force_draw || self.is_finished()); - self.draw_target.apply_draw_state(draw_state) - } -} - -impl Drop for ProgressState { - fn drop(&mut self) { - // Progress bar is already finished. Do not need to do anything. - if self.is_finished() { - return; - } - - self.finish_using_style(); - } } /// Ring buffer with constant capacity. Used by `ProgressBar`s to display `{eta}`, `{eta_precise}`, diff --git a/src/style.rs b/src/style.rs index 4ffea58d..a176243d 100644 --- a/src/style.rs +++ b/src/style.rs @@ -718,7 +718,7 @@ mod tests { style.format_map.0.insert("bar", |_| "BAR".into()); let draw_target = ProgressDrawTarget::stdout(); let width = draw_target.width(); - let state = ProgressState::new(10, draw_target); + let state = ProgressState::new(10); style.template = Template::from_str("{{ {foo} {bar} }}"); let rv = style.format_state(&state, width); @@ -737,7 +737,7 @@ mod tests { style.format_map.0.insert("foo", |_| "XXX".into()); let draw_target = ProgressDrawTarget::stdout(); let width = draw_target.width(); - let state = ProgressState::new(10, draw_target); + let state = ProgressState::new(10); style.template = Template::from_str("{foo:5}"); let rv = style.format_state(&state, width); From 3ac8030a74b944f99dca705d976287ed4c1ac0e6 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 2 Feb 2022 15:36:27 +0100 Subject: [PATCH 08/11] Draw into persistent draw states --- src/draw_target.rs | 199 ++++++++++++++++++++++++++++++++------------ src/progress_bar.rs | 87 +++++++++++-------- src/state.rs | 26 ++++-- 3 files changed, 216 insertions(+), 96 deletions(-) diff --git a/src/draw_target.rs b/src/draw_target.rs index f7fb4449..08d6df61 100644 --- a/src/draw_target.rs +++ b/src/draw_target.rs @@ -1,5 +1,5 @@ use std::io; -use std::sync::{Arc, RwLock}; +use std::sync::{Arc, RwLock, RwLockWriteGuard}; use std::time::Instant; use console::Term; @@ -105,6 +105,7 @@ impl ProgressDrawTarget { leak_rate: rate as f64, last_update: Instant::now(), }), + draw_state: ProgressDrawState::new(Vec::new(), false), }, } } @@ -140,12 +141,13 @@ impl ProgressDrawTarget { } /// Apply the given draw state (draws it). - pub(crate) fn apply_draw_state(&mut self, mut draw_state: ProgressDrawState) -> io::Result<()> { + pub(crate) fn drawable(&mut self) -> Option> { match &mut self.kind { ProgressDrawTargetKind::Term { term, last_line_count, leaky_bucket, + draw_state, } => { let has_capacity = leaky_bucket .as_mut() @@ -153,17 +155,24 @@ impl ProgressDrawTarget { .unwrap_or(true); match draw_state.force_draw || has_capacity { - true => draw_state.draw_to_term(term, last_line_count), - false => Ok(()), // rate limited + true => Some(Drawable::Term { + term, + last_line_count, + draw_state, + }), + false => None, // rate limited } } - ProgressDrawTargetKind::Remote { idx, state, .. } => state - .write() - .unwrap() - .draw(*idx, draw_state) - .map_err(|e| io::Error::new(io::ErrorKind::Other, e)), + ProgressDrawTargetKind::Remote { idx, state, .. } => { + let state = state.write().unwrap(); + Some(Drawable::Multi { + idx: *idx, + state, + force_draw: false, + }) + } // Hidden, finished, or no need to refresh yet - _ => Ok(()), + _ => None, } } @@ -172,20 +181,18 @@ impl ProgressDrawTarget { match self.kind { ProgressDrawTargetKind::Term { .. } => {} ProgressDrawTargetKind::Remote { idx, ref state, .. } => { - state - .write() - .unwrap() - .draw( - idx, - ProgressDrawState { - lines: vec![], - orphan_lines: 0, - force_draw: true, - move_cursor: false, - alignment: Default::default(), - }, - ) - .ok(); + let state = state.write().unwrap(); + let mut drawable = Drawable::Multi { + state, + idx, + force_draw: false, + }; + + let mut draw_state = drawable.state(); + draw_state.reset(); + draw_state.force_draw = true; + drop(draw_state); + let _ = drawable.draw(); } ProgressDrawTargetKind::Hidden => {} }; @@ -205,6 +212,7 @@ enum ProgressDrawTargetKind { term: Term, last_line_count: usize, leaky_bucket: Option, + draw_state: ProgressDrawState, }, Remote { state: Arc>, @@ -213,6 +221,96 @@ enum ProgressDrawTargetKind { Hidden, } +pub(crate) enum Drawable<'a> { + Term { + term: &'a Term, + last_line_count: &'a mut usize, + draw_state: &'a mut ProgressDrawState, + }, + Multi { + state: RwLockWriteGuard<'a, MultiProgressState>, + idx: usize, + force_draw: bool, + }, +} + +impl<'a> Drawable<'a> { + pub(crate) fn state(&mut self) -> DrawStateWrapper<'_> { + match self { + Drawable::Term { draw_state, .. } => DrawStateWrapper { + state: *draw_state, + extra: None, + }, + Drawable::Multi { + state, + idx, + force_draw, + } => { + let state = &mut **state; + let (states, orphans) = (&mut state.draw_states, &mut state.orphan_lines); + match states.get_mut(*idx) { + Some(Some(draw_state)) => DrawStateWrapper { + state: draw_state, + extra: Some((orphans, force_draw)), + }, + Some(inner) => { + *inner = Some(ProgressDrawState::new(Vec::new(), false)); + DrawStateWrapper { + state: inner.as_mut().unwrap(), + extra: Some((orphans, force_draw)), + } + } + _ => unreachable!(), + } + } + } + } + + pub(crate) fn draw(self) -> io::Result<()> { + match self { + Drawable::Term { + term, + last_line_count, + draw_state, + } => draw_state.draw_to_term(term, last_line_count), + Drawable::Multi { + mut state, + force_draw, + .. + } => state.draw(force_draw), + } + } +} + +pub(crate) struct DrawStateWrapper<'a> { + state: &'a mut ProgressDrawState, + extra: Option<(&'a mut Vec, &'a mut bool)>, +} + +impl std::ops::Deref for DrawStateWrapper<'_> { + type Target = ProgressDrawState; + + fn deref(&self) -> &Self::Target { + self.state + } +} + +impl std::ops::DerefMut for DrawStateWrapper<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.state + } +} + +impl Drop for DrawStateWrapper<'_> { + fn drop(&mut self) { + if let Some((orphan_lines, force_draw)) = &mut self.extra { + orphan_lines.extend(self.state.lines.drain(..self.state.orphan_lines)); + self.state.orphan_lines = 0; + **force_draw = self.state.force_draw; + } + } +} + #[derive(Debug)] pub(crate) struct MultiProgressState { /// The collection of states corresponding to progress bars @@ -249,47 +347,38 @@ impl MultiProgressState { self.draw_target.width() } - pub(crate) fn draw(&mut self, idx: usize, mut draw_state: ProgressDrawState) -> io::Result<()> { - let force_draw = draw_state.force_draw; - - // Split orphan lines out of the draw state, if any - self.orphan_lines - .extend(draw_state.lines.drain(..draw_state.orphan_lines)); - - let draw_state = ProgressDrawState { - lines: draw_state.lines, - orphan_lines: 0, - ..draw_state - }; - - self.draw_states[idx] = Some(draw_state); - + fn draw(&mut self, force_draw: bool) -> io::Result<()> { // the rest from here is only drawing, we can skip it. if self.draw_target.is_hidden() { return Ok(()); } - let mut lines = vec![]; + let mut drawable = match self.draw_target.drawable() { + Some(drawable) => drawable, + None => return Ok(()), + }; + + let mut draw_state = drawable.state(); + draw_state.reset(); // Make orphaned lines appear at the top, so they can be properly // forgotten. let orphan_lines_count = self.orphan_lines.len(); - lines.append(&mut self.orphan_lines); + draw_state.lines.append(&mut self.orphan_lines); for index in self.ordering.iter() { - let draw_state = &self.draw_states[*index]; - if let Some(ref draw_state) = draw_state { - lines.extend_from_slice(&draw_state.lines[..]); + if let Some(state) = &self.draw_states[*index] { + draw_state.lines.extend_from_slice(&state.lines[..]); } } - self.draw_target.apply_draw_state(ProgressDrawState { - lines, - orphan_lines: orphan_lines_count, - force_draw: force_draw || orphan_lines_count > 0, - move_cursor: self.move_cursor, - alignment: self.alignment, - }) + draw_state.orphan_lines = orphan_lines_count; + draw_state.force_draw = force_draw || orphan_lines_count > 0; + draw_state.move_cursor = self.move_cursor; + draw_state.alignment = self.alignment; + + drop(draw_state); + drawable.draw() } pub(crate) fn len(&self) -> usize { @@ -407,6 +496,14 @@ impl ProgressDrawState { *last_line_count = self.lines.len() - self.orphan_lines + shift; Ok(()) } + + pub(crate) fn reset(&mut self) { + self.lines.clear(); + self.orphan_lines = 0; + self.force_draw = false; + self.move_cursor = false; + self.alignment = MultiProgressAlignment::default(); + } } /// Vertical alignment of a multi progress. diff --git a/src/progress_bar.rs b/src/progress_bar.rs index f7d43236..eadbf572 100644 --- a/src/progress_bar.rs +++ b/src/progress_bar.rs @@ -6,9 +6,7 @@ use std::sync::{Arc, Mutex, Weak}; use std::thread; use std::time::{Duration, Instant}; -use crate::draw_target::{ - MultiProgressAlignment, MultiProgressState, ProgressDrawState, ProgressDrawTarget, -}; +use crate::draw_target::{MultiProgressAlignment, MultiProgressState, ProgressDrawTarget}; use crate::state::{BarState, ProgressState, Status}; use crate::style::ProgressStyle; use crate::{ProgressBarIter, ProgressIterator}; @@ -244,28 +242,35 @@ impl ProgressBar { /// /// [`suspend`]: ProgressBar::suspend pub fn println>(&self, msg: I) { - let mut state = self.state.lock().unwrap(); + let state = &mut *self.state.lock().unwrap(); + let draw_lines = state.state.should_render() && !state.draw_target.is_hidden(); + let (draw_target, state) = (&mut state.draw_target, &state.state); + let width = draw_target.width(); + + let mut drawable = match draw_target.drawable() { + Some(drawable) => drawable, + None => return, + }; - let mut lines: Vec = msg.as_ref().lines().map(Into::into).collect(); - let orphan_lines = lines.len(); - if state.state.should_render() && !state.draw_target.is_hidden() { - lines.extend( - state - .state - .style - .format_state(&state.state, state.draw_target.width()), - ); + let mut draw_state = drawable.state(); + draw_state.reset(); + + draw_state.force_draw = true; + draw_state.move_cursor = false; + draw_state.alignment = Default::default(); + + draw_state + .lines + .extend(msg.as_ref().lines().map(Into::into)); + draw_state.orphan_lines = draw_state.lines.len(); + if draw_lines { + draw_state + .lines + .extend(state.style.format_state(state, width)); } - let draw_state = ProgressDrawState { - lines, - orphan_lines, - force_draw: true, - move_cursor: false, - alignment: Default::default(), - }; - - state.draw_target.apply_draw_state(draw_state).ok(); + drop(draw_state); + let _ = drawable.draw(); } /// Sets the position of the progress bar @@ -435,9 +440,15 @@ impl ProgressBar { /// ``` pub fn suspend R, R>(&self, f: F) -> R { let mut state = self.state.lock().unwrap(); - let _ = state - .draw_target - .apply_draw_state(ProgressDrawState::new(vec![], true)); + + if let Some(mut drawable) = state.draw_target.drawable() { + let mut draw_state = drawable.state(); + draw_state.reset(); + draw_state.force_draw = true; + drop(draw_state); + let _ = drawable.draw(); + } + let ret = f(); let _ = state.draw(true); ret @@ -752,17 +763,21 @@ impl MultiProgress { pub fn clear(&self) -> io::Result<()> { let mut state = self.state.write().unwrap(); - let move_cursor = state.move_cursor; - let alignment = state.alignment; - state.draw_target.apply_draw_state(ProgressDrawState { - lines: vec![], - orphan_lines: 0, - force_draw: true, - move_cursor, - alignment, - })?; - - Ok(()) + let (move_cursor, alignment) = (state.move_cursor, state.alignment); + + let mut drawable = match state.draw_target.drawable() { + Some(drawable) => drawable, + None => return Ok(()), + }; + + let mut draw_state = drawable.state(); + draw_state.reset(); + draw_state.force_draw = true; + draw_state.move_cursor = move_cursor; + draw_state.alignment = alignment; + + drop(draw_state); + drawable.draw() } } diff --git a/src/state.rs b/src/state.rs index 8ddd210c..c09baedb 100644 --- a/src/state.rs +++ b/src/state.rs @@ -4,7 +4,7 @@ use std::io; use std::thread; use std::time::{Duration, Instant}; -use crate::draw_target::{ProgressDrawState, ProgressDrawTarget}; +use crate::draw_target::ProgressDrawTarget; use crate::style::{ProgressFinish, ProgressStyle}; pub(crate) struct BarState { @@ -107,18 +107,26 @@ impl BarState { return Ok(()); } - let lines = match self.state.should_render() { - true => self - .state - .style - .format_state(&self.state, self.draw_target.width()), - false => Vec::new(), + let width = self.draw_target.width(); + let mut drawable = match self.draw_target.drawable() { + Some(drawable) => drawable, + None => return Ok(()), }; + let mut draw_state = drawable.state(); + draw_state.reset(); + // `|| self.is_finished()` should not be needed here, but we used to always for draw for // finished progress bar, so it's kept as to not cause compatibility issues in weird cases. - let draw_state = ProgressDrawState::new(lines, force_draw || self.state.is_finished()); - self.draw_target.apply_draw_state(draw_state) + draw_state.force_draw = force_draw || self.state.is_finished(); + + draw_state.lines = match self.state.should_render() { + true => self.state.style.format_state(&self.state, width), + false => Vec::new(), + }; + + drop(draw_state); + drawable.draw() } } From c0186a94f1d65434c5b47da1b6c123bab7d102f1 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 2 Feb 2022 15:47:06 +0100 Subject: [PATCH 09/11] Format directly into draw state buffer --- src/progress_bar.rs | 6 ++--- src/state.rs | 9 ++++---- src/style.rs | 56 +++++++++++++++++++++++++++------------------ 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/progress_bar.rs b/src/progress_bar.rs index eadbf572..aeac81b0 100644 --- a/src/progress_bar.rs +++ b/src/progress_bar.rs @@ -264,9 +264,9 @@ impl ProgressBar { .extend(msg.as_ref().lines().map(Into::into)); draw_state.orphan_lines = draw_state.lines.len(); if draw_lines { - draw_state - .lines - .extend(state.style.format_state(state, width)); + state + .style + .format_state(state, &mut draw_state.lines, width); } drop(draw_state); diff --git a/src/state.rs b/src/state.rs index c09baedb..2d0cf6e9 100644 --- a/src/state.rs +++ b/src/state.rs @@ -120,10 +120,11 @@ impl BarState { // finished progress bar, so it's kept as to not cause compatibility issues in weird cases. draw_state.force_draw = force_draw || self.state.is_finished(); - draw_state.lines = match self.state.should_render() { - true => self.state.style.format_state(&self.state, width), - false => Vec::new(), - }; + if self.state.should_render() { + self.state + .style + .format_state(&self.state, &mut draw_state.lines, width); + } drop(draw_state); drawable.draw() diff --git a/src/style.rs b/src/style.rs index a176243d..9caf99ca 100644 --- a/src/style.rs +++ b/src/style.rs @@ -227,10 +227,14 @@ impl ProgressStyle { } } - pub(crate) fn format_state(&self, state: &ProgressState, target_width: usize) -> Vec { + pub(crate) fn format_state( + &self, + state: &ProgressState, + lines: &mut Vec, + target_width: usize, + ) { let mut cur = String::new(); let mut buf = String::new(); - let mut rv = vec![]; let mut wide = None; for part in &self.template.parts { match part { @@ -359,7 +363,7 @@ impl ProgressStyle { } } TemplatePart::Literal(s) => cur.push_str(s), - TemplatePart::NewLine => rv.push(match wide { + TemplatePart::NewLine => lines.push(match wide { Some(inner) => { inner.expand(mem::take(&mut cur), self, state, &mut buf, target_width) } @@ -369,14 +373,13 @@ impl ProgressStyle { } if !cur.is_empty() { - rv.push(match wide { + lines.push(match wide { Some(inner) => { inner.expand(mem::take(&mut cur), self, state, &mut buf, target_width) } None => mem::take(&mut cur), }) } - rv } } @@ -713,46 +716,55 @@ mod tests { #[test] fn test_expand_template() { - let mut style = ProgressStyle::default_bar(); - style.format_map.0.insert("foo", |_| "FOO".into()); - style.format_map.0.insert("bar", |_| "BAR".into()); let draw_target = ProgressDrawTarget::stdout(); let width = draw_target.width(); let state = ProgressState::new(10); + let mut buf = Vec::new(); + + let mut style = ProgressStyle::default_bar(); + style.format_map.0.insert("foo", |_| "FOO".into()); + style.format_map.0.insert("bar", |_| "BAR".into()); style.template = Template::from_str("{{ {foo} {bar} }}"); - let rv = style.format_state(&state, width); - assert_eq!(&rv[0], "{ FOO BAR }"); + style.format_state(&state, &mut buf, width); + assert_eq!(&buf[0], "{ FOO BAR }"); + buf.clear(); style.template = Template::from_str(r#"{ "foo": "{foo}", "bar": {bar} }"#); - let rv = style.format_state(&state, width); - assert_eq!(&rv[0], r#"{ "foo": "FOO", "bar": BAR }"#); + style.format_state(&state, &mut buf, width); + assert_eq!(&buf[0], r#"{ "foo": "FOO", "bar": BAR }"#); } #[test] fn test_expand_template_flags() { use console::set_colors_enabled; set_colors_enabled(true); - let mut style = ProgressStyle::default_bar(); - style.format_map.0.insert("foo", |_| "XXX".into()); + let draw_target = ProgressDrawTarget::stdout(); let width = draw_target.width(); let state = ProgressState::new(10); + let mut buf = Vec::new(); + + let mut style = ProgressStyle::default_bar(); + style.format_map.0.insert("foo", |_| "XXX".into()); style.template = Template::from_str("{foo:5}"); - let rv = style.format_state(&state, width); - assert_eq!(&rv[0], "XXX "); + style.format_state(&state, &mut buf, width); + assert_eq!(&buf[0], "XXX "); + buf.clear(); style.template = Template::from_str("{foo:.red.on_blue}"); - let rv = style.format_state(&state, width); - assert_eq!(&rv[0], "\u{1b}[31m\u{1b}[44mXXX\u{1b}[0m"); + style.format_state(&state, &mut buf, width); + assert_eq!(&buf[0], "\u{1b}[31m\u{1b}[44mXXX\u{1b}[0m"); + buf.clear(); style.template = Template::from_str("{foo:^5.red.on_blue}"); - let rv = style.format_state(&state, width); - assert_eq!(&rv[0], "\u{1b}[31m\u{1b}[44m XXX \u{1b}[0m"); + style.format_state(&state, &mut buf, width); + assert_eq!(&buf[0], "\u{1b}[31m\u{1b}[44m XXX \u{1b}[0m"); + buf.clear(); style.template = Template::from_str("{foo:^5.red.on_blue/green.on_cyan}"); - let rv = style.format_state(&state, width); - assert_eq!(&rv[0], "\u{1b}[31m\u{1b}[44m XXX \u{1b}[0m"); + style.format_state(&state, &mut buf, width); + assert_eq!(&buf[0], "\u{1b}[31m\u{1b}[44m XXX \u{1b}[0m"); } } From e6ef26d0c6d89846157e209f467c295c47a0ad67 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 2 Feb 2022 16:16:17 +0100 Subject: [PATCH 10/11] Make LeakyBucket type private --- src/draw_target.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/draw_target.rs b/src/draw_target.rs index 08d6df61..90974dbc 100644 --- a/src/draw_target.rs +++ b/src/draw_target.rs @@ -402,7 +402,7 @@ impl MultiProgressState { } #[derive(Debug)] -pub(crate) struct LeakyBucket { +struct LeakyBucket { leak_rate: f64, last_update: Instant, bucket: f64, From cafba210675a620c7ffedc6ab99e78a3da0cbe07 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 2 Feb 2022 16:26:57 +0100 Subject: [PATCH 11/11] Inline single-use function --- src/draw_target.rs | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/draw_target.rs b/src/draw_target.rs index 90974dbc..4de082e3 100644 --- a/src/draw_target.rs +++ b/src/draw_target.rs @@ -464,7 +464,16 @@ impl ProgressDrawState { if !self.lines.is_empty() && self.move_cursor { term.move_cursor_up(*last_line_count)?; } else { - clear_last_lines(term, *last_line_count)?; + // Fork of console::clear_last_lines that assumes that the last line doesn't contain a '\n' + let n = *last_line_count; + term.move_cursor_up(n.saturating_sub(1))?; + for i in 0..n { + term.clear_line()?; + if i + 1 != n { + term.move_cursor_down(1)?; + } + } + term.move_cursor_up(n.saturating_sub(1))?; } let shift = match self.alignment { @@ -533,16 +542,3 @@ impl Default for MultiProgressAlignment { Self::Top } } - -/// Fork of console::clear_last_lines that assumes that the last line doesn't contain a '\n' -fn clear_last_lines(term: &Term, n: usize) -> io::Result<()> { - term.move_cursor_up(n.saturating_sub(1))?; - for i in 0..n { - term.clear_line()?; - if i + 1 != n { - term.move_cursor_down(1)?; - } - } - term.move_cursor_up(n.saturating_sub(1))?; - Ok(()) -}