diff options
Diffstat (limited to 'vendor/indicatif/src/draw_target.rs')
-rw-r--r-- | vendor/indicatif/src/draw_target.rs | 561 |
1 files changed, 0 insertions, 561 deletions
diff --git a/vendor/indicatif/src/draw_target.rs b/vendor/indicatif/src/draw_target.rs deleted file mode 100644 index a2c055f..0000000 --- a/vendor/indicatif/src/draw_target.rs +++ /dev/null @@ -1,561 +0,0 @@ -use std::io; -use std::sync::{Arc, RwLock, RwLockWriteGuard}; -use std::thread::panicking; -use std::time::Duration; -#[cfg(not(target_arch = "wasm32"))] -use std::time::Instant; - -use console::Term; -#[cfg(target_arch = "wasm32")] -use instant::Instant; - -use crate::multi::{MultiProgressAlignment, MultiState}; -use crate::TermLike; - -/// Target for draw operations -/// -/// This tells a progress bar or a multi progress object where to paint to. -/// The draw target is a stateful wrapper over a drawing destination and -/// internally optimizes how often the state is painted to the output -/// device. -#[derive(Debug)] -pub struct ProgressDrawTarget { - kind: TargetKind, -} - -impl ProgressDrawTarget { - /// Draw to a buffered stdout terminal at a max of 20 times a second. - /// - /// For more information see [`ProgressDrawTarget::term`]. - pub fn stdout() -> Self { - Self::term(Term::buffered_stdout(), 20) - } - - /// Draw to a buffered stderr terminal at a max of 20 times a second. - /// - /// This is the default draw target for progress bars. For more - /// information see [`ProgressDrawTarget::term`]. - pub fn stderr() -> Self { - Self::term(Term::buffered_stderr(), 20) - } - - /// Draw to a buffered stdout terminal at a max of `refresh_rate` times a second. - /// - /// For more information see [`ProgressDrawTarget::term`]. - pub fn stdout_with_hz(refresh_rate: u8) -> Self { - Self::term(Term::buffered_stdout(), refresh_rate) - } - - /// Draw to a buffered stderr terminal at a max of `refresh_rate` times a second. - /// - /// For more information see [`ProgressDrawTarget::term`]. - pub fn stderr_with_hz(refresh_rate: u8) -> Self { - Self::term(Term::buffered_stderr(), refresh_rate) - } - - pub(crate) fn new_remote(state: Arc<RwLock<MultiState>>, idx: usize) -> Self { - Self { - kind: TargetKind::Multi { state, idx }, - } - } - - /// Draw to a terminal, with a specific refresh rate. - /// - /// Progress bars are by default drawn to terminals however if the - /// terminal is not user attended the entire progress bar will be - /// hidden. This is done so that piping to a file will not produce - /// useless escape codes in that file. - /// - /// Will panic if refresh_rate is `0`. - pub fn term(term: Term, refresh_rate: u8) -> Self { - Self { - kind: TargetKind::Term { - term, - last_line_count: 0, - rate_limiter: RateLimiter::new(refresh_rate), - draw_state: DrawState::default(), - }, - } - } - - /// Draw to a boxed object that implements the [`TermLike`] trait. - pub fn term_like(term_like: Box<dyn TermLike>) -> Self { - Self { - kind: TargetKind::TermLike { - inner: term_like, - last_line_count: 0, - rate_limiter: None, - draw_state: DrawState::default(), - }, - } - } - - /// Draw to a boxed object that implements the [`TermLike`] trait, - /// with a specific refresh rate. - pub fn term_like_with_hz(term_like: Box<dyn TermLike>, refresh_rate: u8) -> Self { - Self { - kind: TargetKind::TermLike { - inner: term_like, - last_line_count: 0, - rate_limiter: Option::from(RateLimiter::new(refresh_rate)), - draw_state: DrawState::default(), - }, - } - } - - /// A hidden draw target. - /// - /// This forces a progress bar to be not rendered at all. - pub fn hidden() -> Self { - Self { - kind: TargetKind::Hidden, - } - } - - /// Returns true if the draw target is hidden. - /// - /// This is internally used in progress bars to figure out if overhead - /// from drawing can be prevented. - pub fn is_hidden(&self) -> bool { - match self.kind { - TargetKind::Hidden => true, - TargetKind::Term { ref term, .. } => !term.is_term(), - TargetKind::Multi { ref state, .. } => state.read().unwrap().is_hidden(), - _ => false, - } - } - - /// Returns the current width of the draw target. - pub(crate) fn width(&self) -> u16 { - match self.kind { - TargetKind::Term { ref term, .. } => term.size().1, - TargetKind::Multi { ref state, .. } => state.read().unwrap().width(), - TargetKind::Hidden => 0, - TargetKind::TermLike { ref inner, .. } => inner.width(), - } - } - - /// Notifies the backing `MultiProgress` (if applicable) that the associated progress bar should - /// be marked a zombie. - pub(crate) fn mark_zombie(&self) { - if let TargetKind::Multi { idx, state } = &self.kind { - state.write().unwrap().mark_zombie(*idx); - } - } - - /// Apply the given draw state (draws it). - pub(crate) fn drawable(&mut self, force_draw: bool, now: Instant) -> Option<Drawable<'_>> { - match &mut self.kind { - TargetKind::Term { - term, - last_line_count, - rate_limiter, - draw_state, - } => { - if !term.is_term() { - return None; - } - - match force_draw || rate_limiter.allow(now) { - true => Some(Drawable::Term { - term, - last_line_count, - draw_state, - }), - false => None, // rate limited - } - } - TargetKind::Multi { idx, state, .. } => { - let state = state.write().unwrap(); - Some(Drawable::Multi { - idx: *idx, - state, - force_draw, - now, - }) - } - TargetKind::TermLike { - inner, - last_line_count, - rate_limiter, - draw_state, - } => match force_draw || rate_limiter.as_mut().map_or(true, |r| r.allow(now)) { - true => Some(Drawable::TermLike { - term_like: &**inner, - last_line_count, - draw_state, - }), - false => None, // rate limited - }, - // Hidden, finished, or no need to refresh yet - _ => None, - } - } - - /// Properly disconnects from the draw target - pub(crate) fn disconnect(&self, now: Instant) { - match self.kind { - TargetKind::Term { .. } => {} - TargetKind::Multi { idx, ref state, .. } => { - let state = state.write().unwrap(); - let _ = Drawable::Multi { - state, - idx, - force_draw: true, - now, - } - .clear(); - } - TargetKind::Hidden => {} - TargetKind::TermLike { .. } => {} - }; - } - - pub(crate) fn remote(&self) -> Option<(&Arc<RwLock<MultiState>>, usize)> { - match &self.kind { - TargetKind::Multi { state, idx } => Some((state, *idx)), - _ => None, - } - } - - pub(crate) fn adjust_last_line_count(&mut self, adjust: LineAdjust) { - self.kind.adjust_last_line_count(adjust); - } -} - -#[derive(Debug)] -enum TargetKind { - Term { - term: Term, - last_line_count: usize, - rate_limiter: RateLimiter, - draw_state: DrawState, - }, - Multi { - state: Arc<RwLock<MultiState>>, - idx: usize, - }, - Hidden, - TermLike { - inner: Box<dyn TermLike>, - last_line_count: usize, - rate_limiter: Option<RateLimiter>, - draw_state: DrawState, - }, -} - -impl TargetKind { - /// Adjust `last_line_count` such that the next draw operation keeps/clears additional lines - fn adjust_last_line_count(&mut self, adjust: LineAdjust) { - let last_line_count: &mut usize = match self { - Self::Term { - last_line_count, .. - } => last_line_count, - Self::TermLike { - last_line_count, .. - } => last_line_count, - _ => return, - }; - - match adjust { - LineAdjust::Clear(count) => *last_line_count = last_line_count.saturating_add(count), - LineAdjust::Keep(count) => *last_line_count = last_line_count.saturating_sub(count), - } - } -} - -pub(crate) enum Drawable<'a> { - Term { - term: &'a Term, - last_line_count: &'a mut usize, - draw_state: &'a mut DrawState, - }, - Multi { - state: RwLockWriteGuard<'a, MultiState>, - idx: usize, - force_draw: bool, - now: Instant, - }, - TermLike { - term_like: &'a dyn TermLike, - last_line_count: &'a mut usize, - draw_state: &'a mut DrawState, - }, -} - -impl<'a> Drawable<'a> { - /// Adjust `last_line_count` such that the next draw operation keeps/clears additional lines - pub(crate) fn adjust_last_line_count(&mut self, adjust: LineAdjust) { - let last_line_count: &mut usize = match self { - Drawable::Term { - last_line_count, .. - } => last_line_count, - Drawable::TermLike { - last_line_count, .. - } => last_line_count, - _ => return, - }; - - match adjust { - LineAdjust::Clear(count) => *last_line_count = last_line_count.saturating_add(count), - LineAdjust::Keep(count) => *last_line_count = last_line_count.saturating_sub(count), - } - } - - pub(crate) fn state(&mut self) -> DrawStateWrapper<'_> { - let mut state = match self { - Drawable::Term { draw_state, .. } => DrawStateWrapper::for_term(draw_state), - Drawable::Multi { state, idx, .. } => state.draw_state(*idx), - Drawable::TermLike { draw_state, .. } => DrawStateWrapper::for_term(draw_state), - }; - - state.reset(); - state - } - - pub(crate) fn clear(mut self) -> io::Result<()> { - let state = self.state(); - drop(state); - self.draw() - } - - 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, - now, - .. - } => state.draw(force_draw, None, now), - Drawable::TermLike { - term_like, - last_line_count, - draw_state, - } => draw_state.draw_to_term(term_like, last_line_count), - } - } -} - -pub(crate) enum LineAdjust { - /// Adds to `last_line_count` so that the next draw also clears those lines - Clear(usize), - /// Subtracts from `last_line_count` so that the next draw retains those lines - Keep(usize), -} - -pub(crate) struct DrawStateWrapper<'a> { - state: &'a mut DrawState, - orphan_lines: Option<&'a mut Vec<String>>, -} - -impl<'a> DrawStateWrapper<'a> { - pub(crate) fn for_term(state: &'a mut DrawState) -> Self { - Self { - state, - orphan_lines: None, - } - } - - pub(crate) fn for_multi(state: &'a mut DrawState, orphan_lines: &'a mut Vec<String>) -> Self { - Self { - state, - orphan_lines: Some(orphan_lines), - } - } -} - -impl std::ops::Deref for DrawStateWrapper<'_> { - type Target = DrawState; - - 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(orphaned) = &mut self.orphan_lines { - orphaned.extend(self.state.lines.drain(..self.state.orphan_lines_count)); - self.state.orphan_lines_count = 0; - } - } -} - -#[derive(Debug)] -struct RateLimiter { - interval: u16, // in milliseconds - capacity: u8, - prev: Instant, -} - -/// Rate limit but allow occasional bursts above desired rate -impl RateLimiter { - fn new(rate: u8) -> Self { - Self { - interval: 1000 / (rate as u16), // between 3 and 1000 milliseconds - capacity: MAX_BURST, - prev: Instant::now(), - } - } - - fn allow(&mut self, now: Instant) -> bool { - if now < self.prev { - return false; - } - - let elapsed = now - self.prev; - // If `capacity` is 0 and not enough time (`self.interval` ms) has passed since - // `self.prev` to add new capacity, return `false`. The goal of this method is to - // make this decision as efficient as possible. - if self.capacity == 0 && elapsed < Duration::from_millis(self.interval as u64) { - return false; - } - - // We now calculate `new`, the number of ms, since we last returned `true`, - // and `remainder`, which represents a number of ns less than 1ms which we cannot - // convert into capacity now, so we're saving it for later. - let (new, remainder) = ( - elapsed.as_millis() / self.interval as u128, - elapsed.as_nanos() % (self.interval as u128 * 1_000_000), - ); - - // We add `new` to `capacity`, subtract one for returning `true` from here, - // then make sure it does not exceed a maximum of `MAX_BURST`, then store it. - self.capacity = Ord::min(MAX_BURST as u128, (self.capacity as u128) + new - 1) as u8; - // Store `prev` for the next iteration after subtracting the `remainder`. - // Just use `unwrap` here because it shouldn't be possible for this to underflow. - self.prev = now - .checked_sub(Duration::from_nanos(remainder as u64)) - .unwrap(); - true - } -} - -const MAX_BURST: u8 = 20; - -/// The drawn state of an element. -#[derive(Clone, Debug, Default)] -pub(crate) struct DrawState { - /// The lines to print (can contain ANSI codes) - pub(crate) lines: Vec<String>, - /// The number of lines that shouldn't be reaped by the next tick. - pub(crate) orphan_lines_count: usize, - /// True if we should move the cursor up when possible instead of clearing lines. - 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, -} - -impl DrawState { - fn draw_to_term( - &mut self, - term: &(impl TermLike + ?Sized), - last_line_count: &mut usize, - ) -> io::Result<()> { - if panicking() { - return Ok(()); - } - - 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 term_height = term.height() as usize; - let term_width = term.width() as usize; - let len = self.lines.len(); - let mut real_len = 0; - let mut last_line_filler = 0; - debug_assert!(self.orphan_lines_count <= self.lines.len()); - for (idx, line) in self.lines.iter().enumerate() { - let line_width = console::measure_text_width(line); - let diff = if line.is_empty() { - // Empty line are new line - 1 - } else { - // Calculate real length based on terminal width - // This take in account linewrap from terminal - let terminal_len = (line_width as f64 / term_width as f64).ceil() as usize; - - // If the line is effectively empty (for example when it consists - // solely of ANSI color code sequences, count it the same as a - // new line. If the line is measured to be len = 0, we will - // subtract with overflow later. - usize::max(terminal_len, 1) - }; - // Don't consider orphan lines when comparing to terminal height. - debug_assert!(idx <= real_len); - if self.orphan_lines_count <= idx - && real_len - self.orphan_lines_count + diff > term_height - { - break; - } - real_len += diff; - if idx != 0 { - term.write_line("")?; - } - term.write_str(line)?; - if idx + 1 == len { - // Keep the cursor on the right terminal side - // So that next user writes/prints will happen on the next line - last_line_filler = term_width.saturating_sub(line_width); - } - } - term.write_str(&" ".repeat(last_line_filler))?; - - term.flush()?; - *last_line_count = real_len - self.orphan_lines_count + shift; - Ok(()) - } - - fn reset(&mut self) { - self.lines.clear(); - self.orphan_lines_count = 0; - } -} - -#[cfg(test)] -mod tests { - use crate::{MultiProgress, ProgressBar, ProgressDrawTarget}; - - #[test] - fn multi_is_hidden() { - let mp = MultiProgress::with_draw_target(ProgressDrawTarget::hidden()); - - let pb = mp.add(ProgressBar::new(100)); - assert!(mp.is_hidden()); - assert!(pb.is_hidden()); - } -} |