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

Implement ProgressBar::suspend #333

Merged
merged 3 commits into from Dec 20, 2021
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
20 changes: 5 additions & 15 deletions src/draw_target.rs
Expand Up @@ -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
Expand Down Expand Up @@ -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(),
},
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
})
}

Expand Down Expand Up @@ -376,8 +369,6 @@ pub(crate) struct ProgressDrawState {
pub lines: Vec<String>,
/// 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.
Expand All @@ -387,12 +378,11 @@ pub(crate) struct ProgressDrawState {
}

impl ProgressDrawState {
pub(crate) fn new(lines: Vec<String>, finished: bool) -> Self {
pub(crate) fn new(lines: Vec<String>, force_draw: bool) -> Self {
Self {
lines,
orphan_lines: 0,
finished,
force_draw: false,
force_draw,
move_cursor: false,
alignment: Default::default(),
}
Expand Down
44 changes: 38 additions & 6 deletions src/progress_bar.rs
Expand Up @@ -138,7 +138,7 @@ impl ProgressBar {
}
ms = state.steady_tick;

state.draw().ok();
state.draw(false).ok();
} else {
break;
}
Expand Down Expand Up @@ -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<I: AsRef<str>>(&self, msg: I) {
let mut state = self.state.lock().unwrap();

Expand All @@ -248,7 +251,6 @@ impl ProgressBar {
let draw_state = ProgressDrawState {
lines,
orphan_lines,
finished: state.is_finished(),
force_draw: true,
move_cursor: false,
alignment: Default::default(),
Expand Down Expand Up @@ -407,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<F: FnOnce() -> 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
Expand Down Expand Up @@ -728,7 +754,6 @@ impl MultiProgress {
state.draw_target.apply_draw_state(ProgressDrawState {
lines: vec![],
orphan_lines: 0,
finished: true,
force_draw: true,
move_cursor,
alignment,
Expand All @@ -741,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<Mutex<ProgressState>>,
}

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.
///
Expand Down
10 changes: 6 additions & 4 deletions src/state.rs
Expand Up @@ -124,7 +124,7 @@ impl ProgressState {
/// progress bar if the state has changed.
pub fn update_and_draw<F: FnOnce(&mut ProgressState)>(&mut self, f: F) {
if self.update(f) {
self.draw().ok();
self.draw(false).ok();
}
}

Expand All @@ -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`.
Expand Down Expand Up @@ -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(());
Expand All @@ -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)
}
}
Expand Down