diff --git a/src/commands.rs b/src/commands.rs index f0e97342c..b24552050 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -58,6 +58,7 @@ use crate::cli_util::{ use crate::commands::CommandError::UserError; use crate::formatter::Formatter; use crate::graphlog::{AsciiGraphDrawer, Edge}; +use crate::progress::Progress; use crate::template_parser::TemplateParser; use crate::templater::Template; use crate::ui::Ui; @@ -4156,43 +4157,9 @@ fn git_fetch( ) -> Result, GitFetchError> { let mut callback = None; if ui.use_progress_indicator() { - let mut rate = RateEstimate::new(); - - struct Guard<'a> { - ui: &'a mut Ui, - printed: bool, - } - impl Drop for Guard<'_> { - fn drop(&mut self) { - if self.printed { - let _ = writeln!(self.ui); - } - } - } - let mut guard = Guard { ui, printed: false }; - - let mut buffer = String::new(); - callback = Some(move |progress: &git::Progress| { - use std::fmt::Write; - - const CLEAR_TRAILING: &str = "\x1b[K"; - buffer.clear(); - write!( - buffer, - "\r{}{: >3.0}%", - CLEAR_TRAILING, - 100.0 * progress.overall - ) - .unwrap(); - if let Some(estimate) = progress - .bytes_downloaded - .and_then(|x| rate.update(Instant::now(), x)) - { - let (scaled, prefix) = binary_prefix(estimate); - write!(buffer, " at {: >5.1} {}B/s", scaled, prefix).unwrap(); - guard.printed = true; - } - _ = write!(guard.ui, "{}", buffer); + let mut progress = Progress::new(ui); + callback = Some(move |x: &git::Progress| { + progress.update(Instant::now(), x); }); } let result = git::fetch( @@ -4206,71 +4173,6 @@ fn git_fetch( result } -/// Find the smallest binary prefix with which the whole part of `x` is at most -/// three digits, and return the scaled `x` and that prefix. -fn binary_prefix(x: f32) -> (f32, &'static str) { - const TABLE: [&str; 9] = ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"]; - - let mut i = 0; - let mut scaled = x; - while scaled.abs() >= 1000.0 && i < TABLE.len() - 1 { - i += 1; - scaled /= 1024.0; - } - (scaled, TABLE[i]) -} - -struct RateEstimate { - state: Option, -} - -impl RateEstimate { - fn new() -> Self { - RateEstimate { state: None } - } - - /// Compute smoothed rate from an update - fn update(&mut self, now: Instant, total: u64) -> Option { - if let Some(ref mut state) = self.state { - return Some(state.update(now, total)); - } - - self.state = Some(RateEstimateState { - total, - avg_rate: None, - last_sample: now, - }); - None - } -} - -struct RateEstimateState { - total: u64, - avg_rate: Option, - last_sample: Instant, -} - -impl RateEstimateState { - fn update(&mut self, now: Instant, total: u64) -> f32 { - let delta = total - self.total; - self.total = total; - let dt = now - self.last_sample; - self.last_sample = now; - let sample = delta as f32 / dt.as_secs_f32(); - match self.avg_rate { - None => *self.avg_rate.insert(sample), - Some(ref mut avg_rate) => { - // From Algorithms for Unevenly Spaced Time Series: Moving - // Averages and Other Rolling Operators (Andreas Eckner, 2019) - const TIME_WINDOW: f32 = 2.0; - let alpha = 1.0 - (-dt.as_secs_f32() / TIME_WINDOW).exp(); - *avg_rate += alpha * (sample - *avg_rate); - *avg_rate - } - } - } -} - fn cmd_git_push( ui: &mut Ui, command: &CommandHelper, diff --git a/src/lib.rs b/src/lib.rs index 0a1d46845..2a9c94b4a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,7 @@ pub mod config; pub mod diff_edit; pub mod formatter; pub mod graphlog; +mod progress; pub mod template_parser; pub mod templater; pub mod ui; diff --git a/src/progress.rs b/src/progress.rs new file mode 100644 index 000000000..4ab9feafd --- /dev/null +++ b/src/progress.rs @@ -0,0 +1,119 @@ +use std::time::Instant; + +use jujutsu_lib::git; + +use crate::ui::Ui; + +pub struct Progress<'a> { + ui: &'a mut Ui, + rate: RateEstimate, + buffer: String, + printed: bool, +} + +impl<'a> Progress<'a> { + pub fn new(ui: &'a mut Ui) -> Self { + Self { + ui, + rate: RateEstimate::new(), + buffer: String::new(), + printed: false, + } + } + + pub fn update(&mut self, now: Instant, progress: &git::Progress) { + use std::fmt::Write as _; + + const CLEAR_TRAILING: &str = "\x1b[K"; + self.buffer.clear(); + write!( + self.buffer, + "\r{}{: >3.0}%", + CLEAR_TRAILING, + 100.0 * progress.overall + ) + .unwrap(); + if let Some(estimate) = progress + .bytes_downloaded + .and_then(|x| self.rate.update(now, x)) + { + let (scaled, prefix) = binary_prefix(estimate); + write!(self.buffer, " at {: >5.1} {}B/s", scaled, prefix).unwrap(); + } + _ = write!(self.ui, "{}", self.buffer); + self.printed = true; + } +} + +impl Drop for Progress<'_> { + fn drop(&mut self) { + if self.printed { + let _ = writeln!(self.ui); + } + } +} + +/// Find the smallest binary prefix with which the whole part of `x` is at most +/// three digits, and return the scaled `x` and that prefix. +fn binary_prefix(x: f32) -> (f32, &'static str) { + const TABLE: [&str; 9] = ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"]; + + let mut i = 0; + let mut scaled = x; + while scaled.abs() >= 1000.0 && i < TABLE.len() - 1 { + i += 1; + scaled /= 1024.0; + } + (scaled, TABLE[i]) +} + +struct RateEstimate { + state: Option, +} + +impl RateEstimate { + fn new() -> Self { + RateEstimate { state: None } + } + + /// Compute smoothed rate from an update + fn update(&mut self, now: Instant, total: u64) -> Option { + if let Some(ref mut state) = self.state { + return Some(state.update(now, total)); + } + + self.state = Some(RateEstimateState { + total, + avg_rate: None, + last_sample: now, + }); + None + } +} + +struct RateEstimateState { + total: u64, + avg_rate: Option, + last_sample: Instant, +} + +impl RateEstimateState { + fn update(&mut self, now: Instant, total: u64) -> f32 { + let delta = total - self.total; + self.total = total; + let dt = now - self.last_sample; + self.last_sample = now; + let sample = delta as f32 / dt.as_secs_f32(); + match self.avg_rate { + None => *self.avg_rate.insert(sample), + Some(ref mut avg_rate) => { + // From Algorithms for Unevenly Spaced Time Series: Moving + // Averages and Other Rolling Operators (Andreas Eckner, 2019) + const TIME_WINDOW: f32 = 2.0; + let alpha = 1.0 - (-dt.as_secs_f32() / TIME_WINDOW).exp(); + *avg_rate += alpha * (sample - *avg_rate); + *avg_rate + } + } + } +}