From 7485ae991b2fb478a78ab4c06307e3b1785dd13a Mon Sep 17 00:00:00 2001 From: Noah Too Date: Sat, 10 Jun 2023 21:42:33 +0300 Subject: [PATCH] feat: Display directory size add option to display lazily loaded directory sizes --- config/joshuto.toml | 2 ++ docs/configuration/joshuto.toml.md | 5 +++++ src/commands/cursor_move.rs | 26 +++++++++++++++++++++++++ src/config/general/display_raw.rs | 7 ++++++- src/config/option/display_option.rs | 17 ++++++++++++++++ src/fs/dirlist.rs | 22 +++++++++++++++++++-- src/fs/entry.rs | 20 +++++++++++++++++++ src/fs/metadata.rs | 10 +++++++++- src/history.rs | 18 ++++++++++++++++- src/ui/widgets/tui_dirlist_detailed.rs | 27 ++++++++++++++------------ 10 files changed, 137 insertions(+), 17 deletions(-) diff --git a/config/joshuto.toml b/config/joshuto.toml index d1f243957..37be196b6 100644 --- a/config/joshuto.toml +++ b/config/joshuto.toml @@ -21,6 +21,8 @@ show_icons = true tilde_in_titlebar = true # none, absolute, relative line_number_style = "none" +# count, size +size_mode = "count" [display.sort] # lexical, mtime, natural diff --git a/docs/configuration/joshuto.toml.md b/docs/configuration/joshuto.toml.md index 331378062..2ef433993 100644 --- a/docs/configuration/joshuto.toml.md +++ b/docs/configuration/joshuto.toml.md @@ -56,6 +56,11 @@ tilde_in_titlebar = true # - relative line_number_style = "none" +# Options include: +# - count (use number of items in directory) +# - size (use the total size of items in a directory) +size_mode = "count" + # Configurations related to file sorting [display.sort] # Options include diff --git a/src/commands/cursor_move.rs b/src/commands/cursor_move.rs index ccb8d988d..11b62ed9a 100644 --- a/src/commands/cursor_move.rs +++ b/src/commands/cursor_move.rs @@ -36,6 +36,32 @@ pub fn lazy_load_directory_size(context: &mut AppContext) { curr_entry.metadata.update_directory_size(s); } } + + if let Some(curr_entry) = context + .tab_context_ref() + .curr_tab_ref() + .curr_list_ref() + .and_then(|l| l.curr_entry_ref()) + { + let old_len = curr_entry.metadata.len(); + let history = context.tab_context_ref().curr_tab_ref().history_ref(); + match history + .get(curr_entry.file_path()) + .map(|d| d.contents.iter().map(|e| e.metadata.len()).sum()) + { + Some(len) if old_len != len => { + if let Some(curr_entry) = context + .tab_context_mut() + .curr_tab_mut() + .curr_list_mut() + .and_then(|l| l.curr_entry_mut()) + { + curr_entry.metadata.update_len(len); + } + } + _ => (), + } + } } pub fn cursor_move(context: &mut AppContext, new_index: usize) { diff --git a/src/config/general/display_raw.rs b/src/config/general/display_raw.rs index 295d30fc3..4702455cc 100644 --- a/src/config/general/display_raw.rs +++ b/src/config/general/display_raw.rs @@ -4,7 +4,7 @@ use serde_derive::Deserialize; use tui::layout::Constraint; use crate::config::option::{ - DisplayMode, DisplayOption, LineMode, LineNumberStyle, TabDisplayOption, + DisplayMode, DisplayOption, LineMode, LineNumberStyle, SizeMode, TabDisplayOption, }; use super::sort_raw::SortOptionRaw; @@ -59,6 +59,9 @@ pub struct DisplayOptionRaw { #[serde(default)] pub line_number_style: String, + + #[serde(default)] + pub size_mode: String, } impl std::default::Default for DisplayOptionRaw { @@ -75,6 +78,7 @@ impl std::default::Default for DisplayOptionRaw { sort_options: SortOptionRaw::default(), tilde_in_titlebar: true, line_number_style: "none".to_string(), + size_mode: "count".to_string(), } } } @@ -126,6 +130,7 @@ impl From for DisplayOption { sort_options: raw.sort_options.into(), // todo: make default line mode configurable linemode: LineMode::Size, + size_mode: SizeMode::from_str(&raw.size_mode), ..Default::default() }, } diff --git a/src/config/option/display_option.rs b/src/config/option/display_option.rs index eaeb97111..5221d17d2 100644 --- a/src/config/option/display_option.rs +++ b/src/config/option/display_option.rs @@ -46,6 +46,7 @@ pub struct TabDisplayOption { pub dirlist_options: HashMap, pub sort_options: SortOption, pub linemode: LineMode, + pub size_mode: SizeMode, } #[derive(Clone, Copy, Debug)] @@ -55,6 +56,13 @@ pub enum LineNumberStyle { Absolute, } +#[derive(Clone, Debug, Default)] +pub enum SizeMode { + #[default] + Count, + ContentSize, +} + impl LineNumberStyle { pub fn from_str(s: &str) -> Option { match s { @@ -66,6 +74,15 @@ impl LineNumberStyle { } } +impl SizeMode { + pub fn from_str(s: &str) -> Self { + match s { + "size" => SizeMode::ContentSize, + _ => SizeMode::Count, + } + } +} + impl DirListDisplayOptions { pub fn set_filter_string(&mut self, pattern: &str) { self.filter_string = pattern.to_owned(); diff --git a/src/fs/dirlist.rs b/src/fs/dirlist.rs index 7d778191b..48ff52d21 100644 --- a/src/fs/dirlist.rs +++ b/src/fs/dirlist.rs @@ -27,8 +27,10 @@ impl JoshutoDirList { index: Option, viewport_index: usize, visual_mode_anchor_index: Option, - metadata: JoshutoMetadata, + mut metadata: JoshutoMetadata, ) -> Self { + let len: u64 = contents.iter().map(|e| e.metadata.len()).sum(); + metadata.update_len(len); Self { path, contents, @@ -51,7 +53,18 @@ impl JoshutoDirList { contents.sort_by(|f1, f2| tab_options.sort_options_ref().compare(f1, f2)); let index = if contents.is_empty() { None } else { Some(0) }; - let metadata = JoshutoMetadata::from(&path)?; + let mut metadata = JoshutoMetadata::from(&path)?; + let len: u64 = contents + .iter() + .map(|e| { + if !e.metadata.is_dir() { + e.metadata.len() + } else { + 0 + } + }) + .sum(); + metadata.update_len(len); Ok(Self { path, @@ -183,6 +196,11 @@ impl JoshutoDirList { self.contents.len() } + /// get size of directory in bytes + pub fn size(&self) -> u64 { + self.metadata.len() + } + pub fn is_empty(&self) -> bool { self.contents.is_empty() } diff --git a/src/fs/entry.rs b/src/fs/entry.rs index 66e0f49e6..4e9589d0b 100644 --- a/src/fs/entry.rs +++ b/src/fs/entry.rs @@ -37,6 +37,9 @@ impl JoshutoDirEntry { let mut metadata = JoshutoMetadata::from(&path)?; if options.automatically_count_files() && metadata.file_type().is_dir() { + if let Ok(len) = get_directory_len(path.as_path()) { + metadata.update_len(len); + } if let Ok(size) = get_directory_size(path.as_path()) { metadata.update_directory_size(size); } @@ -162,3 +165,20 @@ fn create_icon_label(name: &str, metadata: &JoshutoMetadata) -> String { fn get_directory_size(path: &path::Path) -> io::Result { fs::read_dir(path).map(|s| s.count()) } + +fn get_directory_len(path: &path::Path) -> io::Result { + fs::read_dir(path).map(|s| { + s.map(|e| { + if let Ok(entry) = e { + if let Ok(meta) = entry.metadata() { + meta.len() + } else { + 0 + } + } else { + 0 + } + }) + .sum() + }) +} diff --git a/src/fs/metadata.rs b/src/fs/metadata.rs index 132567eca..ea5fcf0df 100644 --- a/src/fs/metadata.rs +++ b/src/fs/metadata.rs @@ -46,7 +46,11 @@ impl JoshutoMetadata { let symlink_metadata = fs::symlink_metadata(path)?; let metadata = fs::metadata(path); let (_len, _modified, _permissions) = match metadata.as_ref() { - Ok(m) => (m.len(), m.modified()?, m.permissions()), + Ok(m) => ( + if m.is_dir() { 0 } else { m.len() }, + m.modified()?, + m.permissions(), + ), Err(_) => ( symlink_metadata.len(), symlink_metadata.modified()?, @@ -104,6 +108,10 @@ impl JoshutoMetadata { self._len } + pub fn update_len(&mut self, len: u64) { + self._len = len; + } + pub fn directory_size(&self) -> Option { self._directory_size } diff --git a/src/history.rs b/src/history.rs index dc70ff520..ec74d55c9 100644 --- a/src/history.rs +++ b/src/history.rs @@ -164,6 +164,20 @@ pub fn create_dirlist_with_history( if entry.metadata.is_dir() { if let Some(lst) = history.get(entry.file_path()) { entry.metadata.update_directory_size(lst.len()); + let len: u64 = lst + .contents + .iter() + .map(|e| { + if let Some(l) = history.get(e.file_path()) { + l.size() + } else { + 0 + } + }) + .sum(); + entry + .metadata + .update_len(if len > lst.size() { len } else { lst.size() }); } } } @@ -231,7 +245,9 @@ pub fn create_dirlist_with_history( } }; - let metadata = JoshutoMetadata::from(path)?; + let mut metadata = JoshutoMetadata::from(path)?; + let len: u64 = contents.iter().map(|e| e.metadata.len()).sum(); + metadata.update_len(len); let dirlist = JoshutoDirList::new( path.to_path_buf(), contents, diff --git a/src/ui/widgets/tui_dirlist_detailed.rs b/src/ui/widgets/tui_dirlist_detailed.rs index 20124b51a..8d006cee0 100644 --- a/src/ui/widgets/tui_dirlist_detailed.rs +++ b/src/ui/widgets/tui_dirlist_detailed.rs @@ -5,7 +5,7 @@ use tui::layout::Rect; use tui::style::{Color, Modifier, Style}; use tui::widgets::Widget; -use crate::config::option::{DisplayOption, LineMode, LineNumberStyle, TabDisplayOption}; +use crate::config::option::{DisplayOption, LineMode, LineNumberStyle, SizeMode, TabDisplayOption}; use crate::fs::{FileType, JoshutoDirEntry, JoshutoDirList, LinkType}; use crate::util::format; use crate::util::string::UnicodeTruncate; @@ -108,7 +108,7 @@ impl<'a> Widget for TuiDirListDetailed<'a> { entry, style, (x + 1, y + i as u16), - self.tab_display_options.linemode, + self.tab_display_options, drawing_width - 1, &prefix, ); @@ -116,13 +116,16 @@ impl<'a> Widget for TuiDirListDetailed<'a> { } } -fn get_entry_size_string(entry: &JoshutoDirEntry) -> String { +fn get_entry_size_string(entry: &JoshutoDirEntry, mode: &SizeMode) -> String { match entry.metadata.file_type() { - FileType::Directory => entry - .metadata - .directory_size() - .map(|n| n.to_string()) - .unwrap_or_else(|| "".to_string()), + FileType::Directory => match mode { + SizeMode::Count => entry + .metadata + .directory_size() + .map(|n| n.to_string()) + .unwrap_or_else(|| "".to_string()), + SizeMode::ContentSize => format::file_size_to_string(entry.metadata.len()), + }, FileType::File => format::file_size_to_string(entry.metadata.len()), } } @@ -132,7 +135,7 @@ fn print_entry( entry: &JoshutoDirEntry, style: Style, (x, y): (u16, u16), - linemode: LineMode, + tab_opts: &TabDisplayOption, drawing_width: usize, prefix: &str, ) { @@ -144,12 +147,12 @@ fn print_entry( let right_label_original = format!( " {}{} ", symlink_string, - match linemode { - LineMode::Size => get_entry_size_string(entry), + match tab_opts.linemode { + LineMode::Size => get_entry_size_string(entry, &tab_opts.size_mode), LineMode::MTime => format::mtime_to_string(entry.metadata.modified()), LineMode::SizeMTime => format!( "{} {}", - get_entry_size_string(entry), + get_entry_size_string(entry, &tab_opts.size_mode), format::mtime_to_string(entry.metadata.modified()) ), }