ux: use in-place progress spinner
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use std::io::IsTerminal;
|
||||
use std::io::{IsTerminal, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{atomic::{AtomicUsize, Ordering}, Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
@@ -434,6 +434,7 @@ fn run_scans(files: Vec<PathBuf>, config: &Config, ruleset: &RuleSet) -> Result<
|
||||
let started = AtomicUsize::new(0);
|
||||
let total = files.len();
|
||||
let show_progress = !config.report.json && std::io::stderr().is_terminal();
|
||||
let in_place = show_progress && jobs.is_none();
|
||||
|
||||
let scans = if let Some(jobs) = jobs {
|
||||
let pool = ThreadPoolBuilder::new().num_threads(jobs).build()?;
|
||||
@@ -442,17 +443,23 @@ fn run_scans(files: Vec<PathBuf>, config: &Config, ruleset: &RuleSet) -> Result<
|
||||
.par_iter()
|
||||
.filter_map(|path| {
|
||||
let idx = started.fetch_add(1, Ordering::SeqCst) + 1;
|
||||
if show_progress && !in_place {
|
||||
eprintln!("[SCAN {}/{}] {}", idx, total, path.display());
|
||||
} else if in_place {
|
||||
eprint!("[SCAN {}/{}] {}", idx, total, path.display());
|
||||
let _ = std::io::stderr().flush();
|
||||
}
|
||||
let progress = if show_progress {
|
||||
Some(make_progress_callback("SCAN", idx, total, path))
|
||||
Some(make_progress_callback("SCAN", idx, total, path, in_place))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if show_progress {
|
||||
eprintln!("[SCAN {}/{}] {}", idx, total, path.display());
|
||||
}
|
||||
match scan_file_with_progress(path, config, ruleset, progress) {
|
||||
Ok(scan) => Some(scan),
|
||||
Err(err) => {
|
||||
if in_place {
|
||||
eprintln!();
|
||||
}
|
||||
eprintln!("[ERROR] {}: {}", path.display(), err);
|
||||
errors.fetch_add(1, Ordering::SeqCst);
|
||||
None
|
||||
@@ -466,17 +473,23 @@ fn run_scans(files: Vec<PathBuf>, config: &Config, ruleset: &RuleSet) -> Result<
|
||||
.iter()
|
||||
.filter_map(|path| {
|
||||
let idx = started.fetch_add(1, Ordering::SeqCst) + 1;
|
||||
if show_progress && !in_place {
|
||||
eprintln!("[SCAN {}/{}] {}", idx, total, path.display());
|
||||
} else if in_place {
|
||||
eprint!("[SCAN {}/{}] {}", idx, total, path.display());
|
||||
let _ = std::io::stderr().flush();
|
||||
}
|
||||
let progress = if show_progress {
|
||||
Some(make_progress_callback("SCAN", idx, total, path))
|
||||
Some(make_progress_callback("SCAN", idx, total, path, in_place))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if show_progress {
|
||||
eprintln!("[SCAN {}/{}] {}", idx, total, path.display());
|
||||
}
|
||||
match scan_file_with_progress(path, config, ruleset, progress) {
|
||||
Ok(scan) => Some(scan),
|
||||
Err(err) => {
|
||||
if in_place {
|
||||
eprintln!();
|
||||
}
|
||||
eprintln!("[ERROR] {}: {}", path.display(), err);
|
||||
errors.fetch_add(1, Ordering::SeqCst);
|
||||
None
|
||||
@@ -523,21 +536,28 @@ fn process_fix_batch(
|
||||
let mut errors = 0usize;
|
||||
let total = files.len();
|
||||
let show_progress = !config.report.json && std::io::stderr().is_terminal();
|
||||
let in_place = show_progress;
|
||||
let mut idx = 0usize;
|
||||
|
||||
for path in files {
|
||||
idx += 1;
|
||||
if show_progress {
|
||||
if show_progress && !in_place {
|
||||
eprintln!("[FIX {}/{}] {}", idx, total, path.display());
|
||||
} else if in_place {
|
||||
eprint!("[FIX {}/{}] {}", idx, total, path.display());
|
||||
let _ = std::io::stderr().flush();
|
||||
}
|
||||
let progress = if show_progress {
|
||||
Some(make_progress_callback("FIX", idx, total, &path))
|
||||
Some(make_progress_callback("FIX", idx, total, &path, in_place))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let scan = match scan_file_with_progress(&path, config, ruleset, progress) {
|
||||
Ok(scan) => scan,
|
||||
Err(err) => {
|
||||
if in_place {
|
||||
eprintln!();
|
||||
}
|
||||
eprintln!("[ERROR] {}: {}", path.display(), err);
|
||||
errors += 1;
|
||||
continue;
|
||||
@@ -584,7 +604,7 @@ fn process_fix_batch_parallel(
|
||||
eprintln!("[FIX {}/{}] {}", idx, total, path.display());
|
||||
}
|
||||
let progress = if show_progress {
|
||||
Some(make_progress_callback("FIX", idx, total, path))
|
||||
Some(make_progress_callback("FIX", idx, total, path, false))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -623,11 +643,16 @@ fn process_fix_batch_parallel(
|
||||
fn watch_scan(paths: Vec<PathBuf>, config: &Config, ruleset: &RuleSet) -> Result<()> {
|
||||
println!("Watch mode enabled. Waiting for files to settle...");
|
||||
watch::watch_paths(&paths, config, |path| {
|
||||
if !config.report.json {
|
||||
let show_progress = !config.report.json && std::io::stderr().is_terminal();
|
||||
let in_place = show_progress;
|
||||
if show_progress && !in_place {
|
||||
eprintln!("[SCAN] {}", path.display());
|
||||
} else if in_place {
|
||||
eprint!("[SCAN] {}", path.display());
|
||||
let _ = std::io::stderr().flush();
|
||||
}
|
||||
let progress = if !config.report.json && std::io::stderr().is_terminal() {
|
||||
Some(make_progress_callback("SCAN", 1, 1, &path))
|
||||
let progress = if show_progress {
|
||||
Some(make_progress_callback("SCAN", 1, 1, &path, in_place))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -645,11 +670,16 @@ fn watch_scan(paths: Vec<PathBuf>, config: &Config, ruleset: &RuleSet) -> Result
|
||||
fn watch_fix(paths: Vec<PathBuf>, config: &Config, ruleset: &RuleSet, dry_run: bool) -> Result<()> {
|
||||
println!("Watch mode enabled. Waiting for files to settle...");
|
||||
watch::watch_paths(&paths, config, |path| {
|
||||
if !config.report.json {
|
||||
let show_progress = !config.report.json && std::io::stderr().is_terminal();
|
||||
let in_place = show_progress;
|
||||
if show_progress && !in_place {
|
||||
eprintln!("[FIX] {}", path.display());
|
||||
} else if in_place {
|
||||
eprint!("[FIX] {}", path.display());
|
||||
let _ = std::io::stderr().flush();
|
||||
}
|
||||
let progress = if !config.report.json && std::io::stderr().is_terminal() {
|
||||
Some(make_progress_callback("FIX", 1, 1, &path))
|
||||
let progress = if show_progress {
|
||||
Some(make_progress_callback("FIX", 1, 1, &path, in_place))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -681,6 +711,7 @@ struct ProgressState {
|
||||
last_emit: Instant,
|
||||
last_percent: Option<u8>,
|
||||
spinner_index: usize,
|
||||
last_len: usize,
|
||||
}
|
||||
|
||||
fn make_progress_callback(
|
||||
@@ -688,6 +719,7 @@ fn make_progress_callback(
|
||||
idx: usize,
|
||||
total: usize,
|
||||
path: &Path,
|
||||
in_place: bool,
|
||||
) -> Arc<dyn Fn(DecodeProgress) + Send + Sync> {
|
||||
let prefix = format!("[{} {}/{}]", kind, idx, total);
|
||||
let path_display = path.display().to_string();
|
||||
@@ -696,6 +728,7 @@ fn make_progress_callback(
|
||||
last_emit: now.checked_sub(Duration::from_secs(2)).unwrap_or(now),
|
||||
last_percent: None,
|
||||
spinner_index: 0,
|
||||
last_len: 0,
|
||||
}));
|
||||
|
||||
Arc::new(move |progress: DecodeProgress| {
|
||||
@@ -760,7 +793,7 @@ fn make_progress_callback(
|
||||
}
|
||||
|
||||
line.push_str(&path_display);
|
||||
eprintln!("{}", line);
|
||||
emit_progress_line(&line, progress.done, in_place, state.clone());
|
||||
})
|
||||
}
|
||||
|
||||
@@ -776,3 +809,33 @@ fn format_duration(seconds: f64) -> String {
|
||||
format!("{:02}:{:02}", minutes, secs)
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_progress_line(line: &str, done: bool, in_place: bool, state: Arc<Mutex<ProgressState>>) {
|
||||
if !in_place {
|
||||
eprintln!("{}", line);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut stderr = std::io::stderr();
|
||||
let mut output = String::new();
|
||||
output.push('\r');
|
||||
output.push_str(line);
|
||||
|
||||
if let Ok(mut state) = state.lock() {
|
||||
let line_len = line.chars().count();
|
||||
if line_len < state.last_len {
|
||||
output.push_str(&" ".repeat(state.last_len - line_len));
|
||||
}
|
||||
if done {
|
||||
output.push('\n');
|
||||
state.last_len = 0;
|
||||
} else {
|
||||
state.last_len = line_len;
|
||||
}
|
||||
} else if done {
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
let _ = stderr.write_all(output.as_bytes());
|
||||
let _ = stderr.flush();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user