diff --git a/src/draw_target.rs b/src/draw_target.rs index 3a7b2f17..4de082e3 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,56 +141,39 @@ 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 drawable(&mut self) -> Option> { + match &mut self.kind { ProgressDrawTargetKind::Term { - ref term, - ref mut last_line_count, - leaky_bucket: None, - } => (term, last_line_count), - ProgressDrawTargetKind::Term { - ref term, - ref mut last_line_count, - leaky_bucket: Some(ref mut leaky_bucket), + term, + last_line_count, + leaky_bucket, + draw_state, } => { - if draw_state.force_draw || leaky_bucket.try_add_work() { - (term, last_line_count) - } else { - // rate limited - return Ok(()); + let has_capacity = leaky_bucket + .as_mut() + .map(|b| b.try_add_work()) + .unwrap_or(true); + + match draw_state.force_draw || has_capacity { + true => Some(Drawable::Term { + term, + last_line_count, + draw_state, + }), + false => None, // 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, state, .. } => { + let state = state.write().unwrap(); + Some(Drawable::Multi { + idx: *idx, + state, + force_draw: false, + }) } // 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)?; + _ => None, } - - 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 @@ -197,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 => {} }; @@ -230,6 +212,7 @@ enum ProgressDrawTargetKind { term: Term, last_line_count: usize, leaky_bucket: Option, + draw_state: ProgressDrawState, }, Remote { state: Arc>, @@ -238,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 @@ -253,60 +326,59 @@ 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 { + 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(), + orphan_lines: Vec::new(), + } + } + fn width(&self) -> usize { self.draw_target.width() } - 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); - split.1.to_vec() - } else { - draw_state.lines - }; - - let draw_state = ProgressDrawState { - 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 = orphan_lines.len(); - lines.append(&mut orphan_lines); + let orphan_lines_count = self.orphan_lines.len(); + 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 { @@ -330,7 +402,7 @@ impl MultiProgressState { } #[derive(Debug)] -pub(crate) struct LeakyBucket { +struct LeakyBucket { leak_rate: f64, last_update: Instant, bucket: f64, @@ -388,7 +460,33 @@ 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 { + // 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 { + 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,8 +500,19 @@ 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(()) } + + 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. @@ -433,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(()) -} diff --git a/src/progress_bar.rs b/src/progress_bar.rs index 3f9f5708..aeac81b0 100644 --- a/src/progress_bar.rs +++ b/src/progress_bar.rs @@ -6,10 +6,8 @@ use std::sync::{Arc, Mutex, Weak}; use std::thread; use std::time::{Duration, Instant}; -use crate::draw_target::{ - MultiProgressAlignment, MultiProgressState, ProgressDrawState, ProgressDrawTarget, -}; -use crate::state::{ProgressState, Status}; +use crate::draw_target::{MultiProgressAlignment, MultiProgressState, ProgressDrawTarget}; +use crate::state::{BarState, ProgressState, Status}; use crate::style::ProgressStyle; use crate::{ProgressBarIter, ProgressIterator}; @@ -19,7 +17,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 +45,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 +97,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 +110,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 +172,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 +193,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 +228,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 @@ -240,23 +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.should_render() && !state.draw_target.is_hidden() { - lines.extend(state.style.format_state(&*state)); + 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 { + state + .style + .format_state(state, &mut draw_state.lines, 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 @@ -426,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 @@ -544,32 +564,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() } } @@ -598,14 +618,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))), } } @@ -750,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() } } @@ -769,7 +786,7 @@ impl MultiProgress { /// Useful for creating custom steady tick implementations #[derive(Clone, Default)] pub struct WeakProgressBar { - state: Weak>, + state: Weak>, } impl WeakProgressBar { @@ -805,14 +822,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 53e8e59d..2d0cf6e9 100644 --- a/src/state.rs +++ b/src/state.rs @@ -4,9 +4,144 @@ 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 { + 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 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. + draw_state.force_draw = force_draw || self.state.is_finished(); + + if self.state.should_render() { + self.state + .style + .format_state(&self.state, &mut draw_state.lines, width); + } + + drop(draw_state); + drawable.draw() + } +} + +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 +149,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 +161,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, @@ -88,11 +221,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() { @@ -120,24 +248,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; @@ -157,104 +267,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), - 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 20219641..9caf99ca 100644 --- a/src/style.rs +++ b/src/style.rs @@ -227,10 +227,14 @@ impl ProgressStyle { } } - pub(crate) fn format_state(&self, state: &ProgressState) -> 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,20 +363,23 @@ 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), + TemplatePart::NewLine => lines.push(match wide { + Some(inner) => { + inner.expand(mem::take(&mut cur), self, state, &mut buf, target_width) + } None => mem::take(&mut cur), }), } } if !cur.is_empty() { - rv.push(match wide { - Some(inner) => inner.expand(mem::take(&mut cur), self, state, &mut buf), + lines.push(match wide { + Some(inner) => { + inner.expand(mem::take(&mut cur), self, state, &mut buf, target_width) + } None => mem::take(&mut cur), }) } - rv } } @@ -389,10 +396,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", @@ -710,42 +716,55 @@ mod tests { #[test] fn test_expand_template() { + 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()); - let state = ProgressState::new(10, ProgressDrawTarget::stdout()); style.template = Template::from_str("{{ {foo} {bar} }}"); - let rv = style.format_state(&state); - 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); - 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 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()); - let state = ProgressState::new(10, ProgressDrawTarget::stdout()); style.template = Template::from_str("{foo:5}"); - let rv = style.format_state(&state); - 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); - 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); - 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); - 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"); } }