From 9726e82507c34ee4b2a7e25f5e97820e6b600700 Mon Sep 17 00:00:00 2001 From: Tatsuyuki Ishi Date: Wed, 15 Dec 2021 21:24:51 +0900 Subject: [PATCH 1/3] Remove ProgressDrawState::is_finished to simplify code is_finished has always been ORed with force_draw, and removing it allows the logic to be simplified. --- src/draw_target.rs | 20 +++++--------------- src/progress_bar.rs | 4 +--- src/state.rs | 10 ++++++---- 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/draw_target.rs b/src/draw_target.rs index 8f475b5b..704026a4 100644 --- a/src/draw_target.rs +++ b/src/draw_target.rs @@ -152,7 +152,7 @@ impl ProgressDrawTarget { ref mut last_line_count, leaky_bucket: Some(ref mut leaky_bucket), } => { - if draw_state.finished || draw_state.force_draw || leaky_bucket.try_add_work() { + if draw_state.force_draw || leaky_bucket.try_add_work() { (term, last_line_count) } else { // rate limited @@ -205,8 +205,7 @@ impl ProgressDrawTarget { ProgressDrawState { lines: vec![], orphan_lines: 0, - finished: true, - force_draw: false, + force_draw: true, move_cursor: false, alignment: Default::default(), }, @@ -262,7 +261,7 @@ impl MultiProgressState { } pub(crate) fn draw(&mut self, idx: usize, draw_state: ProgressDrawState) -> io::Result<()> { - let force_draw = draw_state.finished || draw_state.force_draw; + let force_draw = draw_state.force_draw; let mut orphan_lines = vec![]; // Split orphan lines out of the draw state, if any @@ -301,18 +300,12 @@ impl MultiProgressState { } } - // !any(!done) is also true when iter() is empty, contrary to all(done) - let finished = !self - .draw_states - .iter() - .any(|x| !x.as_ref().map(|s| s.finished).unwrap_or(false)); 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, - finished, }) } @@ -376,8 +369,6 @@ pub(crate) struct ProgressDrawState { pub lines: Vec, /// The number of lines that shouldn't be reaped by the next tick. pub orphan_lines: usize, - /// True if the bar no longer needs drawing. - pub finished: bool, /// True if drawing should be forced. pub force_draw: bool, /// True if we should move the cursor up when possible instead of clearing lines. @@ -387,12 +378,11 @@ pub(crate) struct ProgressDrawState { } impl ProgressDrawState { - pub(crate) fn new(lines: Vec, finished: bool) -> Self { + pub(crate) fn new(lines: Vec, force_draw: bool) -> Self { Self { lines, orphan_lines: 0, - finished, - force_draw: false, + force_draw, move_cursor: false, alignment: Default::default(), } diff --git a/src/progress_bar.rs b/src/progress_bar.rs index 85f97b9f..207b229f 100644 --- a/src/progress_bar.rs +++ b/src/progress_bar.rs @@ -138,7 +138,7 @@ impl ProgressBar { } ms = state.steady_tick; - state.draw().ok(); + state.draw(false).ok(); } else { break; } @@ -248,7 +248,6 @@ impl ProgressBar { let draw_state = ProgressDrawState { lines, orphan_lines, - finished: state.is_finished(), force_draw: true, move_cursor: false, alignment: Default::default(), @@ -728,7 +727,6 @@ impl MultiProgress { state.draw_target.apply_draw_state(ProgressDrawState { lines: vec![], orphan_lines: 0, - finished: true, force_draw: true, move_cursor, alignment, diff --git a/src/state.rs b/src/state.rs index b51f0afc..53e8e59d 100644 --- a/src/state.rs +++ b/src/state.rs @@ -124,7 +124,7 @@ impl ProgressState { /// progress bar if the state has changed. pub fn update_and_draw(&mut self, f: F) { if self.update(f) { - self.draw().ok(); + self.draw(false).ok(); } } @@ -135,7 +135,7 @@ impl ProgressState { state.draw_next = state.pos; f(state); }); - self.draw().ok(); + self.draw(true).ok(); } /// Call the provided `FnOnce` to update the state. If a draw should be run, returns `true`. @@ -228,7 +228,7 @@ impl ProgressState { } } - pub(crate) fn draw(&mut self) -> io::Result<()> { + 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(()); @@ -239,7 +239,9 @@ impl ProgressState { false => Vec::new(), }; - let draw_state = ProgressDrawState::new(lines, self.is_finished()); + // `|| 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) } } From f9ebc70e7f0b32de9d8ce3e4dfab49b0b613d6d6 Mon Sep 17 00:00:00 2001 From: Tatsuyuki Ishi Date: Thu, 16 Dec 2021 15:00:54 +0900 Subject: [PATCH 2/3] Implement ProgressBar::suspend --- src/progress_bar.rs | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/progress_bar.rs b/src/progress_bar.rs index 207b229f..e29ed831 100644 --- a/src/progress_bar.rs +++ b/src/progress_bar.rs @@ -231,11 +231,14 @@ impl ProgressBar { /// Print a log line above the progress bar /// + /// If the progress bar is hidden (e.g. when standard output is not a terminal), `println()` + /// will not do anything. If you want to write to the standard output in such cases as well, use + /// [`suspend`] instead. + /// /// If the progress bar was added to a [`MultiProgress`], the log line will be /// printed above all other progress bars. /// - /// Note that if the progress bar is hidden (which by default happens if the progress bar is - /// redirected into a file) `println()` will not do anything either. + /// [`suspend`]: ProgressBar::suspend pub fn println>(&self, msg: I) { let mut state = self.state.lock().unwrap(); @@ -406,6 +409,30 @@ impl ProgressBar { state.draw_target = target; } + /// Hide the progress bar temporarily, execute `f`, then redraw the progress bar. + /// Useful for external code that writes to the standard output. + /// + /// **Note:** The internal lock is held while `f` is executed. Other threads trying to print + /// anything on the progress bar will be blocked until `f` finishes. + /// Therefore, it is recommended to not do long-running operations in `f`. + /// + /// ```rust,no_run + /// # use indicatif::ProgressBar; + /// let mut pb = ProgressBar::new(3); + /// pb.suspend(|| { + /// println!("Log message"); + /// }) + /// ``` + 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)); + let ret = f(); + let _ = state.draw(true); + ret + } + /// Wraps an [`Iterator`] with the progress bar /// /// ```rust,no_run From 3364cc45cc5b66dbee37c61c9bfa20ba775f2a5c Mon Sep 17 00:00:00 2001 From: Tatsuyuki Ishi Date: Fri, 17 Dec 2021 10:53:57 +0900 Subject: [PATCH 3/3] Allow creating an empty WeakProgressBar --- src/progress_bar.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/progress_bar.rs b/src/progress_bar.rs index e29ed831..38464e22 100644 --- a/src/progress_bar.rs +++ b/src/progress_bar.rs @@ -766,12 +766,19 @@ impl MultiProgress { /// A weak reference to a `ProgressBar`. /// /// Useful for creating custom steady tick implementations -#[derive(Clone)] +#[derive(Clone, Default)] pub struct WeakProgressBar { state: Weak>, } impl WeakProgressBar { + /// Create a new `WeakProgressBar` that returns `None` when [`upgrade`] is called. + /// + /// [`upgrade`]: WeakProgressBar::upgrade + pub fn new(&self) -> WeakProgressBar { + Default::default() + } + /// Attempts to upgrade the Weak pointer to a [`ProgressBar`], delaying dropping of the inner /// value if successful. Returns `None` if the inner value has since been dropped. ///