# HG changeset patch # User Raphaël Gomès # Date 1727705091 -7200 # Node ID 92e23ba257d1c7ec4c7a3df1fef73244291e1613 # Parent 3ae7c43ad8aa5688da36fe6f849b2ad2a79db9f4 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. diff -r 3ae7c43ad8aa -r 92e23ba257d1 rust/Cargo.lock --- a/rust/Cargo.lock Mon Sep 30 16:02:30 2024 +0200 +++ b/rust/Cargo.lock Mon Sep 30 16:04:51 2024 +0200 @@ -231,6 +231,19 @@ ] [[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.52.0", +] + +[[package]] name = "convert_case" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -451,6 +464,12 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] name = "env_logger" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -609,6 +628,7 @@ "hashbrown 0.13.1", "home", "im-rc", + "indicatif", "itertools", "lazy_static", "libc", @@ -711,6 +731,19 @@ ] [[package]] +name = "indicatif" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "unicode-width", +] + +[[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -890,6 +923,12 @@ ] [[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] name = "once_cell" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -938,6 +977,12 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + +[[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1646,6 +1691,15 @@ [[package]] name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" diff -r 3ae7c43ad8aa -r 92e23ba257d1 rust/hg-core/Cargo.toml --- a/rust/hg-core/Cargo.toml Mon Sep 30 16:02:30 2024 +0200 +++ b/rust/hg-core/Cargo.toml Mon Sep 30 16:04:51 2024 +0200 @@ -16,6 +16,7 @@ hashbrown = { version = "0.13.1", features = ["rayon"] } home = "0.5.4" im-rc = "15.1.0" +indicatif = "0.17.8" itertools = "0.10.5" lazy_static = "1.4.0" libc = "0.2.137" diff -r 3ae7c43ad8aa -r 92e23ba257d1 rust/hg-core/src/progress.rs --- 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) { + 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) { + 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(); + } +}