diff options
author | Noah Too <krivahtoo@gmail.com> | 2022-06-22 18:03:43 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-06-22 11:03:43 -0400 |
commit | ba9a5726ab31071c83e83fc808a910fccdbd86f7 (patch) | |
tree | c04165a357b9d355d07be3ad4f1bca8fe9f8e54c | |
parent | f22eecdd3b7addb9b20ec4510106c6ee339d215e (diff) |
Add `flat` command (#177)
* add `flat` command
* fix bulk_rename command
* fix `open_file` commands
* update docs
* fix errors found by clippy
* fix errors found by clippy::unnecessary-to-owned
* fix open and open_with commands
* fix: crash on root dirs
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | docs/configuration/keymap.toml.md | 4 | ||||
-rw-r--r-- | src/commands/bulk_rename.rs | 22 | ||||
-rw-r--r-- | src/commands/flat.rs | 80 | ||||
-rw-r--r-- | src/commands/mod.rs | 1 | ||||
-rw-r--r-- | src/commands/open_file.rs | 48 | ||||
-rw-r--r-- | src/commands/show_tasks.rs | 36 | ||||
-rw-r--r-- | src/fs/entry.rs | 66 | ||||
-rw-r--r-- | src/key_command/command.rs | 2 | ||||
-rw-r--r-- | src/key_command/constants.rs | 1 | ||||
-rw-r--r-- | src/key_command/impl_appcommand.rs | 2 | ||||
-rw-r--r-- | src/key_command/impl_appexecute.rs | 2 | ||||
-rw-r--r-- | src/key_command/impl_comment.rs | 2 | ||||
-rw-r--r-- | src/key_command/impl_from_str.rs | 8 |
15 files changed, 201 insertions, 75 deletions
@@ -393,6 +393,7 @@ dependencies = [ "unicode-segmentation", "unicode-width", "users", + "walkdir", "whoami", "xdg", ] @@ -38,6 +38,7 @@ unicode-width = "^0" users = "^0" whoami = "^1" xdg = "^2" +walkdir = "^2" [features] devicons = [ "phf" ] diff --git a/docs/configuration/keymap.toml.md b/docs/configuration/keymap.toml.md index 8cc9def..1086f04 100644 --- a/docs/configuration/keymap.toml.md +++ b/docs/configuration/keymap.toml.md @@ -124,6 +124,10 @@ function joshuto() { - `line_nums 0` or `line_nums none`: disable displaying - `line_nums 1` or `line_nums absolute`: enable absolute numbers for each entry - `line_nums 2` or `line_nums relative`: enable numbers relative to selected entry + - `flat`: flattens the directory view up to the specified depth. + - `flat 3`: flatten directory upto 3 directories deep. + depth of 0 corresponds to the current directory. + its direct descendents have depth 1, and their descendents have depth 2, and so on. ## Navigation - `cursor_move_up`: moves the cursor up by x amount diff --git a/src/commands/bulk_rename.rs b/src/commands/bulk_rename.rs index 43facee..096869e 100644 --- a/src/commands/bulk_rename.rs +++ b/src/commands/bulk_rename.rs @@ -33,18 +33,18 @@ pub fn _bulk_rename(context: &mut AppContext) -> JoshutoResult { let mut file_path = path::PathBuf::from(&tmp_directory); file_path.push(rand_str); - let paths = context + let entries = context .tab_context_ref() .curr_tab_ref() .curr_list_ref() - .map_or(vec![], |s| s.get_selected_paths()); + .map_or(vec![], |s| s.iter_selected().collect()); /* write file names into temporary file to edit */ { let mut file = fs::File::create(&file_path)?; - for path in paths.iter() { - let file_name = path.file_name().unwrap(); - let file_name_as_bytes = file_name.to_str().unwrap().as_bytes(); + for path in entries.iter() { + let file_name = path.file_name(); + let file_name_as_bytes = file_name.as_bytes(); file.write_all(file_name_as_bytes)?; file.write_all(&[b'\n'])?; } @@ -66,7 +66,7 @@ pub fn _bulk_rename(context: &mut AppContext) -> JoshutoResult { } } - let mut paths_renamed: Vec<path::PathBuf> = Vec::with_capacity(paths.len()); + let mut paths_renamed: Vec<path::PathBuf> = Vec::with_capacity(entries.len()); { let file = std::fs::File::open(&file_path)?; @@ -82,7 +82,7 @@ pub fn _bulk_rename(context: &mut AppContext) -> JoshutoResult { } std::fs::remove_file(&file_path)?; } - if paths_renamed.len() < paths.len() { + if paths_renamed.len() < entries.len() { return Err(JoshutoError::new( JoshutoErrorKind::Io(io::ErrorKind::InvalidInput), "Insufficient inputs".to_string(), @@ -90,8 +90,8 @@ pub fn _bulk_rename(context: &mut AppContext) -> JoshutoResult { } println!("{}", termion::clear::All); - for (p, q) in paths.iter().zip(paths_renamed.iter()) { - println!("{:?} -> {:?}", p, q); + for (p, q) in entries.iter().zip(paths_renamed.iter()) { + println!("{:?} -> {:?}", p.file_name(), q); } print!("Continue with rename? (Y/n): "); std::io::stdout().flush()?; @@ -102,11 +102,11 @@ pub fn _bulk_rename(context: &mut AppContext) -> JoshutoResult { let user_input_trimmed = user_input.trim(); if user_input_trimmed != "n" || user_input_trimmed != "no" { - for (p, q) in paths.iter().zip(paths_renamed.iter()) { + for (p, q) in entries.iter().zip(paths_renamed.iter()) { let mut handle = process::Command::new("mv") .arg("-iv") .arg("--") - .arg(p) + .arg(p.file_name()) .arg(q) .spawn()?; handle.wait()?; diff --git a/src/commands/flat.rs b/src/commands/flat.rs new file mode 100644 index 0000000..632b321 --- /dev/null +++ b/src/commands/flat.rs @@ -0,0 +1,80 @@ +use std::io; +use std::path::Path; + +use crate::config::option::DisplayOption; +use crate::context::AppContext; +use crate::error::JoshutoResult; +use crate::fs::{JoshutoDirEntry, JoshutoDirList, JoshutoMetadata}; + +use walkdir::WalkDir; + +fn _is_hidden(entry: &walkdir::DirEntry) -> bool { + entry + .file_name() + .to_str() + .map(|s| s.starts_with('.')) + .unwrap_or(false) +} + +pub fn _walk_directory( + path: &Path, + options: &DisplayOption, + depth: usize, +) -> io::Result<Vec<JoshutoDirEntry>> { + let results: Vec<JoshutoDirEntry> = WalkDir::new(path) + .max_depth(depth) + .into_iter() + .filter_entry(|e| { + if options.show_hidden() { + true + } else { + !_is_hidden(e) + } + }) + .filter(|e| { + if let Ok(e) = e.as_ref() { + e.path().to_str().cmp(&path.to_str()).is_ne() + } else { + true + } + }) + .filter_map(|res| JoshutoDirEntry::from_walk(&res.ok()?, path, options).ok()) + .collect(); + + Ok(results) +} + +pub fn flatten(depth: usize, context: &mut AppContext) -> JoshutoResult { + let path = context.tab_context_ref().curr_tab_ref().cwd().to_path_buf(); + + let options = context.config_ref().display_options_ref().clone(); + + let mut index: Option<usize> = context + .tab_context_ref() + .curr_tab_ref() + .curr_list_ref() + .map(|lst| lst.get_index()) + .unwrap_or(None); + let viewport_index: usize = context + .tab_context_ref() + .curr_tab_ref() + .curr_list_ref() + .map(|lst| lst.first_index_for_viewport()) + .unwrap_or(0); + + let mut contents = _walk_directory(path.as_path(), &options, depth)?; + let history = context.tab_context_mut().curr_tab_mut().history_mut(); + + if contents.is_empty() { + index = None; + } + + let sort_options = options.sort_options_ref(); + contents.sort_by(|f1, f2| sort_options.compare(f1, f2)); + + let metadata = JoshutoMetadata::from(path.as_path())?; + let dirlist = JoshutoDirList::new(path.clone(), contents, index, viewport_index, metadata); + history.insert(path, dirlist); + + Ok(()) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 1f447c2..f4e0ee5 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -4,6 +4,7 @@ pub mod command_line; pub mod cursor_move; pub mod delete_files; pub mod file_ops; +pub mod flat; pub mod line_nums; pub mod new_directory; pub mod numbered_command; diff --git a/src/commands/open_file.rs b/src/commands/open_file.rs index 24acae1..dfcbb9a 100644 --- a/src/commands/open_file.rs +++ b/src/commands/open_file.rs @@ -26,7 +26,7 @@ pub fn get_options<'a>(path: &path::Path) -> Vec<&'a AppMimetypeEntry> { pub fn open(context: &mut AppContext, backend: &mut TuiBackend) -> JoshutoResult { let curr_list = context.tab_context_ref().curr_tab_ref().curr_list_ref(); - let entry = curr_list.and_then(|s| s.curr_entry_ref()); + let entry = curr_list.and_then(|s| s.curr_entry_ref().cloned()); match entry { None => (), @@ -35,16 +35,15 @@ pub fn open(context: &mut AppContext, backend: &mut TuiBackend) -> JoshutoResult change_directory::cd(path.as_path(), context)?; reload::soft_reload(context.tab_context_ref().index, context)?; } - Some(_) => { - let paths = curr_list.map_or_else(Vec::new, |s| s.get_selected_paths()); - let path = paths.get(0).ok_or_else(|| { - JoshutoError::new( - JoshutoErrorKind::Io(io::ErrorKind::NotFound), - String::from("No files selected"), - ) - })?; - - let files: Vec<&std::ffi::OsStr> = paths.iter().filter_map(|e| e.file_name()).collect(); + Some(entry) => { + let paths = curr_list.map_or_else(Vec::new, |s| s.iter_selected().cloned().collect()); + let path = if paths.is_empty() { + entry.file_path() + } else { + paths.get(0).unwrap().file_path() + }; + + let files: Vec<&str> = paths.iter().map(|e| e.file_name()).collect(); let options = get_options(path); let option = options.iter().find(|option| option.program_exists()); @@ -160,20 +159,25 @@ where } pub fn open_with_interactive(context: &mut AppContext, backend: &mut TuiBackend) -> JoshutoResult { - let paths = context + let mut paths = context .tab_context_ref() .curr_tab_ref() .curr_list_ref() - .map_or(vec![], |s| s.get_selected_paths()); + .map_or(vec![], |s| s.iter_selected().cloned().collect()); if paths.is_empty() { - return Err(JoshutoError::new( - JoshutoErrorKind::Io(io::ErrorKind::NotFound), - String::from("No files selected"), - )); + paths.push( + context + .tab_context_ref() + .curr_tab_ref() + .curr_list_ref() + .and_then(|s| s.curr_entry_ref()) + .unwrap() + .clone(), + ); } - let files: Vec<&std::ffi::OsStr> = paths.iter().filter_map(|e| e.file_name()).collect(); - let options = get_options(paths[0].as_path()); + let files: Vec<&str> = paths.iter().map(|e| e.file_name()).collect(); + let options = get_options(paths[0].file_path()); open_with_helper(context, backend, options, &files)?; Ok(()) @@ -188,7 +192,7 @@ pub fn open_with_index( .tab_context_ref() .curr_tab_ref() .curr_list_ref() - .map_or(vec![], |s| s.get_selected_paths()); + .map_or(vec![], |s| s.iter_selected().cloned().collect()); if paths.is_empty() { return Err(JoshutoError::new( @@ -196,8 +200,8 @@ pub fn open_with_index( String::from("No files selected"), )); } - let files: Vec<&std::ffi::OsStr> = paths.iter().filter_map(|e| e.file_name()).collect(); - let options = get_options(paths[0].as_path()); + let files: Vec<&str> = paths.iter().map(|e| e.file_name()).collect(); + let options = get_options(paths[0].file_path()); if index >= options.len() { return Err(JoshutoError::new( diff --git a/src/commands/show_tasks.rs b/src/commands/show_tasks.rs index ae2a43a..4b22860 100644 --- a/src/commands/show_tasks.rs +++ b/src/commands/show_tasks.rs @@ -21,29 +21,25 @@ pub fn show_tasks( if let Ok(event) = context.poll_event() { match event { AppEvent::Termion(key) => { - match key { - key => match keymap_t.task_view.get(&key) { - None => { - context - .message_queue_mut() - .push_info(format!("Unmapped input: {}", key.to_string())); + let key = key; + match keymap_t.task_view.get(&key) { + None => { + context + .message_queue_mut() + .push_info(format!("Unmapped input: {}", key.to_string())); + } + Some(CommandKeybind::SimpleKeybind(command)) => { + if let Command::ShowTasks = command { + break; } - Some(CommandKeybind::SimpleKeybind(command)) => match command { - Command::ShowTasks => break, - _ => {} - }, - Some(CommandKeybind::CompositeKeybind(m)) => { - let cmd = - process_event::get_input_while_composite(backend, context, m); + } + Some(CommandKeybind::CompositeKeybind(m)) => { + let cmd = process_event::get_input_while_composite(backend, context, m); - if let Some(command) = cmd { - match command { - Command::ShowTasks => break, - _ => {} - } - } + if let Some(Command::ShowTasks) = cmd { + break; } - }, + } } context.flush_event(); } diff --git a/src/fs/entry.rs b/src/fs/entry.rs index 4dbaeb4..8337bc4 100644 --- a/src/fs/entry.rs +++ b/src/fs/entry.rs @@ -20,37 +20,30 @@ impl JoshutoDirEntry { pub fn from(direntry: &fs::DirEntry, options: &DisplayOption) -> io::Result<Self> { let path = direntry.path(); - let mut metadata = JoshutoMetadata::from(&path)?; let name = direntry .file_name() .as_os_str() .to_string_lossy() .to_string(); - if options.automatically_count_files() && metadata.file_type().is_dir() { - if let Ok(size) = get_directory_size(path.as_path()) { - metadata.update_directory_size(size); - } - } + Self::gen_entry(path, name, options) + } - #[cfg(feature = "devicons")] - let label = if options.show_icons() { - create_icon_label(name.as_str(), &metadata) - } else { - name.clone() - }; + pub fn from_walk( + direntry: &walkdir::DirEntry, + base: &path::Path, + options: &DisplayOption, + ) -> io::Result<Self> { + let path = direntry.path().to_path_buf(); - #[cfg(not(feature = "devicons"))] - let label = name.clone(); + let name = direntry + .path() + .strip_prefix(base) + .unwrap() + .to_string_lossy() + .to_string(); - Ok(Self { - name, - label, - path, - metadata, - selected: false, - _marked: false, - }) + Self::gen_entry(path, name, options) } pub fn file_name(&self) -> &str { @@ -84,6 +77,35 @@ impl JoshutoDirEntry { None => "", } } + + fn gen_entry(path: path::PathBuf, name: String, options: &DisplayOption) -> io::Result<Self> { + let mut metadata = JoshutoMetadata::from(&path)?; + + if options.automatically_count_files() && metadata.file_type().is_dir() { + if let Ok(size) = get_directory_size(path.as_path()) { + metadata.update_directory_size(size); + } + } + + #[cfg(feature = "devicons")] + let label = if options.show_icons() { + create_icon_label(name.as_str(), &metadata) + } else { + name.clone() + }; + + #[cfg(not(feature = "devicons"))] + let label = name.clone(); + + Ok(Self { + name, + label, + path, + metadata, + selected: false, + _marked: false, + }) + } } impl std::fmt::Display for JoshutoDirEntry { diff --git a/src/key_command/command.rs b/src/key_command/command.rs index 3cc0cb3..495cff5 100644 --- a/src/key_command/command.rs +++ b/src/key_command/command.rs @@ -69,6 +69,8 @@ pub enum Command { SwitchLineNums(LineNumberStyle), + Flat(usize), + Sort(SortType), SortReverse, diff --git a/src/key_command/constants.rs b/src/key_command/constants.rs index 6e4ea09..ba5db2e 100644 --- a/src/key_command/constants.rs +++ b/src/key_command/constants.rs @@ -75,6 +75,7 @@ cmd_constants![ (CMD_SUBDIR_FZF, "subdir_fzf"), (CMD_ZOXIDE, "z"), (CMD_ZOXIDE_INTERACTIVE, "zi"), + (CMD_FLAT, "flat"), ]; pub fn complete_command(partial_command: &str) -> Vec<Pair> { diff --git a/src/key_command/impl_appcommand.rs b/src/key_command/impl_appcommand.rs index 68f4824..c04ade7 100644 --- a/src/key_command/impl_appcommand.rs +++ b/src/key_command/impl_appcommand.rs @@ -63,6 +63,8 @@ impl AppCommand for Command { Self::ShowTasks => CMD_SHOW_TASKS, + Self::Flat(_) => CMD_FLAT, + Self::Sort(_) => CMD_SORT, Self::SortReverse => CMD_SORT_REVERSE, diff --git a/src/key_command/impl_appexecute.rs b/src/key_command/impl_appexecute.rs index bf2b9fc..3f88f4b 100644 --- a/src/key_command/impl_appexecute.rs +++ b/src/key_command/impl_appexecute.rs @@ -95,6 +95,8 @@ impl AppExecute for Command { } Self::SwitchLineNums(d) => line_nums::switch_line_numbering(context, *d), + Self::Flat(depth) => flat::flatten(*depth, context), + Self::ToggleHiddenFiles => show_hidden::toggle_hidden(context), Self::TabSwitch(i) => { diff --git a/src/key_command/impl_comment.rs b/src/key_command/impl_comment.rs index 59f0d00..bd5e4e6 100644 --- a/src/key_command/impl_comment.rs +++ b/src/key_command/impl_comment.rs @@ -85,6 +85,8 @@ impl CommandComment for Command { Self::SwitchLineNums(_) => "Switch line numbering", + Self::Flat(_) => "Flattern directory list", + Self::Sort(sort_type) => match sort_type { SortType::Lexical => "Sort lexically", SortType::Mtime => "Sort by modifiaction time", diff --git a/src/key_command/impl_from_str.rs b/src/key_command/impl_from_str.rs index 7a19cce..c09fe66 100644 --- a/src/key_command/impl_from_str.rs +++ b/src/key_command/impl_from_str.rs @@ -316,6 +316,14 @@ impl std::str::FromStr for Command { _ => LineNumberStyle::None, }; Ok(Self::SwitchLineNums(policy)) + } else if command == CMD_FLAT { + match arg.parse::<usize>() { + Ok(i) => Ok(Self::Flat(i + 1)), + Err(e) => Err(JoshutoError::new( + JoshutoErrorKind::InvalidParameters, + format!("{}: {}", command, e), + )), + } } else { Err(JoshutoError::new( JoshutoErrorKind::UnrecognizedCommand, |