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::path::{Path, PathBuf};
|
||||||
use std::sync::{atomic::{AtomicUsize, Ordering}, Arc, Mutex};
|
use std::sync::{atomic::{AtomicUsize, Ordering}, Arc, Mutex};
|
||||||
use std::time::{Duration, Instant};
|
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 started = AtomicUsize::new(0);
|
||||||
let total = files.len();
|
let total = files.len();
|
||||||
let show_progress = !config.report.json && std::io::stderr().is_terminal();
|
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 scans = if let Some(jobs) = jobs {
|
||||||
let pool = ThreadPoolBuilder::new().num_threads(jobs).build()?;
|
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()
|
.par_iter()
|
||||||
.filter_map(|path| {
|
.filter_map(|path| {
|
||||||
let idx = started.fetch_add(1, Ordering::SeqCst) + 1;
|
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 {
|
let progress = if show_progress {
|
||||||
Some(make_progress_callback("SCAN", idx, total, path))
|
Some(make_progress_callback("SCAN", idx, total, path, in_place))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
if show_progress {
|
|
||||||
eprintln!("[SCAN {}/{}] {}", idx, total, path.display());
|
|
||||||
}
|
|
||||||
match scan_file_with_progress(path, config, ruleset, progress) {
|
match scan_file_with_progress(path, config, ruleset, progress) {
|
||||||
Ok(scan) => Some(scan),
|
Ok(scan) => Some(scan),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
if in_place {
|
||||||
|
eprintln!();
|
||||||
|
}
|
||||||
eprintln!("[ERROR] {}: {}", path.display(), err);
|
eprintln!("[ERROR] {}: {}", path.display(), err);
|
||||||
errors.fetch_add(1, Ordering::SeqCst);
|
errors.fetch_add(1, Ordering::SeqCst);
|
||||||
None
|
None
|
||||||
@@ -466,17 +473,23 @@ fn run_scans(files: Vec<PathBuf>, config: &Config, ruleset: &RuleSet) -> Result<
|
|||||||
.iter()
|
.iter()
|
||||||
.filter_map(|path| {
|
.filter_map(|path| {
|
||||||
let idx = started.fetch_add(1, Ordering::SeqCst) + 1;
|
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 {
|
let progress = if show_progress {
|
||||||
Some(make_progress_callback("SCAN", idx, total, path))
|
Some(make_progress_callback("SCAN", idx, total, path, in_place))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
if show_progress {
|
|
||||||
eprintln!("[SCAN {}/{}] {}", idx, total, path.display());
|
|
||||||
}
|
|
||||||
match scan_file_with_progress(path, config, ruleset, progress) {
|
match scan_file_with_progress(path, config, ruleset, progress) {
|
||||||
Ok(scan) => Some(scan),
|
Ok(scan) => Some(scan),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
if in_place {
|
||||||
|
eprintln!();
|
||||||
|
}
|
||||||
eprintln!("[ERROR] {}: {}", path.display(), err);
|
eprintln!("[ERROR] {}: {}", path.display(), err);
|
||||||
errors.fetch_add(1, Ordering::SeqCst);
|
errors.fetch_add(1, Ordering::SeqCst);
|
||||||
None
|
None
|
||||||
@@ -523,21 +536,28 @@ fn process_fix_batch(
|
|||||||
let mut errors = 0usize;
|
let mut errors = 0usize;
|
||||||
let total = files.len();
|
let total = files.len();
|
||||||
let show_progress = !config.report.json && std::io::stderr().is_terminal();
|
let show_progress = !config.report.json && std::io::stderr().is_terminal();
|
||||||
|
let in_place = show_progress;
|
||||||
let mut idx = 0usize;
|
let mut idx = 0usize;
|
||||||
|
|
||||||
for path in files {
|
for path in files {
|
||||||
idx += 1;
|
idx += 1;
|
||||||
if show_progress {
|
if show_progress && !in_place {
|
||||||
eprintln!("[FIX {}/{}] {}", idx, total, path.display());
|
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 {
|
let progress = if show_progress {
|
||||||
Some(make_progress_callback("FIX", idx, total, &path))
|
Some(make_progress_callback("FIX", idx, total, &path, in_place))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
let scan = match scan_file_with_progress(&path, config, ruleset, progress) {
|
let scan = match scan_file_with_progress(&path, config, ruleset, progress) {
|
||||||
Ok(scan) => scan,
|
Ok(scan) => scan,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
if in_place {
|
||||||
|
eprintln!();
|
||||||
|
}
|
||||||
eprintln!("[ERROR] {}: {}", path.display(), err);
|
eprintln!("[ERROR] {}: {}", path.display(), err);
|
||||||
errors += 1;
|
errors += 1;
|
||||||
continue;
|
continue;
|
||||||
@@ -584,7 +604,7 @@ fn process_fix_batch_parallel(
|
|||||||
eprintln!("[FIX {}/{}] {}", idx, total, path.display());
|
eprintln!("[FIX {}/{}] {}", idx, total, path.display());
|
||||||
}
|
}
|
||||||
let progress = if show_progress {
|
let progress = if show_progress {
|
||||||
Some(make_progress_callback("FIX", idx, total, path))
|
Some(make_progress_callback("FIX", idx, total, path, false))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@@ -623,11 +643,16 @@ fn process_fix_batch_parallel(
|
|||||||
fn watch_scan(paths: Vec<PathBuf>, config: &Config, ruleset: &RuleSet) -> Result<()> {
|
fn watch_scan(paths: Vec<PathBuf>, config: &Config, ruleset: &RuleSet) -> Result<()> {
|
||||||
println!("Watch mode enabled. Waiting for files to settle...");
|
println!("Watch mode enabled. Waiting for files to settle...");
|
||||||
watch::watch_paths(&paths, config, |path| {
|
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());
|
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() {
|
let progress = if show_progress {
|
||||||
Some(make_progress_callback("SCAN", 1, 1, &path))
|
Some(make_progress_callback("SCAN", 1, 1, &path, in_place))
|
||||||
} else {
|
} else {
|
||||||
None
|
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<()> {
|
fn watch_fix(paths: Vec<PathBuf>, config: &Config, ruleset: &RuleSet, dry_run: bool) -> Result<()> {
|
||||||
println!("Watch mode enabled. Waiting for files to settle...");
|
println!("Watch mode enabled. Waiting for files to settle...");
|
||||||
watch::watch_paths(&paths, config, |path| {
|
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());
|
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() {
|
let progress = if show_progress {
|
||||||
Some(make_progress_callback("FIX", 1, 1, &path))
|
Some(make_progress_callback("FIX", 1, 1, &path, in_place))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@@ -681,6 +711,7 @@ struct ProgressState {
|
|||||||
last_emit: Instant,
|
last_emit: Instant,
|
||||||
last_percent: Option<u8>,
|
last_percent: Option<u8>,
|
||||||
spinner_index: usize,
|
spinner_index: usize,
|
||||||
|
last_len: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_progress_callback(
|
fn make_progress_callback(
|
||||||
@@ -688,6 +719,7 @@ fn make_progress_callback(
|
|||||||
idx: usize,
|
idx: usize,
|
||||||
total: usize,
|
total: usize,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
|
in_place: bool,
|
||||||
) -> Arc<dyn Fn(DecodeProgress) + Send + Sync> {
|
) -> Arc<dyn Fn(DecodeProgress) + Send + Sync> {
|
||||||
let prefix = format!("[{} {}/{}]", kind, idx, total);
|
let prefix = format!("[{} {}/{}]", kind, idx, total);
|
||||||
let path_display = path.display().to_string();
|
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_emit: now.checked_sub(Duration::from_secs(2)).unwrap_or(now),
|
||||||
last_percent: None,
|
last_percent: None,
|
||||||
spinner_index: 0,
|
spinner_index: 0,
|
||||||
|
last_len: 0,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
Arc::new(move |progress: DecodeProgress| {
|
Arc::new(move |progress: DecodeProgress| {
|
||||||
@@ -760,7 +793,7 @@ fn make_progress_callback(
|
|||||||
}
|
}
|
||||||
|
|
||||||
line.push_str(&path_display);
|
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)
|
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