From 8757ec924c415c3148d8233ec0d0ede45a7fc51a Mon Sep 17 00:00:00 2001 From: solidiquis Date: Sat, 18 May 2024 20:05:40 -0700 Subject: [PATCH 1/3] progress indicator skeleton --- Cargo.lock | 68 +++++---- Cargo.toml | 7 +- rustfmt.toml | 1 - src/disk/mod.rs | 10 +- src/file/mod.rs | 28 ++++ src/file/tree/mod.rs | 60 ++++---- src/file/tree/traverse.rs | 2 +- src/logging.rs | 44 ------ src/main.rs | 78 ++++++---- src/perf.rs | 132 +++++++++++++++++ src/progress.rs | 129 ++++++++++++++++ src/render/mod.rs | 300 ++++++++++++++++++++------------------ src/user/mod.rs | 23 ++- 13 files changed, 590 insertions(+), 292 deletions(-) delete mode 100644 rustfmt.toml delete mode 100644 src/logging.rs create mode 100644 src/perf.rs create mode 100644 src/progress.rs diff --git a/Cargo.lock b/Cargo.lock index 505a9649..a9e7e426 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,15 +44,16 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] @@ -114,6 +115,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + [[package]] name = "bstr" version = "1.3.0" @@ -156,9 +163,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.10" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -166,9 +173,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.9" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", @@ -187,9 +194,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ "heck", "proc-macro2", @@ -199,9 +206,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "colorchoice" @@ -230,11 +237,11 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "crossterm" -version = "0.26.1" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags", + "bitflags 2.5.0", "crossterm_winapi", "libc", "mio", @@ -246,9 +253,9 @@ dependencies = [ [[package]] name = "crossterm_winapi" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" dependencies = [ "winapi", ] @@ -309,7 +316,6 @@ dependencies = [ "indextree", "indoc", "libc", - "log", "lscolors", "once_cell", "regex", @@ -409,9 +415,9 @@ checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" @@ -501,6 +507,12 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "js-sys" version = "0.3.64" @@ -590,7 +602,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "libc", "static_assertions", @@ -637,9 +649,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "overload" @@ -700,7 +712,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -737,7 +749,7 @@ version = "0.36.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno 0.2.8", "io-lifetimes", "libc", @@ -751,7 +763,7 @@ version = "0.37.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2aae838e49b3d63e9274e1c01833cc8139d3fec468c3b84688c628f44b1ae11d" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno 0.3.1", "io-lifetimes", "libc", @@ -805,9 +817,9 @@ dependencies = [ [[package]] name = "signal-hook" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" dependencies = [ "libc", "signal-hook-registry", @@ -856,9 +868,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" diff --git a/Cargo.toml b/Cargo.toml index 78c96036..49cf6832 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,19 +34,18 @@ ahash = "0.8.6" ansi_term = "0.12.1" anyhow = "1.0.75" chrono = { version = "0.4.24", default-features = false, features = ["clock", "std"] } -clap = { version = "4.4.10", features = ["derive"] } +clap = { version = "4.5.4", features = ["derive"] } clap_complete = "4.1.1" config = { version = "0.14.0", default-features = false, features = ["toml"] } -crossterm = "0.26.1" +crossterm = "0.27.0" ctrlc = "3.4.0" dirs = "5.0" errno = "0.3.1" filesize = "0.2.0" ignore = "0.4.2" indextree = "4.6.0" -log = { version = "0.4.20", features = ["std"] } lscolors = { version = "0.13.0", features = ["ansi_term"] } -once_cell = "1.17.0" +once_cell = "1.19.0" regex = "1.7.3" terminal_size = "0.2.6" thiserror = "1.0.40" diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index 8c795ae5..00000000 --- a/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -match_block_trailing_comma = true diff --git a/src/disk/mod.rs b/src/disk/mod.rs index 044407f1..80d86e94 100644 --- a/src/disk/mod.rs +++ b/src/disk/mod.rs @@ -119,10 +119,7 @@ impl Usage { std::fs::read_to_string(data.path()).map(|data| data.split_whitespace().count())?; let word_count = u64::try_from(word_count).map_or_else( - |e| { - log::warn!("Usage::init_word_count {e}"); - Self::WordCount(word_count as u64) - }, + |_| Self::WordCount(word_count as u64), Self::WordCount, ); @@ -144,10 +141,7 @@ impl Usage { let line_count = fs::read_to_string(data.path()).map(|data| data.lines().count())?; let line_count = u64::try_from(line_count).map_or_else( - |e| { - log::warn!("Usage::init_line_count {e}"); - Self::WordCount(line_count as u64) - }, + |_| Self::WordCount(line_count as u64), Self::LineCount, ); diff --git a/src/file/mod.rs b/src/file/mod.rs index 473bbfdb..75650128 100644 --- a/src/file/mod.rs +++ b/src/file/mod.rs @@ -46,6 +46,14 @@ pub struct File { unix_attrs: unix::Attrs, } +// For keeping track of the count of file-types while loading from disk. +#[derive(Default)] +pub struct Accumulator { + num_file: usize, + num_dir: usize, + num_link: usize, +} + /// [`Display`] implementation concerned with human-readable presentation of the file-name. pub struct DisplayName<'a> { file: &'a File, @@ -246,6 +254,26 @@ impl Deref for File { } } +impl Accumulator { + pub fn total(&self) -> usize { + self.num_dir + self.num_file + self.num_link + } + + pub fn increment(&mut self, ft: Option) { + let Some(file_type) = ft else { + return + }; + + if file_type.is_file() { + self.num_file += 1; + } else if file_type.is_dir() { + self.num_dir += 1; + } else if file_type.is_symlink() { + self.num_link += 1; + } + } +} + impl Display for DisplayName<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let file_name = self.file.file_name().to_string_lossy(); diff --git a/src/file/tree/mod.rs b/src/file/tree/mod.rs index afaa8c22..034adce8 100644 --- a/src/file/tree/mod.rs +++ b/src/file/tree/mod.rs @@ -2,6 +2,7 @@ use super::order; use crate::{ error::prelude::*, file::File, + progress, user::{ args::{Layout, Sort, SortType}, column, Context, @@ -41,13 +42,13 @@ pub enum TreeError { impl Tree { /// Like [`Tree::init`] but leverages parallelism for disk-reads and [`File`] initialization. - pub fn init(ctx: &Context) -> Result<(Self, column::Metadata)> { - let TransitionState { + pub fn init(ctx: &Context) -> Result<(Self, super::Accumulator, column::Metadata)> { + let (TransitionState { mut arena, mut branches, mut column_metadata, root_id, - } = Self::load(ctx)?; + }, accumulator)= Self::load(ctx)?; let mut dir_stack = vec![root_id]; let mut inode_set = HashSet::default(); @@ -83,23 +84,15 @@ impl Tree { continue 'outer; } - match dirent_node.inode() { - Ok(inode) => { - #[cfg(unix)] - column_metadata.update_inode_attr_widths(&inode); - - *current_dirsize += inode_set - .insert(inode) - .then(|| dirent_node.size().value()) - .unwrap_or(0); - }, - Err(err) => { - log::warn!( - "Failed to query inode of {} which may affect disk usage report: {err}", - dirent_node.path().display(), - ); - }, - }; + if let Ok(inode) = dirent_node.inode() { + #[cfg(unix)] + column_metadata.update_inode_attr_widths(&inode); + + *current_dirsize += inode_set + .insert(inode) + .then(|| dirent_node.size().value()) + .unwrap_or(0); + } } dir_stack.pop(); @@ -175,16 +168,16 @@ impl Tree { let tree = Self { root_id, arena }; - Ok((tree, column_metadata)) + Ok((tree, accumulator, column_metadata)) } - pub fn init_without_disk_usage(ctx: &Context) -> Result<(Self, column::Metadata)> { - let TransitionState { + pub fn init_without_disk_usage(ctx: &Context) -> Result<(Self, super::Accumulator, column::Metadata)> { + let (TransitionState { mut arena, mut branches, mut column_metadata, root_id, - } = Self::load(ctx)?; + }, accumulator)= Self::load(ctx)?; #[cfg(unix)] macro_rules! update_metadata { @@ -256,16 +249,20 @@ impl Tree { let tree = Self { root_id, arena }; - Ok((tree, column_metadata)) + Ok((tree, accumulator, column_metadata)) } /// Reads data from disk and aggregates data along with metadata into a [`TransitionState`] /// which callers would then consume to construct a [`Tree`]. - fn load(ctx: &Context) -> Result { + fn load(ctx: &Context) -> Result<(TransitionState, super::Accumulator)> { let mut arena = Arena::new(); let mut branches = HashMap::>::default(); let mut column_metadata = column::Metadata::default(); let mut maybe_root_id = None; + let mut accumulator = super::Accumulator::default(); + + // To notify the progress indicator + let notifier = progress::Indicator::use_notifier(); traverse::run(ctx, |file| { #[cfg(unix)] @@ -275,6 +272,9 @@ impl Tree { let file = arena[node_id].get(); let file_path = file.path(); + accumulator.increment(file.file_type()); + progress::Indicator::notify(¬ifier, accumulator.total()); + maybe_root_id = (file.depth() == 0).then_some(node_id).or(maybe_root_id); if let Some(parent) = file_path.parent() { @@ -287,17 +287,21 @@ impl Tree { Ok(()) })?; + progress::Indicator::finish(¬ifier); + let root_id = maybe_root_id .ok_or(TreeError::RootDir) .into_report(ErrorCategory::Internal) .context(error_source!())?; - Ok(TransitionState { + let ts = TransitionState { arena, branches, column_metadata, root_id, - }) + }; + + Ok((ts, accumulator)) } pub fn root_id(&self) -> NodeId { diff --git a/src/file/tree/traverse.rs b/src/file/tree/traverse.rs index 3ebbd54c..dd725fd1 100644 --- a/src/file/tree/traverse.rs +++ b/src/file/tree/traverse.rs @@ -24,9 +24,9 @@ where loop { match rx.recv().into_report(ErrorCategory::Internal) { Ok(TraversalState::Ongoing(file)) => op(file)?, - Ok(TraversalState::Error(e)) => log::warn!("{e}"), Ok(TraversalState::Done) => break, Err(e) => return Err(e), + _ => (), } } Ok(()) diff --git a/src/logging.rs b/src/logging.rs deleted file mode 100644 index b8944e85..00000000 --- a/src/logging.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::error::prelude::*; -use chrono::Utc; -use log::{LevelFilter, Log, Metadata, Record}; -use std::fmt::Write; - -pub static mut BUFFER: String = String::new(); - -pub struct LoggityLog; - -impl LoggityLog { - pub fn new() -> Self { - LoggityLog {} - } - - pub fn init() -> Result<&'static LoggityLog> { - let logger = Box::new(LoggityLog::new()); - let leak: &'static LoggityLog = Box::leak(logger); - log::set_logger(leak).into_report(ErrorCategory::Internal)?; - log::set_max_level(LevelFilter::Info); - Ok(leak) - } -} - -impl Log for LoggityLog { - fn enabled(&self, _metadata: &Metadata<'_>) -> bool { - true - } - - fn log(&self, record: &Record<'_>) { - unsafe { - let _ = writeln!( - BUFFER, - "[{}] {} {}", - Utc::now().to_rfc3339(), - record.level(), - record.args() - ); - } - } - - fn flush(&self) { - unsafe { println!("{BUFFER}") } - } -} diff --git a/src/main.rs b/src/main.rs index 915192c5..42edb582 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,10 @@ #![cfg_attr(windows, feature(windows_by_handle))] use clap::CommandFactory; -use log::Log; use std::{ - io::{stdout, Write}, + io::stdout, process::ExitCode, }; -/// Defines the command-line interface and the context used throughout Erdtree. -mod user; -use user::Context; - /// Concerned with disk usage calculation and presentation. mod disk; @@ -23,11 +18,22 @@ mod file; /// Concerned with file icons. mod icon; -/// Concerned with logging throughout the application. -mod logging; +/// Progress indicator. +mod progress; + +/// Defines the command-line interface and the context used throughout Erdtree. +mod user; +use user::Context; + + +/// For basic performance measurements when compiling for debug. +#[cfg(debug_assertions)] +#[macro_use] +mod perf; /// Concerned with rendering the program output. mod render; +use render::Renderer; const BIN_NAME: &str = "erd"; @@ -40,39 +46,55 @@ fn main() -> ExitCode { } fn run() -> Result<()> { + #[cfg(debug_assertions)] + { + perf::init_global(); + perf::begin_recording("crate::main"); + } + + #[cfg(debug_assertions)] + crate::perf::begin_recording("crate::user::Context::init"); + let mut ctx = user::Context::init()?; + #[cfg(debug_assertions)] + crate::perf::finish_recording("crate::user::Context::init"); + if let Some(shell) = ctx.completions { clap_complete::generate(shell, &mut Context::command(), BIN_NAME, &mut stdout()); return Ok(()); } - let logger = ctx - .verbose - .then_some(logging::LoggityLog::init()) - .transpose()?; - - let mut file_tree = if ctx.suppress_size { - file::Tree::init_without_disk_usage(&ctx).map(|(tree, column_metadata)| { - ctx.update_column_metadata(column_metadata); - tree - })? + // TODO: Use accumulator + let (mut file_tree, _accumulator, column_metadata) = if ctx.no_progress { + if ctx.suppress_size { + file::Tree::init_without_disk_usage(&ctx) + } else { + file::Tree::init(&ctx) + } } else { - file::Tree::init(&ctx).map(|(tree, column_metadata)| { - ctx.update_column_metadata(column_metadata); - tree - })? - }; + progress::Indicator::init().show_progress(|| { + if ctx.suppress_size { + file::Tree::init_without_disk_usage(&ctx) + } else { + file::Tree::init(&ctx) + } + }) + }?; + + ctx.update_column_metadata(column_metadata); file_tree.filter_nodes(&ctx)?; - let output = render::output(&file_tree, &ctx)?; + Renderer::new(&ctx, &file_tree).render()?; - let mut stdout = stdout().lock(); - writeln!(stdout, "{output}").into_report(ErrorCategory::Warning)?; + #[cfg(debug_assertions)] + { + perf::finish_recording("crate::main"); - if let Some(logger) = logger { - logger.flush(); + if ctx.debug { + perf::output(std::io::stdout()); + } } Ok(()) diff --git a/src/perf.rs b/src/perf.rs new file mode 100644 index 00000000..4afe22d3 --- /dev/null +++ b/src/perf.rs @@ -0,0 +1,132 @@ +use ahash::HashMap; +use once_cell::sync::Lazy; +use std::{ + io, + fmt, + sync::{ + mpsc::{self, Sender}, + OnceLock, + }, + time::{Duration, Instant}, + thread::{self, JoinHandle}, +}; + +static mut GLOBAL_PERF_METRICS: OnceLock = OnceLock::new(); +static OUTPUT_NOTIFIER: OnceLock> = OnceLock::new(); +static mut ONGOING_RECORDINGS: Lazy> = Lazy::new(HashMap::default); + +#[derive(Debug)] +pub struct Recording { + name: String, + start: Instant, + duration: Option, +} + +#[derive(Debug)] +pub struct PerfMetrics(Option>>); + +enum Message { + Recording(Recording), + Terminate, +} + +pub fn init_global() { + let (tx, rx) = mpsc::channel(); + + OUTPUT_NOTIFIER.set(tx).unwrap(); + + let handle = thread::spawn(move || { + let mut recordings = Vec::new(); + + while let Ok(Message::Recording(recording)) = rx.recv() { + recordings.push(recording); + } + + recordings + }); + + unsafe { + GLOBAL_PERF_METRICS.set(PerfMetrics(Some(handle))).unwrap(); + } +} + +pub fn begin_recording(name: &str) { + let recording = Recording { + name: name.to_string(), + start: Instant::now(), + duration: None, + }; + + unsafe { + if let Some(old_value) = ONGOING_RECORDINGS.insert(name.to_string(), recording) { + panic!("Can't have two ongoing recordings with the same name: {old_value}"); + } + } +} + +pub fn finish_recording(name: &str) { + let mut recording = unsafe { + ONGOING_RECORDINGS + .remove(name) + .unwrap_or_else(|| panic!("No ongoing recording with name '{name}'")) + }; + + recording.duration = Some(recording.start.elapsed()); + + OUTPUT_NOTIFIER + .get() + .cloned() + .and_then(|tx| { + tx.send(Message::Recording(recording)).ok() + }); +} + +pub fn output(mut w: T) { + OUTPUT_NOTIFIER.get().cloned().and_then(|tx| { + tx.send(Message::Terminate).ok() + }); + + let mut recordings = unsafe { + GLOBAL_PERF_METRICS + .get_mut() + .and_then(|PerfMetrics(join)| { + join.take() + .and_then(|h| h.join().ok()) + }) + .unwrap() + }; + + recordings.sort_by_cached_key(|a| a.duration.unwrap().as_nanos()); + + let out = recordings + .into_iter() + .rev() + .map(|r| format!("{r}")) + .collect::>() + .join("\n"); + + writeln!(w, "Performance metrics:\n{out}").unwrap(); + + unsafe { + if !ONGOING_RECORDINGS.is_empty() { + let still_ongoing = ONGOING_RECORDINGS + .keys() + .map(|k| format!("- {k}")) + .collect::>() + .join("\n"); + writeln!(w, "The following recordings are finished:\n{still_ongoing}").unwrap(); + } + } +} + +impl fmt::Display for Recording { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + " {}:\n\t{}ms | {}ns", + self.name, + self.duration.unwrap().as_millis(), + self.duration.unwrap().as_nanos(), + ) + } +} diff --git a/src/progress.rs b/src/progress.rs new file mode 100644 index 00000000..be0059a3 --- /dev/null +++ b/src/progress.rs @@ -0,0 +1,129 @@ +use crossterm::{ + cursor, + ExecutableCommand, + terminal::{self, ClearType}, +}; +use std::{ + io::{self, Write}, + fmt, + sync::{ + mpsc::{self, Receiver, Sender}, + OnceLock, + }, + thread, +}; + +/// For progress indicator to throttle printin. +pub const RENDER_INTERVAL_MS: u64 = 33; + +/// To notify the progress indicator +static NOTIFIER: OnceLock> = OnceLock::new(); + +pub struct Indicator { + counter: usize, + state: IndicatorState, + mailbox: Receiver, +} + +pub enum Message { + Indexing(usize), + DoneIndexing, +} + +#[derive(Default)] +enum IndicatorState { + #[default] + Indexing, + PreparingOutput, +} + +#[derive(Default)] +pub struct FileCounter { + num_file: usize, + num_dir: usize, + num_link: usize, +} + +impl Indicator { + pub fn init() -> Self { + let (tx, rx) = mpsc::channel(); + + NOTIFIER.set(tx).unwrap(); + + Self { + counter: 0, + state: IndicatorState::default(), + mailbox: rx, + } + } + + pub fn use_notifier() -> Option> { + NOTIFIER.get().cloned() + } + + fn update_state(&mut self, new_state: IndicatorState) { + self.state = new_state; + } + + pub fn notify(notifier: &Option>, count: usize) { + if let Some(n) = notifier.as_ref() { + let _ = n.send(Message::Indexing(count)); + } + } + + pub fn finish(notifier: &Option>) { + if let Some(n) = notifier.as_ref() { + let _ = n.send(Message::DoneIndexing); + } + } + + pub fn show_progress(mut self, op: F) -> T + where F: FnOnce() -> T + { + let _ = io::stdout().execute(cursor::Hide); + + let comp_result = thread::scope(move |s| { + let handle = s.spawn(move || { + let threshold = std::time::Duration::from_millis(RENDER_INTERVAL_MS); + let mut time_last_print = std::time::Instant::now(); + let mut stdout = io::stdout(); + let _ = stdout.execute(cursor::SavePosition); + + while let Ok(Message::Indexing(count)) = self.mailbox.recv() { + self.counter = count; + + if time_last_print.elapsed() < threshold { + continue; + } + let _ = stdout.execute(terminal::Clear(ClearType::CurrentLine)); + let _ = write!(stdout, "{self}"); + let _ = stdout.execute(cursor::RestorePosition); + time_last_print = std::time::Instant::now(); + } + self.update_state(IndicatorState::PreparingOutput); + let _ = stdout.execute(terminal::Clear(ClearType::CurrentLine)); + let _ = write!(stdout, "{self}"); + }); + + let out = op(); + handle.join().unwrap(); + + out + }); + + let _ = io::stdout().execute(cursor::Show); + + comp_result + } +} + +impl fmt::Display for Indicator { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.state { + IndicatorState::Indexing => { + write!(f, "Indexing {} files...", self.counter) + } + IndicatorState::PreparingOutput => write!(f, "Preparing output..."), + } + } +} diff --git a/src/render/mod.rs b/src/render/mod.rs index 66d2c1fb..a32364bc 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -4,6 +4,7 @@ use crate::{ user::{args::Layout, Context}, }; use indextree::{NodeEdge, NodeId}; +use std::io::{self, Write}; /// Used for padding between tree branches. pub const SEP: &str = " "; @@ -24,188 +25,199 @@ pub const ROTATED_T: &str = "\u{251C}\u{2500} "; /// row in the program output. mod row; -pub fn output(file_tree: &file::Tree, ctx: &Context) -> Result { - match ctx.layout { - Layout::Tree => tree(file_tree, ctx), - Layout::InvertedTree => inverted_tree(file_tree, ctx), - Layout::Flat => flat(file_tree, ctx), - } +pub struct Renderer<'a> { + ctx: &'a Context, + file_tree: &'a file::Tree, } -fn inverted_tree(file_tree: &file::Tree, ctx: &Context) -> Result { - let arena = file_tree.arena(); - let root = file_tree.root_id(); - let max_depth = ctx.level(); +impl<'a> Renderer<'a> { + pub fn new(ctx: &'a Context, file_tree: &'a file::Tree) -> Self { + Self { ctx, file_tree } + } - let mut buf = String::new(); + pub fn render(self) -> Result<()> { + let out = match self.ctx.layout { + Layout::Tree => self.tree(), + Layout::InvertedTree => self.inverted_tree(), + Layout::Flat => self.flat(), + }?; - let is_first_sibling = |node_id: NodeId, depth: usize| { - (depth > 0) - .then(|| node_id.following_siblings(arena).nth(1).is_none()) - .unwrap_or(false) - }; + writeln!(io::stdout(), "{out}").into_report(ErrorCategory::Warning)?; - let mut inherited_prefix_components = vec![""]; + Ok(()) + } - let mut formatter = row::formatter(&mut buf, ctx)?; + fn inverted_tree(&self) -> Result { + let arena = self.file_tree.arena(); + let root = self.file_tree.root_id(); + let max_depth = self.ctx.level(); - let mut reverse_traverse = root.reverse_traverse(arena); - reverse_traverse.next(); + let mut buf = String::new(); - for node_edge in reverse_traverse { - let (node, node_id, depth) = match node_edge { - NodeEdge::Start(node_id) => { - let node = arena[node_id].get(); - let depth = node.depth(); + let is_first_sibling = |node_id: NodeId, depth: usize| { + (depth > 0) + .then(|| node_id.following_siblings(arena).nth(1).is_none()) + .unwrap_or(false) + }; - if node.is_dir() { - inherited_prefix_components.pop(); - } + let mut inherited_prefix_components = vec![""]; - if depth > max_depth { - continue; - } + let mut formatter = row::formatter(&mut buf, self.ctx)?; + + let mut reverse_traverse = root.reverse_traverse(arena); + reverse_traverse.next(); - (node, node_id, depth) - }, - NodeEdge::End(node_id) => { - let node = arena[node_id].get(); - let depth = node.depth(); - - if node.is_dir() { - if is_first_sibling(node_id, depth) { - inherited_prefix_components.push(SEP); - } else { - inherited_prefix_components.push(VLINE); + for node_edge in reverse_traverse { + let (node, node_id, depth) = match node_edge { + NodeEdge::Start(node_id) => { + let node = arena[node_id].get(); + let depth = node.depth(); + + if node.is_dir() { + inherited_prefix_components.pop(); } - } - continue; - }, - }; - let prefix = format!( - "{}{}", - inherited_prefix_components.join(""), - (depth > 0) - .then(|| { - is_first_sibling(node_id, depth) - .then_some(UL_CORNER) - .unwrap_or(ROTATED_T) - }) - .unwrap_or("") - ); - - if let Err(e) = formatter(node, prefix) { - log::warn!("{e}"); + if depth > max_depth { + continue; + } + + (node, node_id, depth) + }, + NodeEdge::End(node_id) => { + let node = arena[node_id].get(); + let depth = node.depth(); + + if node.is_dir() { + if is_first_sibling(node_id, depth) { + inherited_prefix_components.push(SEP); + } else { + inherited_prefix_components.push(VLINE); + } + } + continue; + }, + }; + + let prefix = format!( + "{}{}", + inherited_prefix_components.join(""), + (depth > 0) + .then(|| { + is_first_sibling(node_id, depth) + .then_some(UL_CORNER) + .unwrap_or(ROTATED_T) + }) + .unwrap_or("") + ); + + let _ = formatter(node, prefix); } - } - drop(formatter); + drop(formatter); - Ok(buf) -} + Ok(buf) + } -pub fn tree(file_tree: &file::Tree, ctx: &Context) -> Result { - let arena = file_tree.arena(); - let root = file_tree.root_id(); - let max_depth = ctx.level(); + pub fn tree(&self) -> Result { + let arena = self.file_tree.arena(); + let root = self.file_tree.root_id(); + let max_depth = self.ctx.level(); - let mut buf = String::new(); + let mut buf = String::new(); - let is_last_sibling = |node_id: NodeId, depth: usize| { - (depth > 0) - .then(|| node_id.following_siblings(arena).nth(1).is_none()) - .unwrap_or(false) - }; + let is_last_sibling = |node_id: NodeId, depth: usize| { + (depth > 0) + .then(|| node_id.following_siblings(arena).nth(1).is_none()) + .unwrap_or(false) + }; - let mut inherited_prefix_components = vec![""]; + let mut inherited_prefix_components = vec![""]; - let mut formatter = row::formatter(&mut buf, ctx)?; + let mut formatter = row::formatter(&mut buf, self.ctx)?; - let mut traverse = root.traverse(arena); - traverse.next(); + let mut traverse = root.traverse(arena); + traverse.next(); - formatter(arena[root].get(), "".to_string()) - .into_report(ErrorCategory::Internal) - .context(error_source!())?; + formatter(arena[root].get(), "".to_string()) + .into_report(ErrorCategory::Internal) + .context(error_source!())?; - for node_edge in traverse { - let (node, node_id, depth) = match node_edge { - NodeEdge::Start(node_id) => { - let node = arena[node_id].get(); - let depth = node.depth(); + for node_edge in traverse { + let (node, node_id, depth) = match node_edge { + NodeEdge::Start(node_id) => { + let node = arena[node_id].get(); + let depth = node.depth(); - if depth > max_depth { - continue; - } + if depth > max_depth { + continue; + } - (node, node_id, depth) - }, - NodeEdge::End(node_id) => { - let node = arena[node_id].get(); - let depth = node.depth(); + (node, node_id, depth) + }, + NodeEdge::End(node_id) => { + let node = arena[node_id].get(); + let depth = node.depth(); - if node.is_dir() && depth < max_depth { - inherited_prefix_components.pop(); + if node.is_dir() && depth < max_depth { + inherited_prefix_components.pop(); + } + continue; + }, + }; + + let prefix = format!( + "{}{}", + inherited_prefix_components.join(""), + (depth > 0) + .then(|| { + is_last_sibling(node_id, depth) + .then_some(BL_CORNER) + .unwrap_or(ROTATED_T) + }) + .unwrap_or("") + ); + + let _ = formatter(node, prefix); + + if node.is_dir() && depth < max_depth { + if is_last_sibling(node_id, depth) { + inherited_prefix_components.push(SEP); + } else { + inherited_prefix_components.push(VLINE); } - continue; - }, - }; - - let prefix = format!( - "{}{}", - inherited_prefix_components.join(""), - (depth > 0) - .then(|| { - is_last_sibling(node_id, depth) - .then_some(BL_CORNER) - .unwrap_or(ROTATED_T) - }) - .unwrap_or("") - ); - - if let Err(e) = formatter(node, prefix) { - log::warn!("{e}"); - } - - if node.is_dir() && depth < max_depth { - if is_last_sibling(node_id, depth) { - inherited_prefix_components.push(SEP); - } else { - inherited_prefix_components.push(VLINE); } } - } - drop(formatter); + drop(formatter); - Ok(buf) -} + Ok(buf) + } -fn flat(file_tree: &file::Tree, ctx: &Context) -> Result { - let arena = file_tree.arena(); - let root = file_tree.root_id(); - let max_depth = ctx.level(); - let mut buf = String::new(); + fn flat(&self) -> Result { + let arena = self.file_tree.arena(); + let root = self.file_tree.root_id(); + let max_depth = self.ctx.level(); + let mut buf = String::new(); - let mut formatter = row::formatter(&mut buf, ctx)?; + let mut formatter = row::formatter(&mut buf, self.ctx)?; - for node_edge in root.traverse(arena) { - let node_id = match node_edge { - NodeEdge::Start(_) => continue, - NodeEdge::End(id) if id.is_removed(arena) => continue, - NodeEdge::End(id) => id, - }; + for node_edge in root.traverse(arena) { + let node_id = match node_edge { + NodeEdge::Start(_) => continue, + NodeEdge::End(id) if id.is_removed(arena) => continue, + NodeEdge::End(id) => id, + }; - let node = arena[node_id].get(); + let node = arena[node_id].get(); - if node.depth() > max_depth { - continue; + if node.depth() > max_depth { + continue; + } + + formatter(node, "".to_string()).into_report(ErrorCategory::Warning)?; } + drop(formatter); - formatter(node, "".to_string()).into_report(ErrorCategory::Warning)?; + Ok(buf) } - drop(formatter); - - Ok(buf) } diff --git a/src/user/mod.rs b/src/user/mod.rs index c6ae0717..d772f857 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -1,7 +1,11 @@ use crate::error::prelude::*; use ahash::HashMap; use clap::{parser::ValueSource, ArgMatches, Args, CommandFactory, FromArgMatches, Parser}; -use std::{env, fs, path::PathBuf}; +use std::{ + env, + fs, + path::PathBuf, +}; /// Enum definitions for enumerated command-line arguments. pub mod args; @@ -85,6 +89,10 @@ pub struct Context { #[arg(short, long)] pub no_config: bool, + /// Hide the progress indicator + #[arg(long)] + pub no_progress: bool, + #[command(flatten)] pub search: Search, @@ -116,10 +124,6 @@ pub struct Context { #[arg(short = 'x', long = "one-file-system")] pub same_fs: bool, - /// Prints logs at the end of the output - #[arg(short = 'v', long = "verbose")] - pub verbose: bool, - #[arg(long)] /// Print completions for a given shell to stdout pub completions: Option, @@ -129,6 +133,11 @@ pub struct Context { ////////////////////////// #[clap(skip = column::Metadata::default())] pub column_metadata: column::Metadata, + + #[cfg(debug_assertions)] + #[arg(long)] + // Output debug information at the end of the output + pub debug: bool, } #[derive(Args, Debug)] @@ -193,7 +202,9 @@ impl Context { Self::from_arg_matches(&clargs).into_report(ErrorCategory::User)? }; - if ctx.dir.is_none() { + if let Some(dir_arg) = clargs.get_one::("dir").cloned() { + ctx.dir = Some(dir_arg) + } else { let current_dir = Self::get_current_dir()?; ctx.dir = Some(current_dir); } From 56fbf61019971e2dd817f73a8cfe8131e2a6e4db Mon Sep 17 00:00:00 2001 From: solidiquis Date: Sat, 18 May 2024 20:16:06 -0700 Subject: [PATCH 2/3] print once for preparing output state --- src/progress.rs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/progress.rs b/src/progress.rs index be0059a3..1c30ceec 100644 --- a/src/progress.rs +++ b/src/progress.rs @@ -1,6 +1,8 @@ use crossterm::{ cursor, + execute, ExecutableCommand, + style::Print, terminal::{self, ClearType}, }; use std::{ @@ -14,7 +16,7 @@ use std::{ }; /// For progress indicator to throttle printin. -pub const RENDER_INTERVAL_MS: u64 = 33; +pub const RENDER_INTERVAL_MS: u64 = 16; /// To notify the progress indicator static NOTIFIER: OnceLock> = OnceLock::new(); @@ -37,13 +39,6 @@ enum IndicatorState { PreparingOutput, } -#[derive(Default)] -pub struct FileCounter { - num_file: usize, - num_dir: usize, - num_link: usize, -} - impl Indicator { pub fn init() -> Self { let (tx, rx) = mpsc::channel(); @@ -95,14 +90,12 @@ impl Indicator { if time_last_print.elapsed() < threshold { continue; } - let _ = stdout.execute(terminal::Clear(ClearType::CurrentLine)); let _ = write!(stdout, "{self}"); let _ = stdout.execute(cursor::RestorePosition); time_last_print = std::time::Instant::now(); } self.update_state(IndicatorState::PreparingOutput); - let _ = stdout.execute(terminal::Clear(ClearType::CurrentLine)); - let _ = write!(stdout, "{self}"); + let _ = execute!(stdout, terminal::Clear(ClearType::CurrentLine), Print(self)); }); let out = op(); From 37bdf6b6ae6a3ece82ae035ad8cf6d14208142fa Mon Sep 17 00:00:00 2001 From: solidiquis Date: Sun, 19 May 2024 11:04:09 -0700 Subject: [PATCH 3/3] progress indicator --- Cargo.lock | 247 +++++++++++++++++++++++++------------- Cargo.toml | 12 +- src/disk/mod.rs | 16 +-- src/file/mod.rs | 32 ++++- src/file/tree/filter.rs | 10 +- src/file/tree/mod.rs | 44 ++++--- src/file/tree/traverse.rs | 6 +- src/main.rs | 21 ++-- src/perf.rs | 21 ++-- src/progress.rs | 58 +++++++-- src/render/mod.rs | 8 +- src/render/row/long.rs | 32 ++--- src/render/row/mod.rs | 6 +- src/user/config/parse.rs | 12 +- src/user/mod.rs | 14 +-- 15 files changed, 341 insertions(+), 198 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a9e7e426..510e1503 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ahash" -version = "0.8.6" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "getrandom", @@ -17,9 +17,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -123,9 +123,9 @@ checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "bstr" -version = "1.3.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffdb39cb703212f3c11973452c2861b972f757b021158f3516ba10f2fa8b2c1" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", "serde", @@ -149,6 +149,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "chrono" version = "0.4.24" @@ -235,6 +241,31 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + [[package]] name = "crossterm" version = "0.27.0" @@ -262,12 +293,12 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.4.0" +version = "3.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a011bbe2c35ce9c1f143b7af6f94f29a167beb4cd1d29e6740ce836f723120e" +checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345" dependencies = [ "nix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -377,12 +408,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "getrandom" version = "0.2.9" @@ -396,15 +421,15 @@ dependencies = [ [[package]] name = "globset" -version = "0.4.10" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" dependencies = [ "aho-corasick", "bstr", - "fnv", "log", - "regex", + "regex-automata", + "regex-syntax", ] [[package]] @@ -450,17 +475,16 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" +checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" dependencies = [ + "crossbeam-deque", "globset", - "lazy_static", "log", "memchr", - "regex", + "regex-automata", "same-file", - "thread_local", "walkdir", "winapi-util", ] @@ -530,9 +554,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.141" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "linux-raw-sys" @@ -542,9 +566,9 @@ checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "linux-raw-sys" -version = "0.3.1" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" @@ -564,9 +588,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "lscolors" -version = "0.13.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dedc85d67baf5327114fad78ab9418f8893b1121c17d5538dd11005ad1ddf2" +checksum = "53304fff6ab1e597661eee37e42ea8c47a146fca280af902bb76bff8a896e523" dependencies = [ "ansi_term", "nu-ansi-term", @@ -574,9 +598,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "minimal-lexical" @@ -598,14 +622,14 @@ dependencies = [ [[package]] name = "nix" -version = "0.26.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "cfg-if", + "cfg_aliases", "libc", - "static_assertions", ] [[package]] @@ -620,12 +644,11 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.46.0" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "dd2800e1520bdc966782168a627aa5d1ad92e33b984bf7c7615d31280c83ff14" dependencies = [ - "overload", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -653,12 +676,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "parking_lot" version = "0.12.1" @@ -728,9 +745,21 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -739,9 +768,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "rustix" @@ -759,16 +788,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.7" +version = "0.38.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aae838e49b3d63e9274e1c01833cc8139d3fec468c3b84688c628f44b1ae11d" +checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "errno 0.3.1", - "io-lifetimes", "libc", - "linux-raw-sys 0.3.1", - "windows-sys 0.45.0", + "linux-raw-sys 0.4.14", + "windows-sys 0.48.0", ] [[package]] @@ -851,12 +879,6 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strip-ansi-escapes" version = "0.1.1" @@ -898,11 +920,11 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix 0.37.7", + "rustix 0.38.25", "windows-sys 0.48.0", ] @@ -926,16 +948,6 @@ dependencies = [ "syn", ] -[[package]] -name = "thread_local" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" -dependencies = [ - "cfg-if", - "once_cell", -] - [[package]] name = "toml" version = "0.8.8" @@ -1011,9 +1023,9 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.3.3" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -1152,6 +1164,15 @@ dependencies = [ "windows-targets 0.48.0", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -1182,6 +1203,22 @@ dependencies = [ "windows_x86_64_msvc 0.48.0", ] +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -1194,6 +1231,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -1206,6 +1249,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -1218,6 +1267,18 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -1230,6 +1291,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -1242,6 +1309,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -1254,6 +1327,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -1266,6 +1345,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + [[package]] name = "winnow" version = "0.5.28" @@ -1277,18 +1362,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.25" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd369a67c0edfef15010f980c3cbe45d7f651deac2cd67ce097cd801de16557" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.25" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 49cf6832..6e6ef1fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ wildcard_imports = "allow" obfuscated_if_else = "allow" [dependencies] -ahash = "0.8.6" +ahash = "0.8.11" ansi_term = "0.12.1" anyhow = "1.0.75" chrono = { version = "0.4.24", default-features = false, features = ["clock", "std"] } @@ -38,16 +38,16 @@ clap = { version = "4.5.4", features = ["derive"] } clap_complete = "4.1.1" config = { version = "0.14.0", default-features = false, features = ["toml"] } crossterm = "0.27.0" -ctrlc = "3.4.0" +ctrlc = "3.4.4" dirs = "5.0" errno = "0.3.1" filesize = "0.2.0" -ignore = "0.4.2" +ignore = "0.4.22" indextree = "4.6.0" -lscolors = { version = "0.13.0", features = ["ansi_term"] } +lscolors = { version = "0.17.0", features = ["ansi_term"] } once_cell = "1.19.0" -regex = "1.7.3" -terminal_size = "0.2.6" +regex = "1.10.4" +terminal_size = "0.3.0" thiserror = "1.0.40" toml = "0.8.8" diff --git a/src/disk/mod.rs b/src/disk/mod.rs index 80d86e94..70bfecf8 100644 --- a/src/disk/mod.rs +++ b/src/disk/mod.rs @@ -118,10 +118,8 @@ impl Usage { let word_count = std::fs::read_to_string(data.path()).map(|data| data.split_whitespace().count())?; - let word_count = u64::try_from(word_count).map_or_else( - |_| Self::WordCount(word_count as u64), - Self::WordCount, - ); + let word_count = u64::try_from(word_count) + .map_or_else(|_| Self::WordCount(word_count as u64), Self::WordCount); Ok(word_count) } @@ -140,10 +138,8 @@ impl Usage { let line_count = fs::read_to_string(data.path()).map(|data| data.lines().count())?; - let line_count = u64::try_from(line_count).map_or_else( - |_| Self::WordCount(line_count as u64), - Self::LineCount, - ); + let line_count = u64::try_from(line_count) + .map_or_else(|_| Self::WordCount(line_count as u64), Self::LineCount); Ok(line_count) } @@ -186,7 +182,7 @@ impl Display for Usage { let bytes = ($v as f64) / prefix.base_value(); write!(f, "{bytes:.FLOAT_PRECISION$} {prefix}B") } - }, + } BytePresentation::Si => { let prefix = prefix::Si::from($v); @@ -196,7 +192,7 @@ impl Display for Usage { let bytes = ($v as f64) / prefix.base_value(); write!(f, "{bytes:.1} {prefix}B") } - }, + } } }; } diff --git a/src/file/mod.rs b/src/file/mod.rs index 75650128..b3fae8d4 100644 --- a/src/file/mod.rs +++ b/src/file/mod.rs @@ -260,9 +260,7 @@ impl Accumulator { } pub fn increment(&mut self, ft: Option) { - let Some(file_type) = ft else { - return - }; + let Some(file_type) = ft else { return }; if file_type.is_file() { self.num_file += 1; @@ -294,7 +292,7 @@ impl Display for DisplayPath<'_> { let path = self.file.path(); path.strip_prefix(prefix) .map_or_else(|_| path.display(), |p| p.display()) - }, + } None => self.file.path().display(), }; @@ -307,3 +305,29 @@ impl Display for DisplayPath<'_> { } } } + +impl Display for Accumulator { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut displayed_count = Vec::new(); + + match self.num_dir { + n if n > 1 => displayed_count.push(format!("{} directories", n)), + 1 => displayed_count.push("1 directory".to_string()), + _ => (), + } + + match self.num_file { + n if n > 1 => displayed_count.push(format!("{} files", n)), + 1 => displayed_count.push("1 file".to_string()), + _ => (), + } + + match self.num_link { + n if n > 1 => displayed_count.push(format!("{} links", n)), + 1 => displayed_count.push("1 link".to_string()), + _ => (), + } + + writeln!(f, "{}", displayed_count.join(", ")) + } +} diff --git a/src/file/tree/filter.rs b/src/file/tree/filter.rs index 98e8ee13..09b0e2fd 100644 --- a/src/file/tree/filter.rs +++ b/src/file/tree/filter.rs @@ -90,7 +90,7 @@ impl Tree { match ft { FileType::Dir if matches!(layout, Layout::Tree | Layout::InvertedTree) => { filters.push(Box::new(|f| f.is_dir())) - }, + } FileType::Dir => filters.push(Box::new(|f| f.is_dir())), FileType::File => filters.push(Box::new(|f| f.is_file())), FileType::Symlink => filters.push(Box::new(|f| f.is_symlink())), @@ -152,7 +152,7 @@ impl Tree { to_remove .into_iter() .for_each(|n| n.remove(&mut self.arena)); - }, + } _ => { let to_remove = self .root_id @@ -167,7 +167,7 @@ impl Tree { to_remove .into_iter() .for_each(|n| n.remove_subtree(&mut self.arena)); - }, + } }; Ok(()) @@ -230,7 +230,7 @@ impl Tree { to_remove .into_iter() .for_each(|n| n.remove(&mut self.arena)); - }, + } _ => { let to_remove = self .root_id @@ -251,7 +251,7 @@ impl Tree { to_remove .into_iter() .for_each(|n| n.remove_subtree(&mut self.arena)); - }, + } } Ok(()) diff --git a/src/file/tree/mod.rs b/src/file/tree/mod.rs index 034adce8..650fba2b 100644 --- a/src/file/tree/mod.rs +++ b/src/file/tree/mod.rs @@ -43,12 +43,15 @@ pub enum TreeError { impl Tree { /// Like [`Tree::init`] but leverages parallelism for disk-reads and [`File`] initialization. pub fn init(ctx: &Context) -> Result<(Self, super::Accumulator, column::Metadata)> { - let (TransitionState { - mut arena, - mut branches, - mut column_metadata, - root_id, - }, accumulator)= Self::load(ctx)?; + let ( + TransitionState { + mut arena, + mut branches, + mut column_metadata, + root_id, + }, + accumulator, + ) = Self::load(ctx)?; let mut dir_stack = vec![root_id]; let mut inode_set = HashSet::default(); @@ -130,7 +133,7 @@ impl Tree { all_dirents .into_iter() .for_each(|n| root_id.append(n, &mut arena)); - }, + } _ => { for (dir_id, dirsize) in dirsize_map.into_iter() { let dir = arena[dir_id].get_mut(); @@ -148,7 +151,7 @@ impl Tree { } } } - }, + } }, None => { for (dir_id, dirsize) in dirsize_map.into_iter() { @@ -161,7 +164,7 @@ impl Tree { } } } - }, + } } column_metadata.update_size_width(arena[root_id].get(), ctx); @@ -171,13 +174,18 @@ impl Tree { Ok((tree, accumulator, column_metadata)) } - pub fn init_without_disk_usage(ctx: &Context) -> Result<(Self, super::Accumulator, column::Metadata)> { - let (TransitionState { - mut arena, - mut branches, - mut column_metadata, - root_id, - }, accumulator)= Self::load(ctx)?; + pub fn init_without_disk_usage( + ctx: &Context, + ) -> Result<(Self, super::Accumulator, column::Metadata)> { + let ( + TransitionState { + mut arena, + mut branches, + mut column_metadata, + root_id, + }, + accumulator, + ) = Self::load(ctx)?; #[cfg(unix)] macro_rules! update_metadata { @@ -211,7 +219,7 @@ impl Tree { #[cfg(unix)] update_metadata!(dirent_id); } - }, + } _ => { let dirs = arena .iter() @@ -244,7 +252,7 @@ impl Tree { } } }); - }, + } } let tree = Self { root_id, arena }; diff --git a/src/file/tree/traverse.rs b/src/file/tree/traverse.rs index dd725fd1..4bb3e091 100644 --- a/src/file/tree/traverse.rs +++ b/src/file/tree/traverse.rs @@ -76,16 +76,16 @@ impl ParallelVisitor for Visitor<'_> { Err(e) => { let _ = self.send(TraversalState::Error(e)); return WalkState::Continue; - }, + } }; match File::init(entry, self.ctx).into_report(ErrorCategory::Warning) { Ok(file) => { let _ = self.send(TraversalState::Ongoing(file)); - }, + } Err(e) => { let _ = self.send(TraversalState::Error(e)); - }, + } } WalkState::Continue diff --git a/src/main.rs b/src/main.rs index 42edb582..acd21a70 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,6 @@ #![cfg_attr(windows, feature(windows_by_handle))] use clap::CommandFactory; -use std::{ - io::stdout, - process::ExitCode, -}; +use std::{io::stdout, process::ExitCode}; /// Concerned with disk usage calculation and presentation. mod disk; @@ -25,7 +22,6 @@ mod progress; mod user; use user::Context; - /// For basic performance measurements when compiling for debug. #[cfg(debug_assertions)] #[macro_use] @@ -65,15 +61,8 @@ fn run() -> Result<()> { return Ok(()); } - // TODO: Use accumulator - let (mut file_tree, _accumulator, column_metadata) = if ctx.no_progress { - if ctx.suppress_size { - file::Tree::init_without_disk_usage(&ctx) - } else { - file::Tree::init(&ctx) - } - } else { - progress::Indicator::init().show_progress(|| { + let (mut file_tree, accumulator, column_metadata) = { + progress::init_indicator(&ctx)?.show_progress(|| { if ctx.suppress_size { file::Tree::init_without_disk_usage(&ctx) } else { @@ -88,6 +77,10 @@ fn run() -> Result<()> { Renderer::new(&ctx, &file_tree).render()?; + if !ctx.no_report { + println!("{accumulator}"); + } + #[cfg(debug_assertions)] { perf::finish_recording("crate::main"); diff --git a/src/perf.rs b/src/perf.rs index 4afe22d3..01a9e185 100644 --- a/src/perf.rs +++ b/src/perf.rs @@ -1,14 +1,13 @@ use ahash::HashMap; use once_cell::sync::Lazy; use std::{ - io, - fmt, + fmt, io, sync::{ mpsc::{self, Sender}, OnceLock, }, - time::{Duration, Instant}, thread::{self, JoinHandle}, + time::{Duration, Instant}, }; static mut GLOBAL_PERF_METRICS: OnceLock = OnceLock::new(); @@ -76,23 +75,19 @@ pub fn finish_recording(name: &str) { OUTPUT_NOTIFIER .get() .cloned() - .and_then(|tx| { - tx.send(Message::Recording(recording)).ok() - }); + .and_then(|tx| tx.send(Message::Recording(recording)).ok()); } pub fn output(mut w: T) { - OUTPUT_NOTIFIER.get().cloned().and_then(|tx| { - tx.send(Message::Terminate).ok() - }); + OUTPUT_NOTIFIER + .get() + .cloned() + .and_then(|tx| tx.send(Message::Terminate).ok()); let mut recordings = unsafe { GLOBAL_PERF_METRICS .get_mut() - .and_then(|PerfMetrics(join)| { - join.take() - .and_then(|h| h.join().ok()) - }) + .and_then(|PerfMetrics(join)| join.take().and_then(|h| h.join().ok())) .unwrap() }; diff --git a/src/progress.rs b/src/progress.rs index 1c30ceec..a38541a5 100644 --- a/src/progress.rs +++ b/src/progress.rs @@ -1,13 +1,13 @@ +use crate::error::prelude::*; use crossterm::{ - cursor, - execute, - ExecutableCommand, + cursor, execute, style::Print, terminal::{self, ClearType}, + ExecutableCommand, }; use std::{ - io::{self, Write}, fmt, + io::{self, IsTerminal, Write}, sync::{ mpsc::{self, Receiver, Sender}, OnceLock, @@ -21,12 +21,36 @@ pub const RENDER_INTERVAL_MS: u64 = 16; /// To notify the progress indicator static NOTIFIER: OnceLock> = OnceLock::new(); +pub fn init_indicator( + ctx: &crate::user::Context, +) -> Result>> { + if ctx.no_progress || !io::stdout().is_terminal() { + return Ok(Box::new(NoOpIndicator::new())); + } + + ctrlc::try_set_handler(|| { + let _ = io::stdout().execute(cursor::Show); + std::process::exit(libc::SIGINT + 128); + }) + .into_report(ErrorCategory::Internal)?; + + Ok(Box::new(Indicator::new())) +} + +pub trait ProgressIndicator { + fn show_progress(&mut self, op: F) -> T + where + F: FnOnce() -> T; +} + pub struct Indicator { counter: usize, state: IndicatorState, mailbox: Receiver, } +pub struct NoOpIndicator; + pub enum Message { Indexing(usize), DoneIndexing, @@ -40,7 +64,7 @@ enum IndicatorState { } impl Indicator { - pub fn init() -> Self { + fn new() -> Self { let (tx, rx) = mpsc::channel(); NOTIFIER.set(tx).unwrap(); @@ -71,9 +95,18 @@ impl Indicator { let _ = n.send(Message::DoneIndexing); } } +} + +impl NoOpIndicator { + fn new() -> Self { + NoOpIndicator {} + } +} - pub fn show_progress(mut self, op: F) -> T - where F: FnOnce() -> T +impl ProgressIndicator for Indicator { + fn show_progress(&mut self, op: F) -> T + where + F: FnOnce() -> T, { let _ = io::stdout().execute(cursor::Hide); @@ -83,7 +116,7 @@ impl Indicator { let mut time_last_print = std::time::Instant::now(); let mut stdout = io::stdout(); let _ = stdout.execute(cursor::SavePosition); - + while let Ok(Message::Indexing(count)) = self.mailbox.recv() { self.counter = count; @@ -110,6 +143,15 @@ impl Indicator { } } +impl ProgressIndicator for NoOpIndicator { + fn show_progress(&mut self, op: F) -> T + where + F: FnOnce() -> T, + { + op() + } +} + impl fmt::Display for Indicator { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.state { diff --git a/src/render/mod.rs b/src/render/mod.rs index a32364bc..243812c5 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -82,7 +82,7 @@ impl<'a> Renderer<'a> { } (node, node_id, depth) - }, + } NodeEdge::End(node_id) => { let node = arena[node_id].get(); let depth = node.depth(); @@ -95,7 +95,7 @@ impl<'a> Renderer<'a> { } } continue; - }, + } }; let prefix = format!( @@ -153,7 +153,7 @@ impl<'a> Renderer<'a> { } (node, node_id, depth) - }, + } NodeEdge::End(node_id) => { let node = arena[node_id].get(); let depth = node.depth(); @@ -162,7 +162,7 @@ impl<'a> Renderer<'a> { inherited_prefix_components.pop(); } continue; - }, + } }; let prefix = format!( diff --git a/src/render/row/long.rs b/src/render/row/long.rs index cca17de3..1616febb 100644 --- a/src/render/row/long.rs +++ b/src/render/row/long.rs @@ -90,7 +90,7 @@ impl Display for Format<'_> { f, "{attrs:max_owner_width$} {timestamp:>max_time_width$}" ) - }, + } (true, false, false, false) => { let column::Metadata { max_owner_width, @@ -100,7 +100,7 @@ impl Display for Format<'_> { } = column_metadata; let group = unix_attrs.group().unwrap_or(PLACEHOLDER); write!(f, "{attrs:max_owner_width$} {group:>max_group_width$} {timestamp:>max_time_width$}") - }, + } (false, true, false, false) => { let column::Metadata { max_owner_width, @@ -110,7 +110,7 @@ impl Display for Format<'_> { } = column_metadata; let Inode { ino, .. } = self.file.inode()?; write!(f, "{ino:max_ino_width$} {attrs:max_owner_width$} {timestamp:>max_time_width$}") - }, + } (false, false, true, false) => { let column::Metadata { max_owner_width, @@ -120,7 +120,7 @@ impl Display for Format<'_> { } = column_metadata; let Inode { nlink, .. } = self.file.inode()?; write!(f, "{attrs:max_nlink_width$} {owner:>max_owner_width$} {timestamp:>max_time_width$}") - }, + } (true, true, false, false) => { let column::Metadata { max_owner_width, @@ -132,7 +132,7 @@ impl Display for Format<'_> { let group = unix_attrs.group().unwrap_or(PLACEHOLDER); let Inode { ino, .. } = self.file.inode()?; write!(f, "{ino:max_ino_width$} {attrs:max_owner_width$} {group:>max_group_width$} {timestamp:>max_time_width$}") - }, + } (true, false, true, false) => { let column::Metadata { max_owner_width, @@ -144,7 +144,7 @@ impl Display for Format<'_> { let group = unix_attrs.group().unwrap_or(PLACEHOLDER); let Inode { nlink, .. } = self.file.inode()?; write!(f, "{attrs:max_nlink_width$} {owner:>max_owner_width$} {group:>max_group_width$} {timestamp:>max_time_width$}") - }, + } (false, true, true, false) => { let column::Metadata { max_owner_width, @@ -155,7 +155,7 @@ impl Display for Format<'_> { } = column_metadata; let Inode { ino, nlink, .. } = self.file.inode()?; write!(f, "{ino:max_ino_width$} {attrs:max_nlink_width$} {owner:>max_owner_width$} {timestamp:>max_time_width$}") - }, + } (true, true, true, false) => { let column::Metadata { max_owner_width, @@ -168,7 +168,7 @@ impl Display for Format<'_> { let group = unix_attrs.group().unwrap_or(PLACEHOLDER); let Inode { ino, nlink, .. } = self.file.inode()?; write!(f, "{ino:max_ino_width$} {attrs:max_nlink_width$} {owner:>max_owner_width$} {group:>max_group_width$} {timestamp:>max_time_width$}") - }, + } (false, false, false, true) => { let column::Metadata { max_owner_width, @@ -176,7 +176,7 @@ impl Display for Format<'_> { .. } = column_metadata; write!(f, "{file_mode:0OCTAL_PERMISSIONS_WIDTH$o} {attrs:max_owner_width$} {timestamp:>max_time_width$}") - }, + } (true, false, false, true) => { let column::Metadata { max_owner_width, @@ -186,7 +186,7 @@ impl Display for Format<'_> { } = column_metadata; let group = unix_attrs.group().unwrap_or(PLACEHOLDER); write!(f, "{file_mode:0OCTAL_PERMISSIONS_WIDTH$o} {attrs:max_owner_width$} {group:>max_group_width$} {timestamp:>max_time_width$}") - }, + } (true, true, false, true) => { let column::Metadata { max_owner_width, @@ -198,7 +198,7 @@ impl Display for Format<'_> { let group = unix_attrs.group().unwrap_or(PLACEHOLDER); let Inode { ino, .. } = self.file.inode()?; write!(f, "{ino:max_ino_width$} {file_mode:0OCTAL_PERMISSIONS_WIDTH$o} {attrs:max_owner_width$} {group:>max_group_width$} {timestamp:>max_time_width$}") - }, + } (true, false, true, true) => { let column::Metadata { max_owner_width, @@ -210,7 +210,7 @@ impl Display for Format<'_> { let group = unix_attrs.group().unwrap_or(PLACEHOLDER); let Inode { nlink, .. } = self.file.inode()?; write!(f, "{file_mode:0OCTAL_PERMISSIONS_WIDTH$o} {attrs:max_nlink_width$} {owner:>max_owner_width$} {group:>max_group_width$} {timestamp:>max_time_width$}") - }, + } (false, false, true, true) => { let column::Metadata { max_owner_width, @@ -220,7 +220,7 @@ impl Display for Format<'_> { } = column_metadata; let Inode { nlink, .. } = self.file.inode()?; write!(f, "{file_mode:0OCTAL_PERMISSIONS_WIDTH$o} {attrs:max_nlink_width$} {owner:>max_owner_width$} {timestamp:>max_time_width$}") - }, + } (false, true, false, true) => { let column::Metadata { max_owner_width, @@ -230,7 +230,7 @@ impl Display for Format<'_> { } = column_metadata; let Inode { ino, .. } = self.file.inode()?; write!(f, "{ino:max_ino_width$} {file_mode:0OCTAL_PERMISSIONS_WIDTH$o} {attrs:max_owner_width$} {timestamp:>max_time_width$}") - }, + } (false, true, true, true) => { let column::Metadata { max_owner_width, @@ -241,7 +241,7 @@ impl Display for Format<'_> { } = column_metadata; let Inode { ino, nlink, .. } = self.file.inode()?; write!(f, "{ino:max_ino_width$} {file_mode:0OCTAL_PERMISSIONS_WIDTH$o} {attrs:max_nlink_width$} {owner:>max_owner_width$} {timestamp:>max_time_width$}") - }, + } (true, true, true, true) => { let column::Metadata { max_owner_width, @@ -254,7 +254,7 @@ impl Display for Format<'_> { let group = unix_attrs.group().unwrap_or(PLACEHOLDER); let Inode { ino, nlink, .. } = self.file.inode()?; write!(f, "{ino:max_ino_width$} {file_mode:0OCTAL_PERMISSIONS_WIDTH$o} {attrs:max_nlink_width$} {owner:>max_owner_width$} {group:>max_group_width$} {timestamp:>max_time_width$}") - }, + } } } } diff --git a/src/render/row/mod.rs b/src/render/row/mod.rs index 2953d581..4ae8b130 100644 --- a/src/render/row/mod.rs +++ b/src/render/row/mod.rs @@ -74,7 +74,7 @@ pub fn formatter<'a>(buf: &'a mut String, ctx: &'a Context) -> Result match (ctx.long.long, ctx.suppress_size) { (false, false) => Ok(Box::new(|file, prefix| { let size = format!("{}", file.size()); @@ -160,7 +160,7 @@ pub fn formatter<'a>(buf: &'a mut String, ctx: &'a Context) -> Result { if ctx.suppress_size { Ok(Box::new(|file, prefix| { @@ -185,6 +185,6 @@ pub fn formatter<'a>(buf: &'a mut String, ctx: &'a Context) -> Result) -> Result Some(name) => match config.remove(&format!("--{name}")) { Some(Value::Table(sub_table)) => { config = sub_table; - }, + } _ => return Err(Error::TableNotFound(name.to_string())), }, None => remove_sub_tables(&mut config), @@ -58,16 +58,16 @@ fn into_args(conf: TomlConfig) -> Result { args.push(farg) } } - }, + } Value::Boolean(arg) if arg => { args.push(arg_name.clone()); - }, + } _ => { if let Some(farg) = fmt_arg(param) { args.push(arg_name.clone()); args.push(farg) } - }, + } } } @@ -129,11 +129,11 @@ fn deep_transform_keys(toml: TomlConfig, transformer: KeyTransformer) -> Option< dfs_stack_dst.push((transformed_key, toml::map::Map::default())); dfs_stack_src.push(value); continue 'outer; - }, + } _ => { let transformed_key = transformer(key); copy_dst.insert(transformed_key, value); - }, + } }, None => continue, } diff --git a/src/user/mod.rs b/src/user/mod.rs index d772f857..ee4f9062 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -1,11 +1,7 @@ use crate::error::prelude::*; use ahash::HashMap; use clap::{parser::ValueSource, ArgMatches, Args, CommandFactory, FromArgMatches, Parser}; -use std::{ - env, - fs, - path::PathBuf, -}; +use std::{env, fs, path::PathBuf}; /// Enum definitions for enumerated command-line arguments. pub mod args; @@ -93,6 +89,10 @@ pub struct Context { #[arg(long)] pub no_progress: bool, + /// Hide the file count at the end of the output + #[arg(long)] + pub no_report: bool, + #[command(flatten)] pub search: Search, @@ -203,7 +203,7 @@ impl Context { }; if let Some(dir_arg) = clargs.get_one::("dir").cloned() { - ctx.dir = Some(dir_arg) + ctx.dir = Some(dir_arg) } else { let current_dir = Self::get_current_dir()?; ctx.dir = Some(current_dir); @@ -322,7 +322,7 @@ impl Context { // Prioritize config argument over default (ValueSource::DefaultValue, ValueSource::CommandLine) => { push_args(arg_name, arg_id_str, config) - }, + } // Prioritize user argument in all other cases _ => push_args(arg_name, arg_id_str, clargs),