112 lines · 3.8 KB · Rust
RawDownload
// Copyright 2022-2026 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. use std::io;use std::path::Path;use std::sync::Mutex;use std::time::Duration;use std::time::Instant; use crossterm::terminal::Clear;use crossterm::terminal::ClearType;use jj_lib::repo_path::RepoPath; use crate::text_util;use crate::ui::OutputGuard;use crate::ui::ProgressOutput;use crate::ui::Ui; pub const UPDATE_HZ: u32 = 30;pub const INITIAL_DELAY: Duration = Duration::from_millis(250); pub struct ProgressWriter<'a> {    prefix: &'a str,    guard: Option<OutputGuard>,    output: ProgressOutput<io::Stderr>,    next_display_time: Instant,} // Future work: Make that the progress prints the current element we are// currently working on, either:// - upon change of the element we are working on and if the next display time//   is passed (current behavior)// - upon reaching the next display time for an element that would have not been//   displayed yet. This// would assure two things:// - The first message will end-up being displayed if it takes more than the//   initial delay to process the associated element. Assuring jj never goes//   silent for more than the specified initial delay.// - For the other elements, what we print is more factual regarding what we are//   doing. Without printing too much.//// Note that the first message printing by itself, would not be suitable for the// commit signing progress as, on some configuration the user is prompted to// enter its password to unlock the key used to sign, then the message would be// conflicting with that message from another process. impl<'a> ProgressWriter<'a> {    pub fn new(ui: &Ui, prefix: &'a str) -> Option<Self> {        let output = ui.progress_output()?;         // Don't clutter the output during fast operations.        let next_display_time = Instant::now() + INITIAL_DELAY;        Some(Self {            prefix,            guard: None,            output,            next_display_time,        })    }     pub fn display(&mut self, text: &str) -> io::Result<()> {        let now = Instant::now();        if now < self.next_display_time {            return Ok(());        }         self.next_display_time = now + Duration::from_secs(1) / UPDATE_HZ;         if self.guard.is_none() {            self.guard = Some(                self.output                    .output_guard(format!("\r{}", Clear(ClearType::CurrentLine))),            );        }         let line_width = self.output.term_width().map(usize::from).unwrap_or(80);        let max_path_width = self.prefix.len() + 1; // Take into account the empty space added after the prefix.        let (display_text, _) =            text_util::elide_start(text, "...", line_width.saturating_sub(max_path_width));         write!(            self.output,            "\r{}{} {display_text}",            Clear(ClearType::CurrentLine),            self.prefix        )?;        self.output.flush()    }} pub fn snapshot_progress(ui: &Ui) -> Option<impl Fn(&RepoPath) + use<>> {    let writer = Mutex::new(ProgressWriter::new(ui, "Snapshotting")?);     Some(move |path: &RepoPath| {        writer            .lock()            .unwrap()            .display(path.to_fs_path_unchecked(Path::new("")).to_str().unwrap())            .ok();    })}