use std::path; use crate::context::AppContext; use crate::error::{JoshutoError, JoshutoErrorKind, JoshutoResult}; use crate::io::IoWorkerOptions; use crate::ui::TuiBackend; use crate::util::load_child::LoadChild; use crate::util::select::SelectOption; use crate::util::sort::SortType; use crate::HOME_DIR; use dirs_next::home_dir; use shellexpand::tilde_with_context; use super::*; #[derive(Clone, Debug)] pub enum KeyCommand { BulkRename, ChangeDirectory(path::PathBuf), CommandLine(String, String), CutFiles, CopyFiles, PasteFiles(IoWorkerOptions), CopyFileName, CopyFilePath, CopyDirName, CursorMoveUp(usize), CursorMoveDown(usize), CursorMoveHome, CursorMoveEnd, CursorMovePageUp, CursorMovePageDown, ParentCursorMoveUp(usize), ParentCursorMoveDown(usize), // ChildCursorMoveUp(usize), // ChildCursorMoveDown(usize), DeleteFiles, NewDirectory(path::PathBuf), OpenFile, OpenFileWith(Option), ParentDirectory, Quit, ForceQuit, ReloadDirList, RenameFile(path::PathBuf), RenameFileAppend, RenameFilePrepend, TouchFile(String), SearchGlob(String), SearchString(String), SearchSkim, SearchNext, SearchPrev, SelectFiles(String, SelectOption), SetMode, SubProcess(Vec, bool), ShowWorkers, ToggleHiddenFiles, Sort(SortType), SortReverse, NewTab, CloseTab, TabSwitch(i32), } impl KeyCommand { pub const fn command(&self) -> &'static str { match self { Self::BulkRename => "bulk_rename", Self::ChangeDirectory(_) => "cd", Self::NewTab => "new_tab", Self::CloseTab => "close_tab", Self::CommandLine(_, _) => ":", Self::CutFiles => "cut_files", Self::CopyFiles => "copy_files", Self::PasteFiles(_) => "paste_files", Self::CopyFileName => "copy_filename", Self::CopyFilePath => "copy_filepath", Self::CopyDirName => "copy_dirname", Self::CursorMoveUp(_) => "cursor_move_up", Self::CursorMoveDown(_) => "cursor_move_down", Self::CursorMoveHome => "cursor_move_home", Self::CursorMoveEnd => "cursor_move_end", Self::CursorMovePageUp => "cursor_move_page_up", Self::CursorMovePageDown => "cursor_move_page_down", Self::ParentCursorMoveUp(_) => "parent_cursor_move_up", Self::ParentCursorMoveDown(_) => "parent_cursor_move_down", Self::DeleteFiles => "delete_files", Self::NewDirectory(_) => "mkdir", Self::OpenFile => "open", Self::OpenFileWith(_) => "open_with", Self::ParentDirectory => "cd ..", Self::Quit => "quit", Self::ForceQuit => "force_quit", Self::ReloadDirList => "reload_dirlist", Self::RenameFile(_) => "rename", Self::TouchFile(_) => "touch", Self::RenameFileAppend => "rename_append", Self::RenameFilePrepend => "rename_prepend", Self::SearchString(_) => "search", Self::SearchGlob(_) => "search_glob", Self::SearchSkim => "search_skim", Self::SearchNext => "search_next", Self::SearchPrev => "search_prev", Self::SelectFiles(_, _) => "select", Self::SetMode => "set_mode", Self::SubProcess(_, false) => "shell", Self::SubProcess(_, true) => "spawn", Self::ShowWorkers => "show_workers", Self::ToggleHiddenFiles => "toggle_hidden", Self::Sort(_) => "sort", Self::SortReverse => "sort reverse", Self::TabSwitch(_) => "tab_switch", } } } impl std::str::FromStr for KeyCommand { type Err = JoshutoError; fn from_str(s: &str) -> Result { if let Some(stripped) = s.strip_prefix(':') { return Ok(Self::CommandLine(stripped.to_owned(), "".to_owned())); } let (command, arg) = match s.find(' ') { Some(i) => (&s[..i], s[i..].trim_start()), None => (s, ""), }; match command { "bulk_rename" => Ok(Self::BulkRename), "cd" => match arg { "" => match HOME_DIR.as_ref() { Some(s) => Ok(Self::ChangeDirectory(s.clone())), None => Err(JoshutoError::new( JoshutoErrorKind::EnvVarNotPresent, format!("{}: Cannot find home directory", command), )), }, ".." => Ok(Self::ParentDirectory), arg => Ok({ let path_accepts_tilde = tilde_with_context(arg, home_dir); Self::ChangeDirectory(path::PathBuf::from(path_accepts_tilde.as_ref())) }), }, "close_tab" => Ok(Self::CloseTab), "copy_files" => Ok(Self::CopyFiles), "copy_filename" => Ok(Self::CopyFileName), "copy_filepath" => Ok(Self::CopyFilePath), "copy_dirname" => Ok(Self::CopyDirName), "cursor_move_home" => Ok(Self::CursorMoveHome), "cursor_move_end" => Ok(Self::CursorMoveEnd), "cursor_move_page_up" => Ok(Self::CursorMovePageUp), "cursor_move_page_down" => Ok(Self::CursorMovePageDown), "cursor_move_down" => match arg { "" => Ok(Self::CursorMoveDown(1)), arg => match arg.trim().parse::() { Ok(s) => Ok(Self::CursorMoveDown(s)), Err(e) => Err(JoshutoError::new( JoshutoErrorKind::ParseError, e.to_string(), )), }, }, "cursor_move_up" => match arg { "" => Ok(Self::CursorMoveUp(1)), arg => match arg.trim().parse::() { Ok(s) => Ok(Self::CursorMoveUp(s)), Err(e) => Err(JoshutoError::new( JoshutoErrorKind::ParseError, e.to_string(), )), }, }, "parent_cursor_move_down" => match arg { "" => Ok(Self::ParentCursorMoveDown(1)), arg => match arg.trim().parse::() { Ok(s) => Ok(Self::ParentCursorMoveDown(s)), Err(e) => Err(JoshutoError::new( JoshutoErrorKind::ParseError, e.to_string(), )), }, }, "parent_cursor_move_up" => match arg { "" => Ok(Self::ParentCursorMoveUp(1)), arg => match arg.trim().parse::() { Ok(s) => Ok(Self::ParentCursorMoveUp(s)), Err(e) => Err(JoshutoError::new( JoshutoErrorKind::ParseError, e.to_string(), )), }, }, "cut_files" => Ok(Self::CutFiles), "delete_files" => Ok(Self::DeleteFiles), "force_quit" => Ok(Self::ForceQuit), "mkdir" => { if arg.is_empty() { Err(JoshutoError::new( JoshutoErrorKind::InvalidParameters, format!("{}: no directory name given", command), )) } else { Ok(Self::NewDirectory(path::PathBuf::from(arg))) } } "new_tab" => Ok(Self::NewTab), "open" => Ok(Self::OpenFile), "open_with" => match arg { "" => Ok(Self::OpenFileWith(None)), arg => match arg.trim().parse::() { Ok(s) => Ok(Self::OpenFileWith(Some(s))), Err(e) => Err(JoshutoError::new( JoshutoErrorKind::ParseError, e.to_string(), )), }, }, "paste_files" => { let mut options = IoWorkerOptions::default(); for arg in arg.split_whitespace() { match arg { "--overwrite=true" => options.overwrite = true, "--skip_exist=true" => options.skip_exist = true, "--overwrite=false" => options.overwrite = false, "--skip_exist=false" => options.skip_exist = false, _ => { return Err(JoshutoError::new( JoshutoErrorKind::UnrecognizedArgument, format!("{}: unknown option '{}'", command, arg), )); } } } Ok(Self::PasteFiles(options)) } "quit" => Ok(Self::Quit), "reload_dirlist" => Ok(Self::ReloadDirList), "rename" => match arg { "" => Err(JoshutoError::new( JoshutoErrorKind::InvalidParameters, format!("{}: Expected 1, got 0", command), )), arg => { let path: path::PathBuf = path::PathBuf::from(arg); Ok(Self::RenameFile(path)) } }, "touch" => Ok(Self::TouchFile(arg.to_string())), "rename_append" => Ok(Self::RenameFileAppend), "rename_prepend" => Ok(Self::RenameFilePrepend), "search" => match arg { "" => Err(JoshutoError::new( JoshutoErrorKind::InvalidParameters, format!("{}: Expected 1, got 0", command), )), arg => Ok(Self::SearchString(arg.to_string())), }, "search_glob" => match arg { "" => Err(JoshutoError::new( JoshutoErrorKind::InvalidParameters, format!("{}: Expected 1, got 0", command), )), arg => Ok(Self::SearchGlob(arg.to_string())), }, "search_skim" => Ok(Self::SearchSkim), "search_next" => Ok(Self::SearchNext), "search_prev" => Ok(Self::SearchPrev), "select" => { let mut options = SelectOption::default(); let mut pattern = ""; match shell_words::split(arg) { Ok(args) => { for arg in args.iter() { match arg.as_str() { "--toggle=true" => options.toggle = true, "--all=true" => options.all = true, "--toggle=false" => options.toggle = false, "--all=false" => options.all = false, "--deselect=true" => options.reverse = true, "--deselect=false" => options.reverse = false, s => pattern = s, } } Ok(Self::SelectFiles(pattern.to_string(), options)) } Err(e) => Err(JoshutoError::new( JoshutoErrorKind::InvalidParameters, format!("{}: {}", arg, e), )), } } "set_mode" => Ok(Self::SetMode), "shell" | "spawn" => match shell_words::split(arg) { Ok(s) if !s.is_empty() => Ok(Self::SubProcess(s, command == "spawn")), Ok(_) => Err(JoshutoError::new( JoshutoErrorKind::InvalidParameters, format!("{}: No commands given", command), )), Err(e) => Err(JoshutoError::new( JoshutoErrorKind::InvalidParameters, format!("{}: {}", arg, e), )), }, "show_workers" => Ok(Self::ShowWorkers), "sort" => match arg { "reverse" => Ok(Self::SortReverse), arg => match SortType::parse(arg) { Some(s) => Ok(Self::Sort(s)), None => Err(JoshutoError::new( JoshutoErrorKind::InvalidParameters, format!("{}: Unknown option '{}'", command, arg), )), }, }, "tab_switch" => match arg.parse::() { Ok(s) => Ok(Self::TabSwitch(s)), Err(e) => Err(JoshutoError::new( JoshutoErrorKind::InvalidParameters, format!("{}: {}", command, e.to_string()), )), }, "toggle_hidden" => Ok(Self::ToggleHiddenFiles), inp => Err(JoshutoError::new( JoshutoErrorKind::UnrecognizedCommand, format!("Unrecognized command '{}'", inp), )), } } } impl AppExecute for KeyCommand { fn execute(&self, context: &mut AppContext, backend: &mut TuiBackend) -> JoshutoResult<()> { match &*self { Self::BulkRename => bulk_rename::bulk_rename(context, backend), Self::ChangeDirectory(p) => { change_directory::change_directory(context, p.as_path())?; LoadChild::load_child(context)?; Ok(()) } Self::NewTab => tab_ops::new_tab(context), Self::CloseTab => tab_ops::close_tab(context), Self::CommandLine(p, s) => { command_line::readline(context, backend, p.as_str(), s.as_str()) } Self::CutFiles => file_ops::cut(context), Self::CopyFiles => file_ops::copy(context), Self::PasteFiles(options) => file_ops::paste(context, options.clone()), Self::CopyFileName => file_ops::copy_filename(context), Self::CopyFilePath => file_ops::copy_filepath(context), Self::CopyDirName => file_ops::copy_dirname(context), Self::CursorMoveUp(u) => cursor_move::up(context, *u), Self::CursorMoveDown(u) => cursor_move::down(context, *u), Self::CursorMoveHome => cursor_move::home(context), Self::CursorMoveEnd => cursor_move::end(context), Self::CursorMovePageUp => cursor_move::page_up(context, backend), Self::CursorMovePageDown => cursor_move::page_down(context, backend), Self::ParentCursorMoveUp(u) => parent_cursor_move::parent_up(context, *u), Self::ParentCursorMoveDown(u) => parent_cursor_move::parent_down(context, *u), Self::DeleteFiles => { delete_files::delete_selected_files(context, backend)?; Ok(()) } Self::NewDirectory(p) => new_directory::new_directory(context, p.as_path()), Self::OpenFile => open_file::open(context, backend), Self::OpenFileWith(None) => open_file::open_with_interactive(context, backend), Self::OpenFileWith(Some(i)) => open_file::open_with_index(context, backend, *i), Self::ParentDirectory => parent_directory::parent_directory(context), Self::Quit => quit::quit(context), Self::ForceQuit => quit::force_quit(context), Self::ReloadDirList => reload::reload_dirlist(context), Self::RenameFile(p) => rename_file::rename_file(context, p.as_path()), Self::RenameFileAppend => rename_file::rename_file_append(context, backend), Self::RenameFilePrepend => rename_file::rename_file_prepend(context, backend), Self::TouchFile(arg) => touch_file::touch_file(context, arg.as_str()), Self::SearchGlob(pattern) => search_glob::search_glob(context, pattern.as_str()), Self::SearchString(pattern) => search_string::search_string(context, pattern.as_str()), Self::SearchSkim => search_skim::search_skim(context, backend), Self::SearchNext => search::search_next(context), Self::SearchPrev => search::search_prev(context), Self::SelectFiles(pattern, options) => { selection::select_files(context, pattern.as_str(), &options) } Self::SetMode => set_mode::set_mode(context, backend), Self::SubProcess(v, spawn) => { sub_process::sub_process(context, backend, v.as_slice(), *spawn) } Self::ShowWorkers => show_workers::show_workers(context, backend), Self::ToggleHiddenFiles => show_hidden::toggle_hidden(context), Self::Sort(t) => sort::set_sort(context, *t), Self::SortReverse => sort::toggle_reverse(context), Self::TabSwitch(i) => { tab_ops::tab_switch(*i, context)?; Ok(()) } } } } impl std::fmt::Display for KeyCommand { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match &*self { Self::ChangeDirectory(p) => write!(f, "{} {:?}", self.command(), p), Self::CommandLine(s, p) => write!(f, "{} {} {}", self.command(), s, p), Self::CursorMoveUp(i) => write!(f, "{} {}", self.command(), i), Self::CursorMoveDown(i) => write!(f, "{} {}", self.command(), i), Self::NewDirectory(d) => write!(f, "{} {:?}", self.command(), d), Self::PasteFiles(options) => write!(f, "{} {}", self.command(), options), Self::RenameFile(name) => write!(f, "{} {:?}", self.command(), name), Self::SearchGlob(s) => write!(f, "{} {}", self.command(), s), Self::SearchString(s) => write!(f, "{} {}", self.command(), s), Self::SelectFiles(pattern, options) => { write!(f, "{} {} {}", self.command(), pattern, options) } Self::SubProcess(c, _) => write!(f, "{} {:?}", self.command(), c), Self::Sort(t) => write!(f, "{} {}", self.command(), t), Self::TabSwitch(i) => write!(f, "{} {}", self.command(), i), _ => write!(f, "{}", self.command()), } } } impl AppCommand for KeyCommand {}