summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNoah Too <krivahtoo@gmail.com>2022-06-22 18:03:43 +0300
committerGitHub <noreply@github.com>2022-06-22 11:03:43 -0400
commitba9a5726ab31071c83e83fc808a910fccdbd86f7 (patch)
treec04165a357b9d355d07be3ad4f1bca8fe9f8e54c
parentf22eecdd3b7addb9b20ec4510106c6ee339d215e (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.lock1
-rw-r--r--Cargo.toml1
-rw-r--r--docs/configuration/keymap.toml.md4
-rw-r--r--src/commands/bulk_rename.rs22
-rw-r--r--src/commands/flat.rs80
-rw-r--r--src/commands/mod.rs1
-rw-r--r--src/commands/open_file.rs48
-rw-r--r--src/commands/show_tasks.rs36
-rw-r--r--src/fs/entry.rs66
-rw-r--r--src/key_command/command.rs2
-rw-r--r--src/key_command/constants.rs1
-rw-r--r--src/key_command/impl_appcommand.rs2
-rw-r--r--src/key_command/impl_appexecute.rs2
-rw-r--r--src/key_command/impl_comment.rs2
-rw-r--r--src/key_command/impl_from_str.rs8
15 files changed, 201 insertions, 75 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 081941e..9a4c329 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -393,6 +393,7 @@ dependencies = [
"unicode-segmentation",
"unicode-width",
"users",
+ "walkdir",
"whoami",
"xdg",
]
diff --git a/Cargo.toml b/Cargo.toml
index bad3957..66084ca 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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,