diff options
author | Canop <cano.petrole@gmail.com> | 2019-08-02 15:12:11 +0200 |
---|---|---|
committer | Canop <cano.petrole@gmail.com> | 2019-08-02 15:12:11 +0200 |
commit | f1ec96bcf72f8cff71bf1caca55f960c84ca4996 (patch) | |
tree | a163107a890751e0a43ff2db09feca1b30643363 | |
parent | 25eb72fd9361f65b0b2f11e9337654637eb17f73 (diff) |
whalespotting modev0.9.3
This mode is activated by the --sizes option and makes a few changes
to the broot logic in order to ease size survey:
- sizes computed and displayed
- only one level of tree
- size based ordering
- hidden files and gitignored ones are shown (by default)
-rw-r--r-- | CHANGELOG.md | 4 | ||||
-rw-r--r-- | Cargo.lock | 8 | ||||
-rw-r--r-- | Cargo.toml | 4 | ||||
-rw-r--r-- | README.md | 16 | ||||
-rw-r--r-- | img/20190802-sizes.png | bin | 0 -> 38176 bytes | |||
-rw-r--r-- | src/browser_states.rs | 4 | ||||
-rw-r--r-- | src/cli.rs | 9 | ||||
-rw-r--r-- | src/displayable_tree.rs | 40 | ||||
-rw-r--r-- | src/file_sizes/file_sizes_default.rs (renamed from src/file_sizes/file_sizes_windows.rs) | 10 | ||||
-rw-r--r-- | src/file_sizes/file_sizes_unix.rs | 11 | ||||
-rw-r--r-- | src/file_sizes/mod.rs | 33 | ||||
-rw-r--r-- | src/flat_tree.rs | 25 | ||||
-rw-r--r-- | src/skin.rs | 9 | ||||
-rw-r--r-- | src/tree_build.rs | 6 | ||||
-rw-r--r-- | website/docs/img/20190802-sizes.png | bin | 0 -> 38176 bytes | |||
-rw-r--r-- | website/docs/index.md | 13 |
16 files changed, 132 insertions, 60 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 850f06a..a033973 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +<a name="v0.9.3"></a> +### v0.9.3 - 2019-08-02 +Launching broot with `--sizes` now sets a set of features enabling fast "whale spotting" navigation + <a name="v0.9.2"></a> ### v0.9.2 - 2019-07-31 Fix non consistent builds due to lack of precise versionning in crossterm subcrate versionning @@ -86,7 +86,7 @@ dependencies = [ [[package]] name = "broot" -version = "0.9.1" +version = "0.9.3" dependencies = [ "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -106,7 +106,7 @@ dependencies = [ "opener 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "simplelog 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "termimad 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "termimad 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "umask 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "users 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -595,7 +595,7 @@ dependencies = [ [[package]] name = "termimad" -version = "0.6.2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "crossbeam 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -781,7 +781,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum syn 0.15.30 (registry+https://github.com/rust-lang/crates.io-index)" = "66c8865bf5a7cbb662d8b011950060b3c8743dca141b054bf7195b20d314d8e2" "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" "checksum term 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" -"checksum termimad 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a1f13066780ec95e8d43f336b92d770fbef329c1b19d8ed78c7c015d2b0ba091" +"checksum termimad 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8e719aff75d732980c604d2c5a28f2d5c532f1e72cbd6eee5bae3a66547226a8" "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" @@ -1,6 +1,6 @@ [package] name = "broot" -version = "0.9.2" +version = "0.9.3" authors = ["dystroy <denys.seguret@gmail.com>"] repository = "https://github.com/Canop/broot" description = "Fuzzy Search + tree + cd" @@ -30,7 +30,7 @@ crossterm_style = "=0.3.3" crossterm_terminal = "=0.2.4" crossterm_utils = "=0.2.3" umask = "0.1.4" -termimad = "0.6.2" +termimad = "0.6.4" #termimad = { path = "../termimad" } [target.'cfg(unix)'.dependencies] @@ -54,18 +54,22 @@ For example, assuming you look for your one file whose name contains `abc` in a And if you look for a filename *ending* in `abc` then you may anchor the regex: `abc$/`. -### See what takes space: - -![size](img/20190128-only-folders-with-size.png) - -To toggle size display, type `:s`. Sizes are computed in the background, you don't have to wait for them when you navigate. - ### Apply a personal shortcut to a file: ![size](img/20190128-edit.png) Just find the file you want to edit with a few keystrokes, type `:e`, then `<enter>` (you should define your preferred editor, see [documentation](documentation.md#verbs)). +### See what takes space: + +![size](img/20190802-sizes.png) + +If you start broot with the `--sizes` option, you get a mode tailored to "whale spotting" nagigation, making it easy to determine what files or folders take space. + +And you keep all broot tools, like filtering or the ability to delete or open files and directories. + +Sizes are computed in the background, you don't have to wait for them when you navigate. + ### More... See **[Broot's web site](https://dystroy.org/broot)** for instructions regarding installation and usage. diff --git a/img/20190802-sizes.png b/img/20190802-sizes.png Binary files differnew file mode 100644 index 0000000..f1a1c2b --- /dev/null +++ b/img/20190802-sizes.png diff --git a/src/browser_states.rs b/src/browser_states.rs index 0061b13..527e611 100644 --- a/src/browser_states.rs +++ b/src/browser_states.rs @@ -169,9 +169,9 @@ impl BrowserState { screen.write_status_text( if tree.selection == 0 { if has_pattern { - "Hit <esc> to go back, <enter> to go up, '?' for help, or a few letters to search" - } else { "Hit <esc> to remove the filter, <enter> to go up, '?' for help" + } else { + "Hit <esc> to go back, <enter> to go up, '?' for help, or a few letters to search" } } else { let line = &tree.lines[tree.selection]; @@ -10,7 +10,7 @@ use crossterm_style::Color; use termimad::{Alignment, MadSkin}; use crate::errors::{ProgramError, TreeBuildError}; -use crate::tree_options::TreeOptions; +use crate::tree_options::{OptionBool, TreeOptions}; pub struct AppLaunchArgs { pub root: PathBuf, // what should be the initial root @@ -132,9 +132,14 @@ pub fn read_launch_args() -> Result<AppLaunchArgs, ProgramError> { } let root = root.canonicalize()?; let mut tree_options = TreeOptions::default(); + tree_options.show_sizes = cli_args.is_present("sizes"); + if tree_options.show_sizes { + // by default, if we're asked to show the size, we show all files + tree_options.show_hidden = true; + tree_options.respect_git_ignore = OptionBool::No; + } tree_options.only_folders = cli_args.is_present("only-folders"); tree_options.show_hidden = cli_args.is_present("hidden"); - tree_options.show_sizes = cli_args.is_present("sizes"); tree_options.show_dates = cli_args.is_present("dates"); tree_options.show_permissions = cli_args.is_present("permissions"); if let Some(respect_ignore) = cli_args.value_of("gitignore") { diff --git a/src/displayable_tree.rs b/src/displayable_tree.rs index bb4982e..cc1d8fa 100644 --- a/src/displayable_tree.rs +++ b/src/displayable_tree.rs @@ -1,5 +1,6 @@ use std::fmt; use std::time::SystemTime; +use crossterm_style::{ObjectStyle}; use crossterm_terminal::{ClearType, Terminal}; use chrono::offset::Local; use chrono::DateTime; @@ -17,6 +18,7 @@ use crate::skin::{Skin, SkinEntry}; use crossterm_style::{Color, Colored}; use crossterm_cursor::TerminalCursor; +use termimad::ProgressBar; /// A tree wrapper implementing Display /// which can be used either @@ -50,6 +52,24 @@ impl<'s, 't> DisplayableTree<'s, 't> { } } + fn name_style( + &self, + line: &TreeLine, + ) -> &ObjectStyle { + match &line.line_type { + LineType::Dir => &self.skin.directory, + LineType::File => { + if line.is_exe() { + &self.skin.exe + } else { + &self.skin.file + } + } + LineType::SymLinkToFile(_) | LineType::SymLinkToDir(_) => &self.skin.link, + LineType::Pruning => &self.skin.pruning, + } + } + fn write_line_size( &self, f: &mut fmt::Formatter<'_>, @@ -57,21 +77,15 @@ impl<'s, 't> DisplayableTree<'s, 't> { total_size: Size, ) -> fmt::Result { if let Some(s) = line.size { - let dr: usize = s.discrete_ratio(total_size, 8) as usize; - let s: Vec<char> = s.to_string().chars().collect(); - let mut bar = String::new(); - for i in 0..dr { - bar.push(if i < s.len() { s[i] } else { ' ' }); - } - self.skin.size_bar.write(f, &bar)?; - let mut no_bar = String::new(); - for i in dr..8 { - no_bar.push(if i < s.len() { s[i] } else { ' ' }); + let pb = ProgressBar::new(s.part_of(total_size), 10); + let style = self.name_style(line); + if let Some(fg) = style.fg_color { + write!(f, "{}{:>5} {:<10} ", Colored::Fg(fg), s.to_string(), pb) + } else { + write!(f, "{:>5} {:<10} ", s.to_string(), pb) } - self.skin.size_no_bar.write(f, &no_bar)?; - write!(f, " ") } else { - self.skin.tree.write(f, "──────── ") + self.skin.tree.write(f, "──────────────── ") } } diff --git a/src/file_sizes/file_sizes_windows.rs b/src/file_sizes/file_sizes_default.rs index 8b4d6c1..bed8584 100644 --- a/src/file_sizes/file_sizes_windows.rs +++ b/src/file_sizes/file_sizes_default.rs @@ -1,3 +1,5 @@ +//! size computation for non linux + use crate::task_sync::TaskLifetime; use crossbeam::channel::unbounded; use crossbeam::sync::WaitGroup; @@ -64,3 +66,11 @@ pub fn compute_dir_size(path: &Path, tl: &TaskLifetime) -> Option<u64> { let size: u64 = size as u64; Some(size) } + + +pub fn compute_file_size(path: &Path) -> u64 { + match fs::metadata(path) { + Ok(m) => m.len(), + Err(_) => 0, + } +} diff --git a/src/file_sizes/file_sizes_unix.rs b/src/file_sizes/file_sizes_unix.rs index a99d81f..19b99f6 100644 --- a/src/file_sizes/file_sizes_unix.rs +++ b/src/file_sizes/file_sizes_unix.rs @@ -3,6 +3,7 @@ use crossbeam::channel::unbounded; use crossbeam::sync::WaitGroup; use std::collections::HashSet; use std::fs; +//use std::os::linux::fs::MetadataExt; use std::os::unix::fs::MetadataExt; use std::path::{Path, PathBuf}; use std::sync::{atomic::{AtomicIsize, AtomicU64, Ordering}, Arc, Mutex}; @@ -48,7 +49,8 @@ pub fn compute_dir_size(path: &Path, tl: &TaskLifetime) -> Option<u64> { continue; // let's not add the size } } - size.fetch_add(md.len(), Ordering::Relaxed); + let file_size = if md.blocks()==0 { 0 } else { md.size() }; + size.fetch_add(file_size, Ordering::Relaxed); } } } @@ -72,3 +74,10 @@ pub fn compute_dir_size(path: &Path, tl: &TaskLifetime) -> Option<u64> { let size = size.load(Ordering::Relaxed); Some(size) } + +pub fn compute_file_size(path: &Path) -> u64 { + match fs::metadata(path) { + Ok(m) => if m.blocks()==0 { 0 } else { m.size() }, + Err(_) => 0, + } +} diff --git a/src/file_sizes/mod.rs b/src/file_sizes/mod.rs index 43bd59c..be1042e 100644 --- a/src/file_sizes/mod.rs +++ b/src/file_sizes/mod.rs @@ -5,7 +5,6 @@ /// twice an inode. use crate::task_sync::TaskLifetime; use std::collections::HashMap; -use std::fs; use std::ops::AddAssign; use std::path::{Path, PathBuf}; use std::sync::Mutex; @@ -18,10 +17,7 @@ pub struct Size(u64); impl Size { pub fn from_file(path: &Path) -> Size { - Size(match fs::metadata(path) { - Ok(m) => m.len(), - Err(_) => 0, - }) + Size(compute_file_size(path)) } /// Return the size of the directory, either by computing it of by @@ -51,17 +47,17 @@ impl Size { pub fn to_string(self) -> String { let mut v = self.0; let mut i = 0; - while v >= 9000 && i < SIZE_NAMES.len() - 1 { + while v >= 5000 && i < SIZE_NAMES.len() - 1 { v >>= 10; i += 1; } format!("{}{}", v, &SIZE_NAMES[i]) } - pub fn discrete_ratio(self, max: Size, r: u64) -> u64 { - if max.0 == 0 || self.0 == 0 { - 0 + pub fn part_of(&self, total: Size) -> f32 { + if total.0 <= 0 { + 0.0 } else { - ((r as f64) * (self.0 as f64).cbrt() / (max.0 as f64).cbrt()).round() as u64 + self.0 as f32 / total.0 as f32 } } } @@ -94,10 +90,19 @@ fn compute_dir_size(path: &Path, tl: &TaskLifetime) -> Option<u64> { file_sizes_unix::compute_dir_size(path, tl) } -#[cfg(windows)] -mod file_sizes_windows; +#[cfg(unix)] +fn compute_file_size(path: &Path) -> u64 { + file_sizes_unix::compute_file_size(path) +} -#[cfg(windows)] +#[cfg(not(unix))] +mod file_sizes_default; + +#[cfg(not(unix))] fn compute_dir_size(path: &Path, tl: &TaskLifetime) -> Option<u64> { - file_sizes_windows::compute_dir_size(path, tl) + file_sizes_default::compute_dir_size(path, tl) +} +#[cfg(not(unix))] +fn compute_file_size(path: &Path) -> u64 { + file_sizes_default::compute_file_size(path) } diff --git a/src/flat_tree.rs b/src/flat_tree.rs index 61c6b76..61eb68b 100644 --- a/src/flat_tree.rs +++ b/src/flat_tree.rs @@ -1,6 +1,6 @@ /// In the flat_tree structure, every "node" is just a line, there's /// no link from a child to its parent or from a parent to its children. -use std::cmp::{self, Ordering}; +use std::cmp::{self, Ord, PartialOrd, Ordering}; use std::fs; use std::mem; use std::path::{Path, PathBuf}; @@ -157,11 +157,8 @@ impl PartialOrd for TreeLine { impl Tree { pub fn refresh(&mut self, page_height: usize) -> Result<(), errors::TreeBuildError> { - let builder = - TreeBuilder::from(self.root().to_path_buf(), self.options.clone(), page_height)?; - debug!("remove 3"); + let builder = TreeBuilder::from(self.root().to_path_buf(), self.options.clone(), page_height)?; let mut tree = builder.build(&TaskLifetime::unlimited()).unwrap(); // should not fail - debug!("remove 4"); // we save the old selection to try restore it let selected_path = self.selected_line().path.to_path_buf(); mem::swap(&mut self.lines, &mut tree.lines); @@ -174,8 +171,8 @@ impl Tree { // - sort the lines // - compute left branchs pub fn after_lines_changed(&mut self) { - // we sort the lines - self.lines.sort(); + // we sort the lines (this is mandatory to avoid crashes) + self.lines[1..].sort(); for i in 1..self.lines.len() { for d in 0..self.lines[i].left_branchs.len() { @@ -372,15 +369,29 @@ impl Tree { self.lines[i].size = Some(Size::from_file(&self.lines[i].path)); } } + self.sort_siblings_by_size(); } pub fn fetch_some_missing_dir_size(&mut self, tl: &TaskLifetime) { for i in 1..self.lines.len() { if self.lines[i].size.is_none() && self.lines[i].line_type == LineType::Dir { self.lines[i].size = Size::from_dir(&self.lines[i].path, tl); + self.sort_siblings_by_size(); return; } } } + /// Sort files according to their size + /// + /// Warning: must not be called if there's more than one level displayed! + /// (a better sort should be devised but it's unsure whether it would be + /// readable enough) + fn sort_siblings_by_size(&mut self) { + self.lines[1..].sort_by(|a, b| { + let asize = a.size.map_or(0, |s| s.into()); + let bsize = b.size.map_or(0, |s| s.into()); + bsize.cmp(&asize) + }); + } pub fn total_size(&self) -> Size { if let Some(size) = self.lines[0].size { // if the real total size is computed, it's in the root line diff --git a/src/skin.rs b/src/skin.rs index 9d92c24..fd285eb 100644 --- a/src/skin.rs +++ b/src/skin.rs @@ -94,19 +94,18 @@ Skin! { pruning: gray(17), None; {Italic} permissions: gray(15), None; dates: ansi(109), None; - selected_line: None, gray(3); - size_bar: Some(White), Some(DarkBlue); - size_no_bar: gray(15), gray(2); + selected_line: None, gray(4); + size_bar: Some(White), gray(2); char_match: Some(Green), None; file_error: Some(Red), None; flag_label: gray(15), gray(1); - flag_value: Some(Blue), gray(1); + flag_value: Some(Blue), gray(1); {Bold} input: Some(White), None; spinner: gray(10), gray(2); status_error: Some(White), Some(Red); status_normal: Some(White), gray(2); scrollbar_track: gray(7), None; - scrollbar_thumb: ansi(178), None; + scrollbar_thumb: gray(22), None; help_paragraph: gray(20), None; help_bold: ansi(178), None; {Bold} help_italic: ansi(229), None; {Italic} diff --git a/src/tree_build.rs b/src/tree_build.rs index c9c47f5..9761bf6 100644 --- a/src/tree_build.rs +++ b/src/tree_build.rs @@ -369,6 +369,12 @@ impl TreeBuilder { } } else { // this depth is finished, we must go deeper + if self.options.show_sizes { + // both for technical reasons (bad sort) and ergonomics + // ones (it proved to be hard to read), we don't want + // a deep tree when looking at sizes. + break; + } if next_level_dirs.is_empty() { // except there's nothing deeper break; diff --git a/website/docs/img/20190802-sizes.png b/website/docs/img/20190802-sizes.png Binary files differnew file mode 100644 index 0000000..f1a1c2b --- /dev/null +++ b/website/docs/img/20190802-sizes.png diff --git a/website/docs/index.md b/website/docs/index.md index 1ffac21..66a8d32 100644 --- a/website/docs/index.md +++ b/website/docs/index.md @@ -44,7 +44,7 @@ Once the file you want is selected you can # Manipulate your files -![size](img/20190306-mv.png) +![mv](img/20190306-mv.png) Most often you move your files in the blind. You do a few `ls` before, then your manipulation, and maybe you check after. @@ -58,11 +58,16 @@ Move, copy, rm, mkdir, are built in and you can add your own shortcuts. Just find the file you want to edit with a few keystrokes, type `:e`, then `<enter>` (you should define your preferred editor, see [documentation](documentation/usage.md#verbs)). -# See what takes space +### See what takes space: -![size](img/20190128-only-folders-with-size.png) +![size](img/20190802-sizes.png) + +If you start broot with the `--sizes` option, you get a mode tailored to "whale spotting" nagigation, making it easy to determine what files or folders take space. + +And you keep all broot tools, like filtering or the ability to delete or open files and directories. + +Sizes are computed in the background, you don't have to wait for them when you navigate. -To toggle size display, type `:s`. Sizes are computed in the background, you don't have to wait for them when you navigate. # More... |