diff options
35 files changed, 1013 insertions, 981 deletions
@@ -11,17 +11,17 @@ edition = "2018" alphanumeric-sort = "^1" chrono = "^0" dirs = "^1" -fs_extra = "^1" lazy_static = "^1" -libc = "^0.2" +libc = "^0" notify = "^4" -open = "1.2.2" +open = "^1" rand = "^0" -rustyline = "^4.1" +rustyline = "^4" serde = "^1" serde_derive = "^1" -structopt = "0.2.14" +structopt = "^0" text_io = "^0" +termion = "*" toml = "^0" unicode-width = "^0" users = "^0" diff --git a/config/keymap.toml b/config/keymap.toml index a7d21d6..d2eee9c 100644 --- a/config/keymap.toml +++ b/config/keymap.toml @@ -2,210 +2,185 @@ # backspace = 263 [[mapcommand]] -keys = [ 81 ] -command = "force_quit" +command = "new_tab" +keys = [ "T" ] [[mapcommand]] -keys = [ 87 ] +command = "new_tab" +keys = [ "ctrl+t" ] +[[mapcommand]] command = "close_tab" +keys = [ "W" ] [[mapcommand]] -keys = [ 113 ] command = "close_tab" - +keys = [ "ctrl+w" ] +[[mapcommand]] +command = "close_tab" +keys = [ "q" ] +[[mapcommand]] +command = "force_quit" +keys = [ "Q" ] [[mapcommand]] -keys = [ 82 ] command = "reload_dir_list" +keys = [ "R" ] +[[mapcommand]] +command = "toggle_hidden" +keys = [ "z", "h" ] +[[mapcommand]] +command = "tab_switch" +args = [ "1" ] +keys = [ "\t" ] +[[mapcommand]] +command = "tab_switch" +args = [ "-1" ] +keys = [ "shift+\t" ] [[mapcommand]] -keys = [ 259 ] command = "cursor_move_up" - +keys = [ "up" ] [[mapcommand]] -keys = [ 107 ] command = "cursor_move_up" - +keys = [ "k" ] [[mapcommand]] -keys = [ 258 ] command = "cursor_move_down" - +keys = [ "down" ] [[mapcommand]] -keys = [ 106 ] command = "cursor_move_down" - +keys = [ "j" ] [[mapcommand]] -keys = [ 260 ] -command = "cd" -args = [ ".." ] - -[[mapcommand]] -keys = [ 104 ] -command = "cd" -args = [ ".." ] - -[[mapcommand]] -keys = [ 261 ] -command = "open_file" - -[[mapcommand]] -keys = [ 108 ] -command = "open_file" - +command = "cursor_move_end" +keys = [ "end" ] [[mapcommand]] -keys = [ 10 ] -command = "open_file" - - +command = "cursor_move_end" +keys = [ "G" ] [[mapcommand]] -keys = [ 262 ] command = "cursor_move_home" - +keys = [ "home" ] [[mapcommand]] -keys = [ 103, 103 ] command = "cursor_move_home" - +keys = [ "g", "g" ] [[mapcommand]] -keys = [ 360 ] -command = "cursor_move_end" - -[[mapcommand]] -keys = [ 71 ] -command = "cursor_move_end" - - +command = "cursor_move_page_up" +keys = [ "page_up" ] [[mapcommand]] -keys = [ 338 ] command = "cursor_move_page_down" +keys = [ "page_down" ] [[mapcommand]] -keys = [ 339 ] -command = "cursor_move_page_up" - - +command = "open_file" +keys = [ "right" ] +[[mapcommand]] +command = "open_file" +keys = [ "l" ] +[[mapcommand]] +command = "open_file" +keys = [ "\n" ] [[mapcommand]] -keys = [ 114 ] command = "open_file_with" - +keys = [ "r" ] [[mapcommand]] -keys = [ 122, 104 ] -command = "toggle_hidden" - +command = "cd" +keys = [ "c", "d" ] +[[mapcommand]] +command = "cd" +args = [ ".." ] +keys = [ "left" ] +[[mapcommand]] +command = "cd" +args = [ ".." ] +keys = [ "h" ] [[mapcommand]] -keys = [ 100, 100 ] command = "cut_files" - +keys = [ "d", "d" ] [[mapcommand]] -keys = [ 121, 121 ] command = "copy_files" - +keys = [ "y", "y" ] [[mapcommand]] -keys = [ 112, 112 ] command = "paste_files" - +keys = [ "p", "p" ] [[mapcommand]] -keys = [ 112, 111 ] command = "paste_files" args = [ "--overwrite" ] - -[[mapcommand]] -keys = [ 97 ] -command = "rename_append" - -[[mapcommand]] -keys = [ 65 ] -command = "rename_prepend" - +keys = [ "p", "o" ] [[mapcommand]] -keys = [ 99, 119 ] -command = "console" -args = [ "rename " ] - -[[mapcommand]] -keys = [ 100, 68 ] command = "delete_files" - +keys = [ "d", "D" ] [[mapcommand]] -keys = [ 330 ] command = "delete_files" +keys = [ "delete" ] +[[mapcommand]] +command = "rename_append" +keys = [ "a" ] +[[mapcommand]] +command = "rename_prepend" +keys = [ "A" ] [[mapcommand]] -keys = [ 32 ] command = "select_files" args = [ "--toggle" ] - +keys = [ " " ] [[mapcommand]] -keys = [ 116 ] command = "select_files" args = [ "--toggle", "--all" ] +keys = [ "t" ] [[mapcommand]] -keys = [ 109, 107 ] -command = "console" -args = [ "mkdir " ] - +command = "search_next" +keys = [ "n", "p" ] [[mapcommand]] -keys = [ 59 ] -command = "console" +command = "search_prev" +keys = [ "n", "n" ] [[mapcommand]] -keys = [ 103, 104 ] -command = "cd" - +command = "bulk_rename" +keys = [ "b", "b" ] [[mapcommand]] -keys = [ 103, 114 ] -command = "cd" -args = [ "/" ] +command = "set_mode" +keys = [ "=" ] [[mapcommand]] -keys = [ 103, 101 ] -command = "cd" -args = [ "/etc" ] - +command = "console" +keys = [ ";" ] [[mapcommand]] -keys = [ 47 ] command = "console" -args = [ "search " ] - +args = [ "mkdir " ] +keys = [ "m", "k" ] [[mapcommand]] -keys = [ 78 ] -command = "search_prev" - +command = "console" +args = [ "rename " ] +keys = [ "c", "w" ] [[mapcommand]] -keys = [ 110 ] -command = "search_next" - +command = "console" +args = [ "search " ] +keys = [ "/" ] [[mapcommand]] -keys = [ 20 ] -command = "new_tab" -[[mapcommand]] -keys = [ 84 ] -command = "new_tab" +command = "console" +args = [ "search " ] +keys = [ "/" ] [[mapcommand]] -keys = [ 23 ] -command = "close_tab" - +command = "sort" +args = [ "lexical" ] +keys = [ "s", "l" ] [[mapcommand]] -keys = [ 9 ] -command = "tab_switch" -args = [ "1" ] - +command = "sort" +args = [ "mtime" ] +keys = [ "s", "m" ] [[mapcommand]] -keys = [ 353 ] -command = "tab_switch" -args = [ "-1" ] +command = "sort" +args = [ "natural" ] +keys = [ "s", "n" ] [[mapcommand]] -keys = [ 45 ] -command = "set_mode" - -## Features not yet implemented - +command = "cd" +args = [ "/" ] +keys = [ "g", "/" ] [[mapcommand]] -keys = [ 98, 98 ] -command = "bulk_rename" - +command = "cd" +args = [ "/etc" ] +keys = [ "g", "e" ] diff --git a/src/commands/bulk_rename.rs b/src/commands/bulk_rename.rs index dab20c7..cd2dcf8 100644 --- a/src/commands/bulk_rename.rs +++ b/src/commands/bulk_rename.rs @@ -97,7 +97,7 @@ impl BulkRename { for (p, q) in paths.iter().zip(paths_renamed.iter()) { println!("{:?} -> {:?}", p, q); } - print!("Continue with rename? (y/N): "); + print!("Continue with rename? (Y/n): "); std::io::stdout().flush()?; let mut user_input = String::with_capacity(4); @@ -105,7 +105,7 @@ impl BulkRename { user_input = user_input.to_lowercase(); let user_input_trimmed = user_input.trim(); - if user_input_trimmed == "y" || user_input_trimmed == "yes" { + if user_input_trimmed != "n" || user_input_trimmed != "no" { for (p, q) in paths.iter().zip(paths_renamed.iter()) { let mut command = process::Command::new("mv"); command.arg("-iv"); diff --git a/src/commands/delete_files.rs b/src/commands/delete_files.rs index adaed07..8233ce2 100644 --- a/src/commands/delete_files.rs +++ b/src/commands/delete_files.rs @@ -5,6 +5,7 @@ use crate::commands::{JoshutoCommand, JoshutoRunnable, ReloadDirList}; use crate::context::JoshutoContext; use crate::error::JoshutoResult; use crate::ui; +use crate::util::event::Event; use crate::window::JoshutoView; use crate::KEYMAP_T; @@ -35,32 +36,50 @@ impl DeleteFiles { fn delete_files(context: &mut JoshutoContext, view: &JoshutoView) -> std::io::Result<()> { ui::wprint_msg(&view.bot_win, "Delete selected files? (Y/n)"); - ncurses::timeout(-1); ncurses::doupdate(); let curr_tab = &mut context.tabs[context.curr_tab_index]; - let mut ch = ncurses::getch(); - if ch == 'y' as i32 || ch == KEYMAP_T.enter { - let paths = curr_tab.curr_list.get_selected_paths(); - if paths.is_empty() { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "no files selected", - )); - } - if paths.len() > 1 { - ui::wprint_msg(&view.bot_win, "Are you sure? (y/N)"); - ncurses::doupdate(); - ch = ncurses::getch(); - } else { - ch = 'y' as i32; - } - if ch == 'y' as i32 { - Self::remove_files(&paths)?; - ui::wprint_msg(&view.bot_win, "Deleted files"); - ReloadDirList::reload(context.curr_tab_index, context)?; + let paths = curr_tab.curr_list.get_selected_paths(); + if paths.is_empty() { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "no files selected", + )); + } + + let mut ch = termion::event::Key::Char('n'); + while let Ok(evt) = context.events.next() { + match evt { + Event::Input(key) => { + if key == termion::event::Key::Char('y') || + key == termion::event::Key::Char('\n') { + if paths.len() > 1 { + ui::wprint_msg(&view.bot_win, "Are you sure? (y/N)"); + ncurses::doupdate(); + while let Ok(evt) = context.events.next() { + match evt { + Event::Input(key) => { + ch = key; + break; + } + _ => {} + } + } + } else { + ch = termion::event::Key::Char('y'); + } + } + break; + } + _ => {} } } + + if ch == termion::event::Key::Char('y') { + Self::remove_files(&paths)?; + ui::wprint_msg(&view.bot_win, "Deleted files"); + ReloadDirList::reload(context.curr_tab_index, context)?; + } Ok(()) } } diff --git a/src/commands/file_operations.rs b/src/commands/file_operations.rs deleted file mode 100644 index a701473..0000000 --- a/src/commands/file_operations.rs +++ /dev/null @@ -1,251 +0,0 @@ -use lazy_static::lazy_static; -use std::path; -use std::sync::{atomic, mpsc, Mutex}; -use std::thread; -use std::time; - -use crate::commands::{JoshutoCommand, JoshutoRunnable}; -use crate::context::JoshutoContext; -use crate::error::JoshutoResult; -use crate::fs::{fs_extra_extra, JoshutoDirList}; -use crate::window::JoshutoView; - -lazy_static! { - static ref SELECTED_FILES: Mutex<Option<Vec<path::PathBuf>>> = Mutex::new(None); - static ref FILE_OPERATION: Mutex<FileOp> = Mutex::new(FileOp::Copy); - static ref TAB_SRC: atomic::AtomicUsize = atomic::AtomicUsize::new(0); -} - -enum FileOp { - Cut, - Copy, -} - -struct LocalState; - -impl LocalState { - pub fn set_file_op(operation: FileOp) { - let mut data = FILE_OPERATION.lock().unwrap(); - *data = operation; - } - - pub fn set_tab_src(tab_index: usize) { - TAB_SRC.store(tab_index, atomic::Ordering::Release); - } - - pub fn repopulated_selected_files(dirlist: &JoshutoDirList) -> std::io::Result<()> { - let selected = dirlist.get_selected_paths(); - if selected.is_empty() { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "no files selected", - )) - } else { - let selected_clone: Vec<path::PathBuf> = - selected.iter().map(|p| (*p).clone()).collect(); - let mut data = SELECTED_FILES.lock().unwrap(); - *data = Some(selected_clone); - Ok(()) - } - } -} - -pub struct FileOperationThread<T, Q> { - pub tab_src: usize, - pub tab_dest: usize, - pub handle: thread::JoinHandle<std::io::Result<T>>, - pub recv: mpsc::Receiver<Q>, -} - -impl<T, Q> FileOperationThread<T, Q> { - pub fn recv_timeout( - &self, - wait_duration: &time::Duration, - ) -> Result<Q, mpsc::RecvTimeoutError> { - self.recv.recv_timeout(*wait_duration) - } -} - -#[derive(Clone, Debug)] -pub struct CutFiles; - -impl CutFiles { - pub fn new() -> Self { - CutFiles - } - pub const fn command() -> &'static str { - "cut_files" - } -} - -impl JoshutoCommand for CutFiles {} - -impl std::fmt::Display for CutFiles { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.write_str(Self::command()) - } -} - -impl JoshutoRunnable for CutFiles { - fn execute(&self, context: &mut JoshutoContext, _: &JoshutoView) -> JoshutoResult<()> { - let curr_tab = context.curr_tab_ref(); - LocalState::repopulated_selected_files(&curr_tab.curr_list)?; - LocalState::set_file_op(FileOp::Cut); - LocalState::set_tab_src(context.curr_tab_index); - Ok(()) - } -} - -#[derive(Clone, Debug)] -pub struct CopyFiles; - -impl CopyFiles { - pub fn new() -> Self { - CopyFiles - } - pub const fn command() -> &'static str { - "copy_files" - } -} - -impl JoshutoCommand for CopyFiles {} - -impl std::fmt::Display for CopyFiles { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.write_str(Self::command()) - } -} - -impl JoshutoRunnable for CopyFiles { - fn execute(&self, context: &mut JoshutoContext, _: &JoshutoView) -> JoshutoResult<()> { - let curr_tab = context.curr_tab_ref(); - LocalState::repopulated_selected_files(&curr_tab.curr_list)?; - LocalState::set_file_op(FileOp::Copy); - LocalState::set_tab_src(context.curr_tab_index); - Ok(()) - } -} - -pub struct PasteFiles { - options: fs_extra::dir::CopyOptions, -} - -impl JoshutoCommand for PasteFiles {} - -impl std::fmt::Display for PasteFiles { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "{} overwrite={} skip_exist={}", - Self::command(), - self.options.overwrite, - self.options.skip_exist, - ) - } -} - -impl std::fmt::Debug for PasteFiles { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.write_str(Self::command()) - } -} - -impl JoshutoRunnable for PasteFiles { - fn execute(&self, context: &mut JoshutoContext, _: &JoshutoView) -> JoshutoResult<()> { - let file_operation = FILE_OPERATION.lock().unwrap(); - - let thread = match *file_operation { - FileOp::Copy => self.copy_paste(context), - FileOp::Cut => self.cut_paste(context), - }?; - - context.threads.push(thread); - Ok(()) - } -} - -impl PasteFiles { - pub fn new(options: fs_extra::dir::CopyOptions) -> Self { - PasteFiles { options } - } - pub const fn command() -> &'static str { - "paste_files" - } - - fn cut_paste( - &self, - context: &mut JoshutoContext, - ) -> std::io::Result<FileOperationThread<u64, fs_extra::TransitProcess>> { - let paths = - SELECTED_FILES.lock().unwrap().take().ok_or_else(|| { - std::io::Error::new(std::io::ErrorKind::Other, "no files selected") - })?; - if paths.is_empty() { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "no files selected", - )); - } - - let tab_src = TAB_SRC.load(atomic::Ordering::SeqCst); - let tab_dest = context.curr_tab_index; - let destination = context.tabs[tab_dest].curr_path.clone(); - - let options = self.options.clone(); - let (tx, rx) = mpsc::channel(); - - let handle = thread::spawn(move || { - let progress_handle = |process_info: fs_extra::TransitProcess| { - tx.send(process_info); - fs_extra::dir::TransitProcessResult::ContinueOrAbort - }; - fs_extra_extra::fs_cut_with_progress(&paths, &destination, options, progress_handle) - }); - - let thread = FileOperationThread { - tab_src, - tab_dest, - handle, - recv: rx, - }; - Ok(thread) - } - - fn copy_paste( - &self, - context: &mut JoshutoContext, - ) -> std::io::Result<FileOperationThread<u64, fs_extra::TransitProcess>> { - let paths = - SELECTED_FILES.lock().unwrap().take().ok_or_else(|| { - std::io::Error::new(std::io::ErrorKind::Other, "no files selected") - })?; - if paths.is_empty() { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "no files selected", - )); - } - - let tab_src = TAB_SRC.load(atomic::Ordering::SeqCst); - let tab_dest = context.curr_tab_index; - let destination = context.tabs[tab_dest].curr_path.clone(); - - let options = self.options.clone(); - let (tx, rx) = mpsc::channel(); - let handle = thread::spawn(move || { - let progress_handle = |process_info: fs_extra::TransitProcess| { - tx.send(process_info); - fs_extra::dir::TransitProcessResult::ContinueOrAbort - }; - fs_extra_extra::fs_copy_with_progress(&paths, &destination, options, progress_handle) - }); - - let thread = FileOperationThread { - tab_src, - tab_dest, - handle, - recv: rx, - }; - Ok(thread) - } -} diff --git a/src/commands/file_ops/copy.rs b/src/commands/file_ops/copy.rs new file mode 100644 index 0000000..a1a562e --- /dev/null +++ b/src/commands/file_ops/copy.rs @@ -0,0 +1,36 @@ +use crate::commands::{JoshutoCommand, JoshutoRunnable}; +use crate::context::JoshutoContext; +use crate::error::JoshutoResult; +use crate::window::JoshutoView; + +use super::local_state::{FileOp, LocalState}; + +#[derive(Clone, Debug)] +pub struct CopyFiles; + +impl CopyFiles { + pub fn new() -> Self { + CopyFiles + } + pub const fn command() -> &'static str { + "copy_files" + } +} + +impl JoshutoCommand for CopyFiles {} + +impl std::fmt::Display for CopyFiles { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.write_str(Self::command()) + } +} + +impl JoshutoRunnable for CopyFiles { + fn execute(&self, context: &mut JoshutoContext, _: &JoshutoView) -> JoshutoResult<()> { + let curr_tab = context.curr_tab_ref(); + LocalState::repopulated_selected_files(&curr_tab.curr_list)?; + LocalState::set_file_op(FileOp::Copy); + LocalState::set_tab_src(context.curr_tab_index); + Ok(()) + } +} diff --git a/src/commands/file_ops/cut.rs b/src/commands/file_ops/cut.rs new file mode 100644 index 0000000..f466ca7 --- /dev/null +++ b/src/commands/file_ops/cut.rs @@ -0,0 +1,36 @@ +use crate::commands::{JoshutoCommand, JoshutoRunnable}; +use crate::context::JoshutoContext; +use crate::error::JoshutoResult; +use crate::window::JoshutoView; + +use super::local_state::{FileOp, LocalState}; + +#[derive(Clone, Debug)] +pub struct CutFiles; + +impl CutFiles { + pub fn new() -> Self { + CutFiles + } + pub const fn command() -> &'static str { + "cut_files" + } +} + +impl JoshutoCommand for CutFiles {} + +impl std::fmt::Display for CutFiles { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.write_str(Self::command()) + } +} + +impl JoshutoRunnable for CutFiles { + fn execute(&self, context: &mut JoshutoContext, _: &JoshutoView) -> JoshutoResult<()> { + let curr_tab = context.curr_tab_ref(); + LocalState::repopulated_selected_files(&curr_tab.curr_list)?; + LocalState::set_file_op(FileOp::Cut); + LocalState::set_tab_src(context.curr_tab_index); + Ok(()) + } +} diff --git a/src/commands/file_ops/local_state.rs b/src/commands/file_ops/local_state.rs new file mode 100644 index 0000000..2849893 --- /dev/null +++ b/src/commands/file_ops/local_state.rs @@ -0,0 +1,56 @@ +use lazy_static::lazy_static; + +use std::path; +use std::sync::{atomic, Mutex}; + +use crate::fs::JoshutoDirList; + +lazy_static! { + static ref SELECTED_FILES: Mutex<Option<Vec<path::PathBuf>>> = Mutex::new(None); + static ref FILE_OPERATION: Mutex<FileOp> = Mutex::new(FileOp::Copy); + static ref TAB_SRC: atomic::AtomicUsize = atomic::AtomicUsize::new(0); +} + +#[derive(Clone, Debug)] +pub enum FileOp { + Cut, + Copy, +} + +pub struct LocalState; + +impl LocalState { + pub fn set_file_op(operation: FileOp) { + let mut data = FILE_OPERATION.lock().unwrap(); + *data = operation; + } + + pub fn set_tab_src(tab_index: usize) { + TAB_SRC.store(tab_index, atomic::Ordering::Release); + } + + pub fn repopulated_selected_files(dirlist: &JoshutoDirList) -> std::io::Result<()> { + let selected = dirlist.get_selected_paths(); + if selected.is_empty() { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "no files selected", + )) + } else { + let selected_clone: Vec<path::PathBuf> = + selected.iter().map(|p| (*p).clone()).collect(); + let mut data = SELECTED_FILES.lock().unwrap(); + *data = Some(selected_clone); + Ok(()) + } + } + + pub fn take_selected_files() -> Option<Vec<path::PathBuf>> { + let paths = SELECTED_FILES.lock().unwrap().take(); + paths + } + + pub fn get_file_operation() -> FileOp { + (*FILE_OPERATION.lock().unwrap()).clone() + } +} diff --git a/src/commands/file_ops/mod.rs b/src/commands/file_ops/mod.rs new file mode 100644 index 0000000..e4a0da3 --- /dev/null +++ b/src/commands/file_ops/mod.rs @@ -0,0 +1,11 @@ +mod copy; +mod cut; +mod local_state; +mod name_resolution; +mod paste; +mod paste_copy; +mod paste_cut; + +pub use self::copy::CopyFiles; +pub use self::cut::CutFiles; +pub use self::paste::PasteFiles; diff --git a/src/commands/file_ops/name_resolution.rs b/src/commands/file_ops/name_resolution.rs new file mode 100644 index 0000000..a8e520b --- /dev/null +++ b/src/commands/file_ops/name_resolution.rs @@ -0,0 +1,15 @@ +use std::path; + +pub fn rename_filename_conflict(path: &mut path::PathBuf) { + let file_name = path.file_name().unwrap().to_os_string(); + for i in 0.. { + if !path.exists() { + break; + } + path.pop(); + + let mut file_name = file_name.clone(); + file_name.push(&format!("_{}", i)); + path.push(file_name); + } +} diff --git a/src/commands/file_ops/paste.rs b/src/commands/file_ops/paste.rs new file mode 100644 index 0000000..ba5dc43 --- /dev/null +++ b/src/commands/file_ops/paste.rs @@ -0,0 +1,55 @@ +use crate::commands::{JoshutoCommand, JoshutoRunnable}; +use crate::context::JoshutoContext; +use crate::error::JoshutoResult; +use crate::io::Options; +use crate::window::JoshutoView; + +use super::local_state::{FileOp, LocalState}; +use super::paste_copy::paste_copy; +use super::paste_cut::paste_cut; + +pub struct PasteFiles { + options: Options, +} + +impl |