From 93d2a18674e6c9c929aeb08c489488fd7b185f04 Mon Sep 17 00:00:00 2001 From: Jeff Zhao Date: Sun, 10 Mar 2024 22:58:45 -0400 Subject: refactor code to reduce clones - move a lot of methods into functions - printing icons is moved to rendering section --- config/joshuto.toml | 2 +- src/commands/change_directory.rs | 26 +++---- src/commands/delete_files.rs | 14 +--- src/commands/new_directory.rs | 12 +-- src/commands/reload.rs | 122 +++++++++++++------------------ src/commands/rename_file.rs | 15 ++-- src/commands/tab_ops.rs | 119 ++++++++++++++++++++++-------- src/commands/touch_file.rs | 16 ++-- src/context/app_context.rs | 20 ++--- src/context/tab_context.rs | 5 +- src/event/process_event.rs | 33 ++++----- src/fs/dirlist.rs | 4 +- src/fs/entry.rs | 63 +--------------- src/history.rs | 130 +++++++++++++-------------------- src/preview/preview_dir.rs | 3 +- src/run.rs | 18 ++++- src/tab/tab_struct.rs | 20 ++--- src/ui/views/tui_folder_view.rs | 6 +- src/ui/views/tui_hsplit_view.rs | 20 ++++- src/ui/widgets/tui_dirlist.rs | 84 ++++++++++++++++++--- src/ui/widgets/tui_dirlist_detailed.rs | 33 +++++++-- 21 files changed, 399 insertions(+), 366 deletions(-) diff --git a/config/joshuto.toml b/config/joshuto.toml index f5fcaf5..7e37573 100644 --- a/config/joshuto.toml +++ b/config/joshuto.toml @@ -21,7 +21,7 @@ column_ratio = [1, 4, 4] scroll_offset = 6 show_borders = true show_hidden = false -show_icons = true +show_icons = false # none, absolute, relative line_number_style = "none" diff --git a/src/commands/change_directory.rs b/src/commands/change_directory.rs index 220ca59..6ac2575 100644 --- a/src/commands/change_directory.rs +++ b/src/commands/change_directory.rs @@ -3,7 +3,7 @@ use std::path; use crate::commands::reload; use crate::context::AppContext; use crate::error::AppResult; -use crate::history::DirectoryHistory; +use crate::history::{generate_entries_to_root, DirectoryHistory}; use crate::util::cwd; // ChangeDirectory command @@ -28,25 +28,19 @@ pub fn change_directory(context: &mut AppContext, mut path: &path::Path) -> AppR }; cd(new_cwd.as_path(), context)?; - let config = context.config_ref().clone(); - let options = context.config_ref().display_options_ref().clone(); - let ui_context = context.ui_context_ref().clone(); - let tab_options = context - .tab_context_ref() - .curr_tab_ref() - .option_ref() - .clone(); + let dirlists = generate_entries_to_root( + new_cwd.as_path(), + context.config_ref(), + context.tab_context_ref().curr_tab_ref().history_ref(), + context.ui_context_ref(), + context.config_ref().display_options_ref(), + context.tab_context_ref().curr_tab_ref().option_ref(), + )?; context .tab_context_mut() .curr_tab_mut() .history_mut() - .populate_to_root( - new_cwd.as_path(), - &config, - &ui_context, - &options, - &tab_options, - )?; + .insert_entries(dirlists); Ok(()) } diff --git a/src/commands/delete_files.rs b/src/commands/delete_files.rs index 755edb8..7076e80 100644 --- a/src/commands/delete_files.rs +++ b/src/commands/delete_files.rs @@ -5,11 +5,12 @@ use termion::event::Key; use crate::context::AppContext; use crate::error::{AppError, AppErrorKind, AppResult}; -use crate::history::DirectoryHistory; use crate::io::{FileOperation, FileOperationOptions, IoWorkerThread}; use crate::ui::widgets::TuiPrompt; use crate::ui::AppBackend; +use super::tab_ops; + fn prompt(context: &mut AppContext, backend: &mut AppBackend, paths_len: usize) -> bool { let ch = { let prompt_str = format!("Delete {} files? (Y/n)", paths_len); @@ -92,14 +93,7 @@ pub fn delete_selected_files( delete_files(context, paths, background, permanently)?; } - let curr_tab = context.tab_context_ref().curr_tab_ref(); - let config = context.config_ref().clone(); - let options = context.config_ref().display_options_ref().clone(); - let curr_path = curr_tab.cwd().to_path_buf(); - for (_, tab) in context.tab_context_mut().iter_mut() { - let tab_options = tab.option_ref().clone(); - tab.history_mut() - .reload(&curr_path, &config, &options, &tab_options)?; - } + let curr_path = context.tab_context_ref().curr_tab_ref().cwd().to_path_buf(); + tab_ops::reload_all_tabs(context, curr_path.as_path())?; Ok(()) } diff --git a/src/commands/new_directory.rs b/src/commands/new_directory.rs index d4bd68d..1d6e2e8 100644 --- a/src/commands/new_directory.rs +++ b/src/commands/new_directory.rs @@ -3,18 +3,14 @@ use std::path; use crate::commands::cursor_move; use crate::context::AppContext; use crate::error::AppResult; -use crate::history::DirectoryHistory; + +use super::tab_ops; pub fn new_directory(context: &mut AppContext, p: &path::Path) -> AppResult { std::fs::create_dir_all(p)?; - let config = context.config_ref().clone(); - let options = context.config_ref().display_options_ref().clone(); + let curr_path = context.tab_context_ref().curr_tab_ref().cwd().to_path_buf(); - for (_, tab) in context.tab_context_mut().iter_mut() { - let tab_options = tab.option_ref().clone(); - tab.history_mut() - .reload(&curr_path, &config, &options, &tab_options)?; - } + tab_ops::reload_all_tabs(context, curr_path.as_path())?; if context.config_ref().focus_on_create { cursor_move::to_path(context, p)?; diff --git a/src/commands/reload.rs b/src/commands/reload.rs index bee7754..4134272 100644 --- a/src/commands/reload.rs +++ b/src/commands/reload.rs @@ -1,55 +1,44 @@ use crate::context::AppContext; use crate::error::AppResult; -use crate::history::create_dirlist_with_history; +use crate::history::{create_dirlist_with_history, DirectoryHistory}; use uuid::Uuid; // reload only if we have a queued reload pub fn soft_reload(context: &mut AppContext, id: &Uuid) -> std::io::Result<()> { - let mut paths = Vec::with_capacity(3); + let mut dirlists = Vec::with_capacity(3); if let Some(curr_tab) = context.tab_context_ref().tab_ref(id) { - if let Some(curr_list) = curr_tab.curr_list_ref() { - if curr_list.need_update() { - paths.push(curr_list.file_path().to_path_buf()); - } - } - if let Some(curr_list) = curr_tab.parent_list_ref() { - if curr_list.need_update() { - paths.push(curr_list.file_path().to_path_buf()); - } - } - if let Some(curr_list) = curr_tab.child_list_ref() { - if curr_list.need_update() { - paths.push(curr_list.file_path().to_path_buf()); - } - } - } - - if !paths.is_empty() { - let config = context.config_ref().clone(); - let options = context.config_ref().display_options_ref().clone(); - let tab_options = context - .tab_context_ref() - .curr_tab_ref() - .option_ref() - .clone(); - if let Some(history) = context - .tab_context_mut() - .tab_mut(id) - .map(|t| t.history_mut()) + let config = context.config_ref(); + let display_options = context.config_ref().display_options_ref(); + let tab_options = context.tab_context_ref().curr_tab_ref().option_ref(); + let history = curr_tab.history_ref(); + for curr_list in [ + curr_tab.parent_list_ref(), + curr_tab.curr_list_ref(), + curr_tab.child_list_ref(), + ] + .into_iter() + .flatten() { - for path in paths { + if curr_list.need_update() { let new_dirlist = create_dirlist_with_history( history, - path.as_path(), - &config, - &options, - &tab_options, + curr_list.file_path(), + display_options, + tab_options, )?; - history.insert(path, new_dirlist); + dirlists.push(new_dirlist); } } } + + if let Some(history) = context + .tab_context_mut() + .tab_mut(id) + .map(|t| t.history_mut()) + { + history.insert_entries(dirlists); + } Ok(()) } @@ -59,43 +48,36 @@ pub fn soft_reload_curr_tab(context: &mut AppContext) -> std::io::Result<()> { } pub fn reload(context: &mut AppContext, id: &Uuid) -> std::io::Result<()> { - let mut paths = Vec::with_capacity(3); + let mut dirlists = Vec::with_capacity(3); if let Some(curr_tab) = context.tab_context_ref().tab_ref(id) { - if let Some(curr_list) = curr_tab.curr_list_ref() { - paths.push(curr_list.file_path().to_path_buf()); - } - if let Some(curr_list) = curr_tab.parent_list_ref() { - paths.push(curr_list.file_path().to_path_buf()); - } - if let Some(curr_list) = curr_tab.child_list_ref() { - paths.push(curr_list.file_path().to_path_buf()); + let config = context.config_ref(); + let display_options = context.config_ref().display_options_ref(); + let tab_options = context.tab_context_ref().curr_tab_ref().option_ref(); + let history = curr_tab.history_ref(); + for curr_list in [ + curr_tab.parent_list_ref(), + curr_tab.curr_list_ref(), + curr_tab.child_list_ref(), + ] + .into_iter() + .flatten() + { + let new_dirlist = create_dirlist_with_history( + history, + curr_list.file_path(), + display_options, + tab_options, + )?; + dirlists.push(new_dirlist); } } - if !paths.is_empty() { - let config = context.config_ref().clone(); - let options = context.config_ref().display_options_ref().clone(); - let tab_options = context - .tab_context_ref() - .curr_tab_ref() - .option_ref() - .clone(); - if let Some(history) = context - .tab_context_mut() - .tab_mut(id) - .map(|t| t.history_mut()) - { - for path in paths { - let new_dirlist = create_dirlist_with_history( - history, - path.as_path(), - &config, - &options, - &tab_options, - )?; - history.insert(path, new_dirlist); - } - } + if let Some(history) = context + .tab_context_mut() + .tab_mut(id) + .map(|t| t.history_mut()) + { + history.insert_entries(dirlists); } context .message_queue_mut() diff --git a/src/commands/rename_file.rs b/src/commands/rename_file.rs index 45ad9fe..3cecc38 100644 --- a/src/commands/rename_file.rs +++ b/src/commands/rename_file.rs @@ -27,16 +27,13 @@ pub fn _rename_file( .map(|lst| lst.file_path().to_path_buf()); if let Some(path) = path { - let config = context.config_ref().clone(); - let options = context.config_ref().display_options_ref().clone(); - let tab_options = context - .tab_context_ref() - .curr_tab_ref() - .option_ref() - .clone(); + let new_dirlist = { + let display_options = context.config_ref().display_options_ref(); + let tab_options = context.tab_context_ref().curr_tab_ref().option_ref(); + let history = context.tab_context_ref().curr_tab_ref().history_ref(); + create_dirlist_with_history(history, path.as_path(), display_options, tab_options)? + }; let history = context.tab_context_mut().curr_tab_mut().history_mut(); - let new_dirlist = - create_dirlist_with_history(history, path.as_path(), &config, &options, &tab_options)?; history.insert(path, new_dirlist); } Ok(()) diff --git a/src/commands/tab_ops.rs b/src/commands/tab_ops.rs index cbba359..f1adfe2 100644 --- a/src/commands/tab_ops.rs +++ b/src/commands/tab_ops.rs @@ -1,11 +1,15 @@ -use std::path; +use std::collections::HashMap; +use std::path::Path; +use std::{io, path}; use uuid::Uuid; use crate::config::clean::app::display::new_tab::NewTabMode; use crate::context::AppContext; use crate::error::{AppError, AppErrorKind, AppResult}; -use crate::history::DirectoryHistory; +use crate::history::{ + create_dirlist_with_history, generate_entries_to_root, DirectoryHistory, JoshutoHistory, +}; use crate::tab::{JoshutoTab, TabHomePage}; use crate::util::{cwd, unix}; @@ -35,39 +39,46 @@ fn _tab_switch(new_index: usize, context: &mut AppContext) -> std::io::Result<() None => None, }; - let config = context.config_ref().clone(); - let options = context.config_ref().display_options_ref().clone(); - let tab_options = context - .tab_context_ref() - .curr_tab_ref() - .option_ref() - .clone(); + let display_options = context.config_ref().display_options_ref(); + let tab_options = context.tab_context_ref().curr_tab_ref().option_ref(); - let history = context.tab_context_mut().curr_tab_mut().history_mut(); - if history - .create_or_soft_update(cwd.as_path(), &config, &options, &tab_options) - .is_err() - { - history.remove(cwd.as_path()); - } + let history = context.tab_context_ref().curr_tab_ref().history_ref(); - if let Some(cwd_parent) = cwd.parent() { - if history - .create_or_soft_update(cwd_parent, &config, &options, &tab_options) - .is_err() - { - history.remove(cwd_parent); + let mut dirlists = Vec::with_capacity(3); + for curr_path in [ + Some(cwd.as_path().to_path_buf()), + cwd.parent().map(|p| p.to_path_buf()), + entry_path, + ] + .into_iter() + .flatten() + { + match history.get(&curr_path) { + Some(list) => { + if list.need_update() { + let dirlist = create_dirlist_with_history( + history, + cwd.as_path(), + display_options, + tab_options, + )?; + dirlists.push(dirlist); + } + } + None => { + let dirlist = create_dirlist_with_history( + history, + cwd.as_path(), + display_options, + tab_options, + )?; + dirlists.push(dirlist); + } } } - if let Some(file_path) = entry_path { - if history - .create_or_soft_update(file_path.as_path(), &config, &options, &tab_options) - .is_err() - { - history.remove(file_path.as_path()); - } - } + let history = context.tab_context_mut().curr_tab_mut().history_mut(); + history.insert_entries(dirlists); Ok(()) } @@ -140,12 +151,28 @@ pub fn new_tab(context: &mut AppContext, mode: &NewTabMode) -> AppResult { }?; if new_tab_path.exists() && new_tab_path.is_dir() { let id = Uuid::new_v4(); - let tab = JoshutoTab::new( - new_tab_path, + let mut new_tab_history = JoshutoHistory::new(); + let tab_display_options = context + .config_ref() + .display_options_ref() + .default_tab_display_option + .clone(); + let dirlists = generate_entries_to_root( + new_tab_path.as_path(), context.config_ref(), + &new_tab_history, context.ui_context_ref(), context.config_ref().display_options_ref(), + &tab_display_options, )?; + new_tab_history.insert_entries(dirlists); + + let tab_display_options = context + .config_ref() + .display_options_ref() + .default_tab_display_option + .clone(); + let tab = JoshutoTab::new(new_tab_path, new_tab_history, tab_display_options)?; context.tab_context_mut().insert_tab(id, tab); let new_index = context.tab_context_ref().len() - 1; context.tab_context_mut().index = new_index; @@ -179,3 +206,31 @@ pub fn close_tab(context: &mut AppContext) -> AppResult { _tab_switch(tab_index, context)?; Ok(()) } + +pub fn reload_all_tabs(context: &mut AppContext, curr_path: &Path) -> io::Result<()> { + let mut map = HashMap::new(); + { + let display_options = context.config_ref().display_options_ref(); + + for (id, tab) in context.tab_context_ref().iter() { + let tab_options = tab.option_ref(); + let history = tab.history_ref(); + let dirlist = + create_dirlist_with_history(history, curr_path, display_options, tab_options)?; + map.insert(*id, dirlist); + } + } + + for (id, dirlist) in map { + if let Some(tab) = context.tab_context_mut().tab_mut(&id) { + tab.history_mut().insert(curr_path.to_path_buf(), dirlist); + } + } + Ok(()) +} + +pub fn remove_entry_from_all_tabs(context: &mut AppContext, curr_path: &Path) { + for (_, tab) in context.tab_context_mut().iter_mut() { + tab.history_mut().remove(curr_path); + } +} diff --git a/src/commands/touch_file.rs b/src/commands/touch_file.rs index 41ba1c3..5fbf039 100644 --- a/src/commands/touch_file.rs +++ b/src/commands/touch_file.rs @@ -48,16 +48,14 @@ pub fn touch_file(context: &mut AppContext, arg: &str) -> AppResult { .map(|lst| lst.file_path().to_path_buf()); if let Some(path) = path { - let config = context.config_ref().clone(); - let options = context.config_ref().display_options_ref().clone(); - let tab_options = context - .tab_context_ref() - .curr_tab_ref() - .option_ref() - .clone(); + let new_dirlist = { + let options = context.config_ref().display_options_ref(); + let tab_options = context.tab_context_ref().curr_tab_ref().option_ref(); + let history = context.tab_context_ref().curr_tab_ref().history_ref(); + + create_dirlist_with_history(history, path.as_path(), options, tab_options)? + }; let history = context.tab_context_mut().curr_tab_mut().history_mut(); - let new_dirlist = - create_dirlist_with_history(history, path.as_path(), &config, &options, &tab_options)?; history.insert(path, new_dirlist); } diff --git a/src/context/app_context.rs b/src/context/app_context.rs index 4b7f8b7..db8e17a 100644 --- a/src/context/app_context.rs +++ b/src/context/app_context.rs @@ -23,27 +23,27 @@ pub struct AppContext { // args from the command line pub args: Args, // app config - config: AppConfig, + pub config: AppConfig, // context related to tabs - tab_context: TabContext, + pub tab_context: TabContext, // context related to local file state - local_state: Option, + pub local_state: Option, // context related to searching - search_context: Option, + pub search_context: Option, // message queue for displaying messages - message_queue: MessageQueue, + pub message_queue: MessageQueue, // context related to io workers - worker_context: WorkerContext, + pub worker_context: WorkerContext, // context related to previews pub preview_context: PreviewContext, // context related to command line - commandline_context: CommandLineContext, + pub commandline_context: CommandLineContext, // user interface context; data which is input to both, the UI rendering and the app state - ui_context: UiContext, + pub ui_context: UiContext, // filesystem watcher to inform about changes in shown directories - watcher: notify::RecommendedWatcher, + pub watcher: notify::RecommendedWatcher, // list of watched paths; seems not to be possible to get them from a notify::Watcher - watched_paths: HashSet, + pub watched_paths: HashSet, // the stdout of the last `shell` command pub last_stdout: Option, } diff --git a/src/context/tab_context.rs b/src/context/tab_context.rs index 1df7295..35ee9ed 100644 --- a/src/context/tab_context.rs +++ b/src/context/tab_context.rs @@ -1,4 +1,4 @@ -use std::collections::hash_map::IterMut; +use std::collections::hash_map::{Iter, IterMut}; use std::collections::HashMap; use uuid::Uuid; @@ -66,6 +66,9 @@ impl TabContext { tab } + pub fn iter(&self) -> Iter { + self.tabs.iter() + } pub fn iter_mut(&mut self) -> IterMut { self.tabs.iter_mut() } diff --git a/src/event/process_event.rs b/src/event/process_event.rs index 91b8930..b8e1cff 100644 --- a/src/event/process_event.rs +++ b/src/event/process_event.rs @@ -7,6 +7,7 @@ use signal_hook::consts::signal; use termion::event::{Event, Key, MouseButton, MouseEvent}; use uuid::Uuid; +use crate::commands::tab_ops; use crate::commands::{cursor_move, parent_cursor_move, reload}; use crate::config::clean::keymap::AppKeyMapping; use crate::config::clean::keymap::KeyMapping; @@ -15,7 +16,6 @@ use crate::error::AppResult; use crate::event::AppEvent; use crate::event::PreviewData; use crate::fs::JoshutoDirList; -use crate::history::DirectoryHistory; use crate::io::FileOperationProgress; use crate::key_command::{AppExecute, Command, CommandKeybind}; use crate::preview::preview_dir::PreviewDirState; @@ -94,24 +94,19 @@ pub fn process_worker_progress(context: &mut AppContext, res: FileOperationProgr pub fn process_finished_worker(context: &mut AppContext, res: AppResult) { let worker_context = context.worker_context_mut(); let observer = worker_context.remove_worker().unwrap(); - let config = context.config_ref().clone(); - let options = context.config_ref().display_options_ref().clone(); - for (_, tab) in context.tab_context_mut().iter_mut() { - let tab_options = tab.option_ref().clone(); - if observer.dest_path().exists() { - let _ = tab - .history_mut() - .reload(observer.dest_path(), &config, &options, &tab_options); - } else { - tab.history_mut().remove(observer.dest_path()); - } - if observer.src_path().exists() { - let _ = tab - .history_mut() - .reload(observer.src_path(), &config, &options, &tab_options); - } else { - tab.history_mut().remove(observer.src_path()); - } + + let observer_path = observer.dest_path(); + if observer_path.exists() { + let _ = tab_ops::reload_all_tabs(context, observer_path); + } else { + tab_ops::remove_entry_from_all_tabs(context, observer_path); + } + + let observer_path = observer.src_path(); + if observer_path.exists() { + let _ = tab_ops::reload_all_tabs(context, observer_path); + } else { + tab_ops::remove_entry_from_all_tabs(context, observer_path); } observer.join(); diff --git a/src/fs/dirlist.rs b/src/fs/dirlist.rs index 97affac..de13440 100644 --- a/src/fs/dirlist.rs +++ b/src/fs/dirlist.rs @@ -44,13 +44,11 @@ impl JoshutoDirList { pub fn from_path( path: path::PathBuf, - config: &AppConfig, options: &DisplayOption, tab_options: &TabDisplayOption, ) -> io::Result { let filter_func = options.filter_func(); - let mut contents = - read_directory(path.as_path(), filter_func, config, options, tab_options)?; + let mut contents = read_directory(path.as_path(), filter_func, options, tab_options)?; contents.sort_by(|f1, f2| tab_options.sort_options_ref().compare(f1, f2)); diff --git a/src/fs/entry.rs b/src/fs/entry.rs index d0940c3..9a4b557 100644 --- a/src/fs/entry.rs +++ b/src/fs/entry.rs @@ -10,10 +10,9 @@ use crate::ICONS_T; #[derive(Clone, Debug)] pub struct JoshutoDirEntry { - name: String, - ext: Option, - label: String, - path: path::PathBuf, + pub name: String, + pub ext: Option, + pub path: path::PathBuf, pub metadata: JoshutoMetadata, /// Directly selected by the user, _not_ by a current visual mode selection permanent_selected: bool, @@ -26,7 +25,6 @@ impl JoshutoDirEntry { pub fn from( direntry: &walkdir::DirEntry, base: &path::Path, - config: &AppConfig, options: &DisplayOption, ) -> io::Result { let path = direntry.path().to_path_buf(); @@ -42,13 +40,7 @@ impl JoshutoDirEntry { .path() .extension() .and_then(|s| s.to_str()) - .map(|s| { - if config.case_sensitive_ext { - s.to_string() - } else { - s.to_lowercase() - } - }); + .map(|s| s.to_string()); let mut metadata = JoshutoMetadata::from(&path)?; @@ -58,20 +50,9 @@ impl JoshutoDirEntry { } } - #[cfg(feature = "devicons")] - let label = if options.show_icons() { - create_icon_label(name.as_str(), &ext, config, &metadata) - } else { - name.clone() - }; - - #[cfg(not(feature = "devicons"))] - let label = name.clone(); - Ok(Self { name, ext, - label, path, metadata, permanent_selected: false, @@ -88,10 +69,6 @@ impl JoshutoDirEntry { self.ext.as_deref() } - pub fn label(&self) -> &str { - self.label.as_str() - } - pub fn file_path(&self) -> &path::Path { self.path.as_path() } @@ -152,38 +129,6 @@ impl std::cmp::Ord for JoshutoDirEntry { } } -#[cfg(feature = "devicons")] -fn create_icon_label( - name: &str, - ext: &Option, - config: &AppConfig, - metadata: &JoshutoMetadata, -) -> String { - let label = { - let icon = match metadata.file_type() { - FileType::Directory => ICONS_T - .directory_exact - .get(name) - .cloned() - .unwrap_or(ICONS_T.default_dir.clone()), - _ => ICONS_T.file_exact.get(name).cloned().unwrap_or(match ext { - Some(ext) => { - let icon = if config.case_sensitive_ext { - ICONS_T.ext.get(ext) - } else { - ICONS_T.ext.get(&ext.to_lowercase()) - }; - - icon.unwrap_or(&ICONS_T.default_file).to_string() - } - None => ICONS_T.default_file.clone(), - }), - }; - format!("{} {}", icon, name) - }; - label -} - fn get_directory_size(path: &path::Path) -> io::Result { fs::read_dir(path).map(|s| s.count()) } diff --git a/src/history.rs b/src/history.rs index 0035d88..5302828 100644 --- a/src/history.rs +++ b/src/history.rs @@ -12,32 +12,16 @@ use crate::context::UiContext; use crate::fs::{JoshutoDirEntry, JoshutoDirList, JoshutoMetadata}; pub trait DirectoryHistory { - fn populate_to_root( - &mut self, - path: &Path, - config: &AppConfig, - ui_context: &UiContext, - options: &DisplayOption, - tab_options: &TabDisplayOption, - ) -> io::Result<()>; + fn insert_entries(&mut self, entries: Vec); fn create_or_soft_update( &mut self, path: &Path, - config: &AppConfig, options: &DisplayOption, tab_options: &TabDisplayOption, ) -> io::Result<()>; fn create_or_reload( &mut self, path: &Path, - config: &AppConfig, - options: &DisplayOption, - tab_options: &TabDisplayOption, - ) -> io::Result<()>; - fn reload( - &mut self, - path: &Path, - config: &AppConfig, options: &DisplayOption, tab_options: &TabDisplayOption, ) -> io::Result<()>; @@ -49,53 +33,15 @@ pub trait DirectoryHistory { pub type JoshutoHistory = HashMap; impl DirectoryHistory for JoshutoHistory { - fn populate_to_root( - &mut self, - path: &Path, - config: &AppConfig, - ui_context: &UiContext, - options: &DisplayOption, - tab_options: &TabDisplayOption, - ) -> io::Result<()> { - let mut dirlists = Vec::new(); - - let mut prev: Option<&Path> = None; - for curr in path.ancestors() { - if self.contains_key(curr) { - let mut new_dirlist = - create_dirlist_with_history(self, curr, config, options, tab_options)?; - if let Some(ancestor) = prev.as_ref() { - if let Some(i) = get_index_of_value(&new_dirlist.contents, ancestor) { - new_dirlist.set_index(Some(i), ui_context, options); - } - } - dirlists.push(new_dirlist); - } else { - let mut new_dirlist = JoshutoDirList::from_path( - curr.to_path_buf().clone(), - config, - options, - tab_options, - )?; - if let Some(ancestor) = prev.as_ref() { - if let Some(i) = get_index_of_value(&new_dirlist.contents, ancestor) { - new_dirlist.set_index(Some(i), ui_context, options); - } - } - dirlists.push(new_dirlist); - } - prev = Some(curr); - } - for dirlist in dirlists { + fn insert_entries(&mut self, entries: Vec) { + for dirlist in entries { self.insert(dirlist.file_path().to_path_buf(), dirlist); } - Ok(()) } fn create_or_soft_update( &mut self, path: &Path, - config: &AppConfig, options: &DisplayOption, tab_options: &TabDisplayOption, ) -> io::Result<()> { @@ -106,9 +52,9 @@ impl DirectoryHistory for JoshutoHistory { }; if need_update { let dirlist = if contains_key { - create_dirlist_with_history(self, path, config, options, tab_options)? + create_dirlist_with_history(self, path, options, tab_options)? } else { - JoshutoDirList::from_path(path.to_path_buf(), config, options, tab_options)? + JoshutoDirList::from_path(path.to_path_buf(), options, tab_options)? }; self.insert(path.to_path_buf(), dirlist); } @@ -118,31 +64,18 @@ impl DirectoryHistory for JoshutoHistory { fn create_or_reload( &mut self, path: &Path, - config: &AppConfig, options: &DisplayOption, tab_options: &TabDisplayOption, ) -> io::Result<()> { let dirlist = if self.contains_key(path) { - create_dirlist_with_history(self, path, config, options, tab_options)? + create_dirlist_with_history(self, path, options, tab_options)? } else { - JoshutoDirList::from_path(path.to_path_buf(), config, options, tab_options)? + JoshutoDirList::from_path(path.to_path_buf(), options, tab_options)? }; self.insert(path.to_path_buf(), dirlist); Ok(()) } - fn reload( - &mut self, - path: &Path, - config: &AppConfig, - options: &DisplayOption, - tab_options: &TabDisplayOption, - ) -> io::Result<()> { - let dirlist = create_dirlist_with_history(self, path, config, options, tab_options)?; - self.insert(path.to_path_buf(), dirlist); - Ok(()) - } - fn depreciate_all_entries(&mut self) { self.iter_mut().for_each(|(_, v)| v.depreciate()); } @@ -167,12 +100,11 @@ fn get_index_of_value(arr: &[JoshutoDirEntry], val: &Path) -> Option { pub fn create_dirlist_with_history( history: &JoshutoHistory, path: &Path, - config: &AppConfig, options: &DisplayOption, tab_options: &TabDisplayOption, ) -> io::Result { let filter_func = options.filter_func(); - let mut contents = read_directory(path, filter_func, config, options, tab_options)?; + let mut contents = read_directory(path, filter_func, options, tab_options)?; // re-use directory size information on reload for entry in contents.iter_mut() { @@ -259,8 +191,7 @@ pub fn create_dirlist_with_history( pub fn read_directory( path: &Path, filter_func: F, - config: &AppConfig, - options: &DisplayOption, + display_options: &DisplayOption, tab_options: &TabDisplayOption, ) -> io::Result> where @@ -276,7 +207,7 @@ where .into_iter() .filter_entry(|e| { if e.path().to_str().cmp(&path.to_str()).is_ne() { - filter_func(e, options, &dirlist_opts) + filter_func(e, display_options, &dirlist_opts) } else { true } @@ -288,8 +219,47 @@ where true } }) - .filter_map(|res| JoshutoDirEntry::from(&res.ok()?, path, config, options).ok()) + .filter_map(|res| JoshutoDirEntry::from(&res.ok()?, path, display_options).ok()) .collect(); Ok(results) } + +pub fn generate_entries_to_root( + path: &Path, + config: &AppConfig, + history: &JoshutoHistory, + ui_context: &UiContext, + display_options: &DisplayOption, + tab_options: &TabDisplayOption, +) -> io::Result> { + let mut dirlists = Vec::new(); + + let mut prev: Option<&Path> = None; + for curr in path.ancestors() { + if history.contains_key(curr) { + let mut new_dirlist = + create_dirlist_with_history(history, curr, display_options, tab_options)?; + if let Some(ancestor) = prev.as_ref() { + if let Some(i) = get_index_of_value(&new_dirlist.contents, ancestor) { + new_dirlist.set_index(Some(i), ui_context, display_options); + } + } + dirlists.push(new_dirlist); + } else { + let mut new_dirlist = JoshutoDirList::from_path( + curr.to_path_buf().clone(), + display_options, + tab_options, + )?; + if let Some(ancestor) = prev.as_ref() { + if let Some(i) = get_index_of_value(&new_dirlist.contents, ancestor) { + new_dirlist.set_index(Some(i), ui_context, display_options); + } + } + dirlists.push(new_dirlist); + } + prev = Some(curr); + } + Ok(dirlists) +} diff --git a/src/preview/preview_dir.rs b/src/preview/preview_dir.rs index 98f6b95..77090a3 100644 --- a/src/preview/preview_dir.rs +++ b/src/preview/preview_dir.rs @@ -22,7 +22,6 @@ pub struct Background {} impl Background { pub fn load_preview(context: &mut AppContext, p: path::PathBuf) -> thread::JoinHandle<()> { let event_tx = context.events.event_tx.clone(); - let config = context.config_ref().clone(); let options = context.config_ref().display_options_ref().clone(); let tab_options = context .tab_context_ref() @@ -40,7 +39,7 @@ impl Background { thread::spawn(move || { let path_clone = p.clone(); - let dir_res = JoshutoDirList::from_path(p, &config, &options, &tab_options); + let dir_res = JoshutoDirList::from_path(p, &options, &tab_options); let res = AppEvent::PreviewDir { id: tab_id, path: path_clone, diff --git a/src/run.rs b/src/run.rs index f984391..1147d52 100644 --- a/src/run.rs +++ b/src/run.rs @@ -4,6 +4,9 @@ use crate::context::calculate_external_preview; use crate::context::AppContext; use crate::event::process_event; use crate::event::AppEvent; +use crate::history::generate_entries_to_root; +use crate::history::DirectoryHistory; +use crate::history::JoshutoHistory; use crate::key_command::{AppExecute, CommandKeybind}; use crate::preview::preview_default; use crate::tab::JoshutoTab; @@ -33,12 +36,23 @@ pub fn run_loop( { let id = Uuid::new_v4(); // Initialize an initial tab - let tab = JoshutoTab::new( - curr_path, + let mut new_tab_history = JoshutoHistory::new(); + let tab_display_options = context + .config_ref() + .display_options_ref() + .default_tab_display_option + .clone(); + let dirlists = generate_entries_to_root( + curr_path.as_path(), context.config_ref(), + &new_tab_history, context.ui_context_ref(), context.config_ref().display_options_ref(), + &tab_display_options, )?; + new_tab_history.insert_entries(dirlists); + + let tab = JoshutoTab::new(curr_path, new_tab_history, tab_display_options)?; context.tab_context_mut().insert_tab(id, tab); // trigger a preview of child diff --git a/src/tab/tab_struct.rs b/src/tab/tab_struct.rs index 66733bf..e8fabcb 100644 --- a/src/tab/tab_struct.rs +++ b/src/tab/tab_struct.rs @@ -2,11 +2,8 @@ use std::collections::HashMap; use std::path; use crate::config::clean::app::display::tab::TabDisplayOption; -use crate::config::clean::app::display::DisplayOption; -use crate::config::clean::app::AppConfig; -use crate::context::UiContext; use crate::fs::JoshutoDirList; -use crate::history::{DirectoryHistory, JoshutoHistory}; +use crate::history::JoshutoHistory; use crate::preview::preview_dir::PreviewDirState; // use crate::HOSTNAME; @@ -16,22 +13,17 @@ pub struct JoshutoTab { _cwd: path::PathBuf, // history is just a HashMap, so we have this property to store last workdir _previous_dir: Option, - history: JoshutoHistory, - history_metadata: HistoryMetadata, - options: TabDisplayOption, + pub history: JoshutoHistory, + pub history_metadata: HistoryMetadata, + pub options: TabDisplayOption, } impl JoshutoTab { pub fn new( cwd: path::PathBuf, - config: &AppConfig, - ui_context: &UiContext, - options: &DisplayOption, + history: JoshutoHistory, + tab_options: TabDisplayOption, ) -> std::io::Result { - let mut history = JoshutoHistory::new(); - let tab_options = options.default_tab_display_option.clone(); - - history.populate_to_root(cwd.as_path(), config, ui_context, options, &tab_options)?; let new_tab = Self { _cwd: cwd, _previous_dir: None, diff --git a/src/ui/views/tui_folder_view.rs b/src/ui/views/tui_folder_view.rs index 4f49ab1..8abcc26 100644 --- a/src/ui/views/tui_folder_view.rs +++ b/src/ui/views/tui_folder_view.rs @@ -143,14 +143,14 @@ impl<'a> Widget for TuiFolderView<'a> { Constraint::Ratio(0, _) => {} _ => { if let Some(list) = curr_tab.parent_list_ref().as_ref() { - TuiDirList::new(list, true).render(layout_rect[0], buf); + TuiDirList::new(config, list, true).render(layout_rect[0], buf); } } } // render current view if let Some(list) = curr_list.as_ref() { - TuiDirListDetailed::new(list, display_options, curr_tab.option_ref(), true) + TuiDirListDetailed::new(config, list, display_options, curr_tab.option_ref(), true) .render(layout_rect[1], buf); let footer_area = Self::footer_area(&area); @@ -185,7 +185,7 @@ impl<'a> Widget for TuiFolderView<'a> { } if let Some(list) = child_list.as_ref() { - TuiDirList::new(list, true).render(layout_rect[2], buf); + TuiDirList::new(config, list, true).render(layout_rect[2], buf); } else if let Some(entry) = curr_entry { match curr_tab.history_metadata_ref().get(entry.file_path()) { Some(PreviewDirState::Loading) => { diff --git a/src/ui/views/tui_hsplit_view.rs b/src/ui/views/tui_hsplit_view.rs index 6244e09..a28a4b9 100644 --- a/src/ui/views/tui_hsplit_view.rs +++ b/src/ui/views/tui_hsplit_view.rs @@ -76,8 +76,14 @@ impl<'a> Widget for TuiHSplitView<'a> { // render current view if let Some(list) = curr_list.as_ref() { - TuiDirListDetailed::new(list, display_options, curr_tab.option_ref(), true) - .render(layout_rect, buf); + TuiDirListDetailed::new( + self.context.config_ref(), + list, + display_options, + curr_tab.option_ref(), + true, + ) + .render(layout_rect, buf); let rect = Rect { x: 0, y: area.height - 1, @@ -132,8 +138,14 @@ impl<'a> Widget for TuiHSplitView<'a> { }; if let Some(list) = curr_list.as_ref() { - TuiDirListDetailed::new(list, display_options, curr_tab.option_ref(), false) - .render(layout_rect, buf); + TuiDirListDetailed::new( + self.context.config_ref(), + list, + display_options, + curr_tab.option_ref(), + false, + ) + .render(layout_rect, buf); } } } diff --git a/src/ui/widgets/tui_dirlist.rs b/src/ui/widgets/tui_dirlist.rs index 8d7b35e..3ee6917 100644 --- a/src/ui/widgets/tui_dirlist.rs +++ b/src/ui/widgets/tui_dirlist.rs @@ -4,18 +4,25 @@ use ratatui::style::{Color, Modifier, Style}; use ratatui::widgets::Widget; use unicode_width::UnicodeWidthStr; +use crate::config::clean::app::AppConfig; +use crate::fs::{FileType, JoshutoMetadata}; use crate::fs::{JoshutoDirEntry, JoshutoDirList}; use crate::ui::widgets::trim_file_label; use crate::util::style; pub struct TuiDirList<'a> { - dirlist: &'a JoshutoDirList, + pub config: &'a AppConfig, + pub dirlist: &'a JoshutoDirList, pub focused: bool, } impl<'a> TuiDirList<'a> { - pub fn new(dirlist: &'a JoshutoDirList, focused: bool) -> Self { - Self { dirlist, focused } + pub fn new(config: &'a AppConfig, dirlist: &'a JoshutoDirList, focused: bool) -> Self { + Self { + config, + dirlist, + focused, + } } } @@ -58,24 +65,83 @@ impl<'a> Widget for TuiDirList<'a> { buf.set_string(x, y + i as u16, space_fill.as_str(), style); - print_entry(buf, entry, style, (x + 1, y + i as u16), drawing_width - 1); + print_entry( + self.config, + buf, + entry, + style, + (x + 1, y + i as u16), + drawing_width - 1, + ); }); } } fn print_entry( + config: &AppConfig, buf: &mut Buffer, entry: &JoshutoDirEntry, style: Style, (x, y): (u16, u16), drawing_width: usize, ) { - let name = entry.label(); - let name_width = name.width(); - let label = if name_width > drawing_width { - trim_file_label(name, drawing_width) + let name = entry.file_name(); + #[cfg(feature = "devicons")] + let (label, label_width) = { + if config.display_options_ref().show_icons() { + let icon = get_entry_icon(&config, entry.file_name(), entry.ext(), &entry.metadata); + let label = format!("{icon} {name}"); + let label_width = label.width(); + (label, label_width) + } else { + (name.to_string(), name.width()) + } + }; + + #[cfg(not(feature = "devicons"))] + let (label, label_width) = { + let label = name.to_string(); + let label_width = label.width(); + (label, label_width) + }; + + let label = if label_width > drawing_width { + trim_file_label(&label, drawing_width) } else { - name.to_string() + label.to_string() }; buf.set_string(x, y, label, style); } + +#[cfg(feature = "devicons")] +pub fn get_entry_icon( + config: &AppConfig, + name: &str, + ext: Option<&str>, + metadata: &JoshutoMetadata, +) -> &'static str { + use crate::ICONS_T; + + if let FileType::Directory = metadata.file_type() { + return ICONS_T + .directory_exact + .get(name) + .map(|s| s.as_str()) + .unwrap_or(ICONS_T.default_dir.as_str()); + } + ICONS_T + .file_exact + .get(name) + .map(|s| s.as_str()) + .unwrap_or_else(|| { + ext.and_then(|ext| { + let ext: String = if config.case_sensitive_ext { + ext.to_owned() + } else { + ext.to_lowercase() + }; + ICONS_T.ext.get(&ext).map(|s| s.as_str()) + }) + .unwrap_or_else(|| ICONS_T.default_file.as_str()) + }) +} diff --git a/src/ui/widgets/tui_dirlist_detailed.rs b/src/ui/widgets/tui_dirlist_detailed.rs index b35dcea..0b203fe 100644 --- a/src/ui/widgets/tui_dirlist_detailed.rs +++ b/src/ui/widgets/tui_dirlist_detailed.rs @@ -9,30 +9,36 @@ use crate::config::clean::app::display::line_mode::LineMode; use crate::config::clean::app::display::line_number::LineNumberStyle; use crate::config::clean::app::display::tab::TabDisplayOption; use crate::config::clean::app::display::DisplayOption; +use crate::config::clean::app::AppConfig; use crate::fs::{FileType, JoshutoDirEntry, JoshutoDirList, LinkType}; use crate::util::string::UnicodeTruncate; use crate::util::style; use crate::util::{format, unix}; use unicode_width::UnicodeWidthStr; +use super::tui_dirlist::get_entry_icon; + const MIN_LEFT_LABEL_WIDTH: i32 = 15; const ELLIPSIS: &str = "…"; pub struct TuiDirListDetailed<'a> { - dirlist: &'a JoshutoDirList, - display_options: &'a DisplayOption, - tab_display_options: &'a TabDisplayOption, + pub config: &'a AppConfig, + pub dirlist: &'a JoshutoDirList, + pub display_options: &'a DisplayOption, + pub tab_display_options: &'a TabDisplayOption, pub focused: bool, } impl<'a> TuiDirListDetailed<'a> { pub fn new( + config: &'a AppConfig, dirlist: &'a JoshutoDirList, display_options: &'a DisplayOption, tab_display_options: &'a TabDisplayOption, focused: bool, ) -> Self { Self { + config, dirlist, display_options, tab_display_options, @@ -104,6 +110,7 @@ impl<'a> Widget for TuiDirListDetailed<'a> { prefix.push_str(&line_number_prefix); print_entry( + self.config, buf, entry, style, @@ -130,6 +137,7 @@ fn get_entry_size_string(entry: &JoshutoDirEntry) -> String { #[allow(clippy::too_many_arguments)] fn print_entry( + config: &AppConfig, buf: &mut Buffer, entry: &JoshutoDirEntry, style: Style, @@ -143,7 +151,22 @@ fn print_entry( LinkType::Normal => "", LinkType::Symlink { .. } => "-> ", }; - let left_label_original = entry.label(); + + let name = entry.file_name(); + #[cfg(feature = "devicons")] + let label = { + if config.display_options_ref().show_icons() { + let icon = get_entry_icon(&config, entry.file_name(), entry.ext(), &entry.metadata); + format!("{icon} {name}") + } else { + name.to_string() + } + }; + + #[cfg(not(feature = "devicons"))] + let label = name.to_string(); + + let left_label_original = label; let right_label_original = format!( " {}{} ", symlink_string, @@ -168,7 +191,7 @@ fn print_entry( // factor left_label and right_label let drawing_width = drawing_width - prefix_width; let (left_label, right_label) = factor_labels_for_entry( - left_label_original, + &left_label_original, right_label_original.as_str(), drawing_width, ); -- cgit v1.2.3