use std::path; use dirs_next::home_dir; use shellexpand::tilde_with_context; use crate::commands::quit::QuitAction; use crate::config::option::{LineMode, LineNumberStyle, SelectOption, SortType}; use crate::error::{JoshutoError, JoshutoErrorKind}; use crate::io::FileOperationOptions; use crate::HOME_DIR; use super::constants::*; use super::Command; macro_rules! simple_command_conversion_case { ($command: ident, $command_match: ident, $enum_name: expr) => { if $command == $command_match { return Ok($enum_name); } }; } impl std::str::FromStr for Command { type Err = JoshutoError; fn from_str(s: &str) -> Result { if let Some(stripped) = s.strip_prefix(':') { return Ok(Self::CommandLine { prefix: stripped.to_owned(), suffix: "".to_owned(), }); } let (command, arg) = match s.find(' ') { Some(i) => (&s[..i], s[i..].trim_start()), None => (s, ""), }; simple_command_conversion_case!(command, CMD_ESCAPE, Self::Escape); simple_command_conversion_case!(command, CMD_TOGGLE_VISUAL, Self::ToggleVisualMode); simple_command_conversion_case!(command, CMD_NEW_TAB, Self::NewTab); simple_command_conversion_case!(command, CMD_CLOSE_TAB, Self::CloseTab); simple_command_conversion_case!(command, CMD_HELP, Self::Help); simple_command_conversion_case!(command, CMD_CURSOR_MOVE_HOME, Self::CursorMoveHome); simple_command_conversion_case!(command, CMD_CURSOR_MOVE_END, Self::CursorMoveEnd); simple_command_conversion_case!( command, CMD_CURSOR_MOVE_PAGEHOME, Self::CursorMovePageHome ); simple_command_conversion_case!( command, CMD_CURSOR_MOVE_PAGEMIDDLE, Self::CursorMovePageMiddle ); simple_command_conversion_case!(command, CMD_CURSOR_MOVE_PAGEEND, Self::CursorMovePageEnd); simple_command_conversion_case!(command, CMD_CUT_FILES, Self::CutFiles); simple_command_conversion_case!(command, CMD_COPY_FILES, Self::CopyFiles); simple_command_conversion_case!(command, CMD_COPY_FILENAME, Self::CopyFileName); simple_command_conversion_case!( command, CMD_COPY_FILENAME_WITHOUT_EXTENSION, Self::CopyFileNameWithoutExtension ); simple_command_conversion_case!(command, CMD_COPY_FILEPATH, Self::CopyFilePath); simple_command_conversion_case!(command, CMD_COPY_DIRECTORY_PATH, Self::CopyDirPath); simple_command_conversion_case!(command, CMD_OPEN_FILE, Self::OpenFile); simple_command_conversion_case!(command, CMD_RELOAD_DIRECTORY_LIST, Self::ReloadDirList); simple_command_conversion_case!(command, CMD_RENAME_FILE_APPEND, Self::RenameFileAppend); simple_command_conversion_case!(command, CMD_RENAME_FILE_PREPEND, Self::RenameFilePrepend); simple_command_conversion_case!(command, CMD_SEARCH_NEXT, Self::SearchNext); simple_command_conversion_case!(command, CMD_SEARCH_PREV, Self::SearchPrev); simple_command_conversion_case!(command, CMD_SHOW_TASKS, Self::ShowTasks); simple_command_conversion_case!(command, CMD_SET_MODE, Self::SetMode); simple_command_conversion_case!(command, CMD_TOGGLE_HIDDEN, Self::ToggleHiddenFiles); simple_command_conversion_case!(command, CMD_BULK_RENAME, Self::BulkRename); simple_command_conversion_case!(command, CMD_SEARCH_FZF, Self::SearchFzf); simple_command_conversion_case!(command, CMD_SUBDIR_FZF, Self::SubdirFzf); simple_command_conversion_case!(command, CMD_ZOXIDE, Self::Zoxide(arg.to_string())); simple_command_conversion_case!(command, CMD_ZOXIDE_INTERACTIVE, Self::ZoxideInteractive); if command == CMD_QUIT { match arg { "--force" => Ok(Self::Quit(QuitAction::Force)), "--output-current-directory" => Ok(Self::Quit(QuitAction::OutputCurrentDirectory)), "--output-selected-files" => Ok(Self::Quit(QuitAction::OutputSelectedFiles)), _ => Ok(Self::Quit(QuitAction::Noop)), } } else if command == CMD_CHANGE_DIRECTORY { match arg { "" => match HOME_DIR.as_ref() { Some(s) => Ok(Self::ChangeDirectory { path: s.clone() }), None => Err(JoshutoError::new( JoshutoErrorKind::EnvVarNotPresent, format!("{}: Cannot find home directory", command), )), }, ".." => Ok(Self::ParentDirectory), "-" => Ok(Self::PreviousDirectory), arg => { let path_accepts_tilde = tilde_with_context(arg, home_dir); let path = path::PathBuf::from(path_accepts_tilde.as_ref()); Ok(Self::ChangeDirectory { path }) } } } else if command == CMD_CURSOR_MOVE_DOWN { match arg { "" => Ok(Self::CursorMoveDown { offset: 1 }), arg => match arg.trim().parse::() { Ok(s) => Ok(Self::CursorMoveDown { offset: s }), Err(e) => Err(JoshutoError::new( JoshutoErrorKind::ParseError, e.to_string(), )), }, } } else if command == CMD_CURSOR_MOVE_PAGEUP { let p = arg.trim().parse::().unwrap_or(1.); Ok(Self::CursorMovePageUp(p)) } else if command == CMD_CURSOR_MOVE_PAGEDOWN { let p = arg.trim().parse::().unwrap_or(1.); Ok(Self::CursorMovePageDown(p)) } else if command == CMD_CURSOR_MOVE_UP { match arg { "" => Ok(Self::CursorMoveUp { offset: 1 }), arg => match arg.trim().parse::() { Ok(s) => Ok(Self::CursorMoveUp { offset: s }), Err(e) => Err(JoshutoError::new( JoshutoErrorKind::ParseError, e.to_string(), )), }, } } else if command == CMD_PARENT_CURSOR_MOVE_DOWN { match arg { "" => Ok(Self::ParentCursorMoveDown { offset: 1 }), arg => match arg.trim().parse::() { Ok(s) => Ok(Self::ParentCursorMoveDown { offset: s }), Err(e) => Err(JoshutoError::new( JoshutoErrorKind::ParseError, e.to_string(), )), }, } } else if command == CMD_PARENT_CURSOR_MOVE_UP { match arg { "" => Ok(Self::ParentCursorMoveUp { offset: 1 }), arg => match arg.trim().parse::() { Ok(s) => Ok(Self::ParentCursorMoveUp { offset: s }), Err(e) => Err(JoshutoError::new( JoshutoErrorKind::ParseError, e.to_string(), )), }, } } else if command == CMD_PREVIEW_CURSOR_MOVE_DOWN { match arg { "" => Ok(Self::PreviewCursorMoveDown { offset: 1 }), arg => match arg.trim().parse::() { Ok(s) => Ok(Self::PreviewCursorMoveDown { offset: s }), Err(e) => Err(JoshutoError::new( JoshutoErrorKind::ParseError, e.to_string(), )), }, } } else if command == CMD_PREVIEW_CURSOR_MOVE_UP { match arg { "" => Ok(Self::PreviewCursorMoveUp { offset: 1 }), arg => match arg.trim().parse::() { Ok(s) => Ok(Self::PreviewCursorMoveUp { offset: s }), Err(e) => Err(JoshutoError::new( JoshutoErrorKind::ParseError, e.to_string(), )), }, } } else if command == CMD_NEW_DIRECTORY { if arg.is_empty() { Err(JoshutoError::new( JoshutoErrorKind::InvalidParameters, format!("{}: no directory name given", command), )) } else { let path = path::PathBuf::from(arg); Ok(Self::NewDirectory { path }) } } else if command == CMD_OPEN_FILE_WITH { match arg { "" => Ok(Self::OpenFileWith { index: None }), arg => match arg.trim().parse::() { Ok(s) => Ok(Self::OpenFileWith { index: Some(s) }), Err(e) => Err(JoshutoError::new( JoshutoErrorKind::ParseError, e.to_string(), )), }, } } else if command == CMD_SYMLINK_FILES { let mut relative = false; for arg in arg.split_whitespace() { match arg { "--relative=true" => relative = true, "--relative=false" => relative = false, _ => { return Err(JoshutoError::new( JoshutoErrorKind::UnrecognizedArgument, format!("{}: unknown option '{}'", command, arg), )); } } } Ok(Self::SymlinkFiles { relative }) } else if command == CMD_PASTE_FILES { let mut options = FileOperationOptions::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 }) } else if command == CMD_DELETE_FILES { match arg { "--foreground=true" => Ok(Self::DeleteFiles { background: true }), "--foreground=false" => Ok(Self::DeleteFiles { background: false }), _ => Err(JoshutoError::new( JoshutoErrorKind::UnrecognizedArgument, format!("{}: unknown option '{}'", command, arg), )), } } else if command == CMD_RENAME_FILE { 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 { new_name: path }) } } } else if command == CMD_SEARCH_STRING { match arg { "" => Err(JoshutoError::new( JoshutoErrorKind::InvalidParameters, format!("{}: Expected 1, got 0", command), )), arg => Ok(Self::SearchString { pattern: arg.to_string(), }), } } else if command == CMD_SEARCH_INCREMENTAL { Ok(Self::SearchIncremental { pattern: arg.to_string(), }) } else if command == CMD_SEARCH_GLOB { match arg { "" => Err(JoshutoError::new( JoshutoErrorKind::InvalidParameters, format!("{}: Expected 1, got 0", command), )), arg => Ok(Self::SearchGlob { pattern: arg.to_string(), }), } } else if command == CMD_SELECT_FILES { 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: pattern.to_string(), options, }) } Err(e) => Err(JoshutoError::new( JoshutoErrorKind::InvalidParameters, format!("{}: {}", arg, e), )), } } else if command == CMD_SUBPROCESS_FOREGROUND || command == CMD_SUBPROCESS_BACKGROUND { match shell_words::split(arg) { Ok(s) if !s.is_empty() => Ok(Self::SubProcess { words: s, spawn: command == "spawn", }), Ok(_) => Err(JoshutoError::new( JoshutoErrorKind::InvalidParameters, format!("{}: No commands given", command), )), Err(e) => Err(JoshutoError::new( JoshutoErrorKind::InvalidParameters, format!("{}: {}", arg, e), )), } } else if command == CMD_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), )), }, } } else if command == CMD_SET_LINEMODE { Ok(Self::SetLineMode(LineMode::from_string(arg)?)) } else if command == CMD_TAB_SWITCH { match arg.parse::() { Ok(s) => Ok(Self::TabSwitch { offset: s }), Err(e) => Err(JoshutoError::new( JoshutoErrorKind::InvalidParameters, format!("{}: {}", command, e), )), } } else if command == CMD_TAB_SWITCH_INDEX { match arg.parse::() { Ok(s) => Ok(Self::TabSwitchIndex { index: s }), Err(e) => Err(JoshutoError::new( JoshutoErrorKind::InvalidParameters, format!("{}: {}", command, e), )), } } else if command == CMD_TOUCH_FILE { Ok(Self::TouchFile { file_name: arg.to_string(), }) } else if command == CMD_SWITCH_LINE_NUMBERS { let policy = match arg { "absolute" | "1" => LineNumberStyle::Absolute, "relative" | "2" => LineNumberStyle::Relative, _ => LineNumberStyle::None, }; Ok(Self::SwitchLineNums(policy)) } else if command == CMD_FLAT { match arg.parse::() { Ok(i) => Ok(Self::Flat { depth: i + 1 }), Err(e) => Err(JoshutoError::new( JoshutoErrorKind::InvalidParameters, format!("{}: {}", command, e), )), } } else if command == CMD_NUMBERED_COMMAND { let c = arg.chars().next(); match c { Some(c) => Ok(Self::NumberedCommand { initial: c }), None => Err(JoshutoError::new( JoshutoErrorKind::InvalidParameters, format!("{}: no starting character given", command), )), } } else if command == CMD_FILTER { Ok(Self::Filter { pattern: arg.to_string(), }) } else { Err(JoshutoError::new( JoshutoErrorKind::UnrecognizedCommand, format!("Unrecognized command '{}'", command), )) } } }