diff --git a/src/logging.rs b/src/logging.rs index 0d6a29e..a6cc6eb 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -1,27 +1,14 @@ use std::cell::RefCell; use std::fmt; -use std::io::{self, IsTerminal, Write}; +use std::io::{self, Write}; use std::sync::{Arc, Mutex, OnceLock}; -use console::style; - -static OUTPUT: OnceLock> = OnceLock::new(); +static OUTPUT: OnceLock> = OnceLock::new(); thread_local! { static REPO_LOG: RefCell> = const { RefCell::new(None) }; } -#[derive(Default)] -struct OutputState { - status: Option, -} - -struct StatusState { - slots: Vec>, - visible: bool, - interactive: bool, -} - #[derive(Clone)] pub(crate) struct RepoLogContext { inner: Arc, @@ -33,20 +20,9 @@ struct ActiveRepoLog { } struct RepoLog { - repo_name: String, - slot: usize, - width: usize, lines: Mutex>, } -pub struct StatusGuard; - -impl Drop for StatusGuard { - fn drop(&mut self) { - finish_status_area(); - } -} - pub struct RepoLogGuard; impl Drop for RepoLogGuard { @@ -55,29 +31,9 @@ impl Drop for RepoLogGuard { } } -pub fn start_status_area(slots: usize) -> StatusGuard { - with_output(|output| { - if let Some(status) = output.status.as_mut() { - clear_status(status); - } - output.status = Some(StatusState { - slots: vec![None; slots], - visible: false, - interactive: io::stdout().is_terminal() && slots > 0, - }); - if let Some(status) = output.status.as_mut() { - draw_status(status); - } - }); - StatusGuard -} - -pub fn start_repo_log(repo_name: String, slot: usize, width: usize) -> RepoLogGuard { +pub fn start_repo_log() -> RepoLogGuard { let context = RepoLogContext { inner: Arc::new(RepoLog { - repo_name, - slot, - width, lines: Mutex::new(Vec::new()), }), }; @@ -144,31 +100,13 @@ pub fn finish_repo_log() { std::mem::take(&mut *lines) }; - with_output(|output| { - if let Some(status) = output.status.as_mut() { - clear_status(status); - if repo_log.slot < status.slots.len() { - status.slots[repo_log.slot] = None; - } - } + with_output(|| { for line in lines { println!("{line}"); } - if let Some(status) = output.status.as_mut() { - draw_status(status); - } }); } -pub fn repo_prefix(repo_name: &str, width: usize) -> String { - let mut prefix = repo_name.chars().take(width).collect::(); - if repo_name.chars().count() > width && width > 0 { - prefix.pop(); - prefix.push('~'); - } - format!("{prefix:) { let text = args.to_string(); let context = current_repo_log_context(); @@ -177,119 +115,35 @@ pub fn line(args: fmt::Arguments<'_>) { return; } - with_output(|output| { - if let Some(status) = output.status.as_mut() { - clear_status(status); - } + with_output(|| { println!("{text}"); - if let Some(status) = output.status.as_mut() { - draw_status(status); - } }); } fn capture_repo_line(context: &RepoLogContext, text: &str) { - let mut status_updates = Vec::new(); - { - let mut lines = context - .inner - .lines - .lock() - .unwrap_or_else(|poisoned| poisoned.into_inner()); - if text.is_empty() { - lines.push(String::new()); - return; - } - for line in text.lines() { - lines.push(line.to_string()); - if !line.trim().is_empty() { - status_updates.push(line.trim().to_string()); - } - } - } - for line in status_updates { - update_status(context, &line); - } -} - -fn update_status(context: &RepoLogContext, line: &str) { - let repo_log = &context.inner; - let repo = repo_prefix(&repo_log.repo_name, repo_log.width); - let line = truncate_status(line, 96); - with_output(|output| { - let Some(status) = output.status.as_mut() else { - return; - }; - if repo_log.slot >= status.slots.len() { - return; - } - clear_status(status); - status.slots[repo_log.slot] = Some(format!( - "{} {} {}", - style(format!("worker {}", repo_log.slot + 1)).dim(), - style(repo).cyan().bold(), - line - )); - draw_status(status); - }); -} - -fn truncate_status(value: &str, max_chars: usize) -> String { - if value.chars().count() <= max_chars { - return value.to_string(); - } - let mut output = value.chars().take(max_chars).collect::(); - output.pop(); - output.push('~'); - output -} - -fn finish_status_area() { - with_output(|output| { - if let Some(status) = output.status.as_mut() { - clear_status(status); - } - output.status = None; - }); -} - -fn with_output(action: impl FnOnce(&mut OutputState)) { - let output = OUTPUT.get_or_init(|| Mutex::new(OutputState::default())); - let mut output = output + let mut lines = context + .inner + .lines .lock() .unwrap_or_else(|poisoned| poisoned.into_inner()); - action(&mut output); + if text.is_empty() { + lines.push(String::new()); + return; + } + for line in text.lines() { + lines.push(line.to_string()); + } +} + +fn with_output(action: impl FnOnce()) { + let output = OUTPUT.get_or_init(|| Mutex::new(())); + let _output = output + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()); + action(); let _ = io::stdout().flush(); } -fn clear_status(status: &mut StatusState) { - if !status.interactive || !status.visible { - return; - } - - let lines = status.slots.len(); - print!("\x1b[{lines}A\r"); - for _ in 0..lines { - println!("\x1b[2K"); - } - print!("\x1b[{lines}A\r"); - status.visible = false; -} - -fn draw_status(status: &mut StatusState) { - if !status.interactive { - return; - } - - for slot in &status.slots { - match slot { - Some(line) => println!("{line}"), - None => println!("{}", style("idle").dim()), - } - } - status.visible = true; -} - #[macro_export] macro_rules! logln { () => { diff --git a/src/sync.rs b/src/sync.rs index 7feb5ba..2e36dc1 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -237,7 +237,6 @@ fn sync_group( ); } - let repo_log_width = repo_log_width(&repo_names); let repo_jobs = repo_names .into_iter() .map(|repo_name| { @@ -260,11 +259,10 @@ fn sync_group( let base_ref_state = context.ref_state.clone(); let queue = Arc::new(Mutex::new(repo_jobs)); let (sender, receiver) = mpsc::channel(); - let use_status_area = worker_count > 1; + let use_repo_logs = worker_count > 1; let jobs = context.options.jobs; - let _status_guard = use_status_area.then(|| logging::start_status_area(worker_count)); let failures = thread::scope(|scope| { - for worker_id in 0..worker_count { + for _ in 0..worker_count { let queue = Arc::clone(&queue); let sender = sender.clone(); let redactor = context.redactor.clone(); @@ -275,9 +273,7 @@ fn sync_group( scope.spawn(move || { while let Some(mut job) = pop_repo_job(&queue) { - let _repo_log_guard = use_status_area.then(|| { - logging::start_repo_log(job.repo_name.clone(), worker_id, repo_log_width) - }); + let _repo_log_guard = use_repo_logs.then(logging::start_repo_log); let repo_context = RepoSyncContext { config, mirror, @@ -383,15 +379,6 @@ fn pop_repo_job(queue: &Arc>>) -> Option) -> usize { - repo_names - .iter() - .map(|name| name.chars().count()) - .max() - .unwrap_or(4) - .clamp(4, 32) -} - struct RepoSyncJob { repo_name: String, existing: Vec, diff --git a/tests/unit/logging.rs b/tests/unit/logging.rs index d36ee9e..42a1129 100644 --- a/tests/unit/logging.rs +++ b/tests/unit/logging.rs @@ -1,20 +1,8 @@ use super::*; -#[test] -fn repo_prefix_pads_and_truncates_to_fixed_width() { - assert_eq!(repo_prefix("api", 6), "api "); - assert_eq!(repo_prefix("very-long-repo", 8), "very-lo~"); -} - -#[test] -fn status_text_truncates_to_fixed_width() { - assert_eq!(truncate_status("short", 8), "short"); - assert_eq!(truncate_status("very-long-status", 8), "very-lo~"); -} - #[test] fn repo_log_context_is_inherited_by_parallel_workers() { - let _guard = start_repo_log("repo-a".to_string(), 0, 8); + let _guard = start_repo_log(); crate::logln!("outer line"); crate::parallel::map(vec!["worker line"], 1, |line| {