Mercurial > public > mercurial-scm > hg-stable
diff rust/hg-core/src/progress.rs @ 52066:92e23ba257d1
rust-hg-cpython: add an `HgProgressBar` util
This will be the entry point for all progress bars from a Python context
in upcoming patches. Like the `Progress` trait, this is subject to change
once we have more use cases, but this is good enough for now.
author | Rapha?l Gom?s <rgomes@octobus.net> |
---|---|
date | Mon, 30 Sep 2024 16:04:51 +0200 |
parents | 3ae7c43ad8aa |
children | a876ab6c3fd5 |
line wrap: on
line diff
--- a/rust/hg-core/src/progress.rs Mon Sep 30 16:02:30 2024 +0200 +++ b/rust/hg-core/src/progress.rs Mon Sep 30 16:04:51 2024 +0200 @@ -1,5 +1,12 @@ //! Progress-bar related things +use std::{ + sync::atomic::{AtomicBool, Ordering}, + time::Duration, +}; + +use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle}; + /// A generic determinate progress bar trait pub trait Progress: Send + Sync + 'static { /// Set the current position and optionally the total @@ -9,3 +16,77 @@ /// Declare that progress is over and the progress bar should be deleted fn complete(self); } + +const PROGRESS_DELAY: Duration = Duration::from_secs(1); + +/// A generic (determinate) progress bar. Stays hidden until [`PROGRESS_DELAY`] +/// to prevent flickering a progress bar for super fast operations. +pub struct HgProgressBar { + progress: ProgressBar, + has_been_shown: AtomicBool, +} + +impl HgProgressBar { + // TODO pass config to check progress.disable/assume-tty/delay/etc. + /// Return a new progress bar with `topic` as the prefix. + /// The progress and total are both set to 0, and it is hidden until the + /// next call to `update` given that more than a second has elapsed. + pub fn new(topic: &str) -> Self { + let template = + format!("{} {{wide_bar}} {{pos}}/{{len}} {{eta}} ", topic); + let style = ProgressStyle::with_template(&template).unwrap(); + let progress_bar = ProgressBar::new(0).with_style(style); + // Hide the progress bar and only show it if we've elapsed more + // than a second + progress_bar.set_draw_target(ProgressDrawTarget::hidden()); + Self { + progress: progress_bar, + has_been_shown: false.into(), + } + } + + /// Called whenever the progress changes to determine whether to start + /// showing the progress bar + fn maybe_show(&self) { + if self.progress.is_hidden() + && self.progress.elapsed() > PROGRESS_DELAY + { + // Catch a race condition whereby we check if it's hidden, then + // set the draw target from another thread, then do it again from + // this thread, which results in multiple progress bar lines being + // left drawn. + let has_been_shown = + self.has_been_shown.fetch_or(true, Ordering::Relaxed); + if !has_been_shown { + // Here we are certain that we're the only thread that has + // set `has_been_shown` and we can change the draw target + self.progress.set_draw_target(ProgressDrawTarget::stderr()); + self.progress.tick(); + } + } + } +} + +impl Progress for HgProgressBar { + fn update(&self, pos: u64, total: Option<u64>) { + self.progress.update(|state| { + state.set_pos(pos); + if let Some(t) = total { + state.set_len(t) + } + }); + self.maybe_show(); + } + + fn increment(&self, step: u64, total: Option<u64>) { + self.progress.inc(step); + if let Some(t) = total { + self.progress.set_length(t) + } + self.maybe_show(); + } + + fn complete(self) { + self.progress.finish_and_clear(); + } +}