From 256d6bbc7bd911a59f9689de1fc528fa8bb033ec Mon Sep 17 00:00:00 2001 From: plum Date: Thu, 4 Jun 2026 14:54:19 -0400 Subject: [PATCH] Improve init progress output --- src/app/mod.rs | 49 +++++++++++++++++++++- src/main.rs | 109 ++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 151 insertions(+), 7 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 0637fcf..eac8a20 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -20,19 +20,64 @@ pub struct InitReport { pub index: IndexReport, } +pub trait InitProgress { + fn paths_started(&mut self) {} + fn paths_ready(&mut self, _config_dir: &Path, _data_dir: &Path, _cache_dir: &Path) {} + fn config_started(&mut self, _path: &Path) {} + fn config_ready(&mut self, _path: &Path, _created: bool) {} + fn database_started(&mut self, _path: &Path) {} + fn database_ready(&mut self, _path: &Path, _created: bool) {} + fn model_started(&mut self, _cache_dir: &Path) {} + fn model_ready(&mut self, _cache_dir: &Path) {} + fn index_started(&mut self, _roots: &[String]) {} +} + +#[derive(Debug, Default)] +pub struct NoopInitProgress; + +impl InitProgress for NoopInitProgress {} + pub async fn init() -> Result { - let mut progress = NoopProgress; - init_with_progress(&mut progress).await + let mut index_progress = NoopProgress; + let mut init_progress = NoopInitProgress; + init_with_progress_and_steps(&mut index_progress, &mut init_progress).await } pub async fn init_with_progress

(progress: &mut P) -> Result where P: IndexProgress + ?Sized, { + let mut init_progress = NoopInitProgress; + init_with_progress_and_steps(progress, &mut init_progress).await +} + +pub async fn init_with_progress_and_steps( + progress: &mut P, + init_progress: &mut I, +) -> Result +where + P: IndexProgress + ?Sized, + I: InitProgress + ?Sized, +{ + init_progress.paths_started(); let paths = AppPaths::discover()?; + init_progress.paths_ready(&paths.config_dir, &paths.data_dir, &paths.cache_dir); + + init_progress.config_started(&paths.config_file); + let config_created = !paths.config_file.exists(); let settings = Settings::load_or_create(&paths.config_file)?; + init_progress.config_ready(&paths.config_file, config_created); + + init_progress.database_started(&paths.database_file); + let database_created = !paths.database_file.exists(); let database = Database::open(&paths.database_file).await?; + init_progress.database_ready(&paths.database_file, database_created); + + init_progress.model_started(&paths.cache_dir); let embedder = RuntimeEmbedder::load(&paths)?; + init_progress.model_ready(&paths.cache_dir); + + init_progress.index_started(&settings.index.roots); let indexer = Indexer::new(&settings, &database, &embedder); let index = indexer .index_configured_roots_with_progress(progress) diff --git a/src/main.rs b/src/main.rs index a3b50a8..4ada87b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -65,12 +65,12 @@ async fn run(invocation: Invocation) -> error::Result<()> { } Invocation::ShellInit { shell } => write_stdout(shell_init(shell).as_bytes())?, Invocation::Init => { + let mut init_progress = TerminalInitProgress::start(); let mut progress = TerminalIndexProgress::start(); - let report = app::init_with_progress(&mut progress).await?; + let report = + app::init_with_progress_and_steps(&mut progress, &mut init_progress).await?; progress.finish(); - println!("config ready: {}", report.config_file.display()); - println!("database ready: {}", report.database_file.display()); - println!("{}", report.index.human_summary()); + init_progress.finish(&report); } Invocation::Index { roots } => { let mut progress = TerminalIndexProgress::start(); @@ -122,6 +122,98 @@ fn confirm_reset() -> error::Result { )) } +struct TerminalInitProgress { + step: usize, +} + +impl TerminalInitProgress { + const STEP_COUNT: usize = 5; + + fn start() -> Self { + println!("cds init"); + flush_stdout(); + Self { step: 0 } + } + + fn begin(&mut self, label: &str) { + self.step += 1; + println!("[{}/{}] {label}", self.step, Self::STEP_COUNT); + flush_stdout(); + } + + fn detail(label: &str, value: impl std::fmt::Display) { + println!(" {label}: {value}"); + flush_stdout(); + } + + fn status(label: &str, path: &Path, created: bool) { + let action = if created { "created" } else { "ready" }; + Self::detail(label, format_args!("{action} {}", path.display())); + } + + fn finish(&mut self, report: &app::InitReport) { + Self::detail("index", report.index.human_summary()); + Self::detail("config", report.config_file.display()); + Self::detail("database", report.database_file.display()); + println!("cds init complete"); + flush_stdout(); + } +} + +impl app::InitProgress for TerminalInitProgress { + fn paths_started(&mut self) { + self.begin("Resolve cds directories"); + } + + fn paths_ready(&mut self, config_dir: &Path, data_dir: &Path, cache_dir: &Path) { + Self::detail("config dir", config_dir.display()); + Self::detail("data dir", data_dir.display()); + Self::detail("cache dir", cache_dir.display()); + } + + fn config_started(&mut self, path: &Path) { + self.begin("Prepare config"); + Self::detail("path", path.display()); + } + + fn config_ready(&mut self, path: &Path, created: bool) { + Self::status("config", path, created); + } + + fn database_started(&mut self, path: &Path) { + self.begin("Prepare database"); + Self::detail("path", path.display()); + } + + fn database_ready(&mut self, path: &Path, created: bool) { + Self::status("database", path, created); + } + + fn model_started(&mut self, cache_dir: &Path) { + self.begin("Load embedding model"); + Self::detail("model", "BAAI/bge-small-en-v1.5"); + Self::detail("cache", cache_dir.join("models").display()); + } + + fn model_ready(&mut self, _cache_dir: &Path) { + Self::detail("model", "ready"); + } + + fn index_started(&mut self, roots: &[String]) { + self.begin("Index configured roots"); + let roots = if roots.is_empty() { + "".to_string() + } else { + roots.join(", ") + }; + Self::detail("roots", roots); + } +} + +fn flush_stdout() { + let _ = io::stdout().flush(); +} + const SEARCH_LABEL: &str = "Searching"; struct SearchAnimation { @@ -195,18 +287,21 @@ struct TerminalIndexProgress { state: Arc>, stop: Arc, worker: Option>, + enabled: bool, } impl TerminalIndexProgress { fn start() -> Self { let state = Arc::new(Mutex::new(ProgressState::default())); let stop = Arc::new(AtomicBool::new(false)); - let worker = Some(spawn_progress_worker(Arc::clone(&state), Arc::clone(&stop))); + let enabled = io::stderr().is_terminal(); + let worker = enabled.then(|| spawn_progress_worker(Arc::clone(&state), Arc::clone(&stop))); Self { state, stop, worker, + enabled, } } @@ -226,6 +321,10 @@ impl Drop for TerminalIndexProgress { impl IndexProgress for TerminalIndexProgress { fn directory_started(&mut self, directory: &Path) { + if !self.enabled { + return; + } + let Ok(mut state) = self.state.lock() else { return; };