use std::path;
use dirs_next::home_dir;
use shellexpand::tilde_with_context;
use crate::commands::quit::QuitAction;
use crate::config::option::{LineNumberStyle, SelectOption, SortType};
use crate::error::{JoshutoError, JoshutoErrorKind};
use crate::io::IoWorkerOptions;
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<Self, Self::Err> {
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, ""),
};
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_DELETE_FILES, Self::DeleteFiles);
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(s.clone())),
None => Err(JoshutoError::new(
JoshutoErrorKind::EnvVarNotPresent,
format!("{}: Cannot find home directory", command),
)),
},
".." => Ok(Self::ParentDirectory),
"-" => Ok(Self::PreviousDirectory),
arg => Ok({
let path_accepts_tilde = tilde_with_context(arg, home_dir);
Self::ChangeDirectory(path::PathBuf::from(path_accepts_tilde.as_ref()))
}),
}
} else if command == CMD_CURSOR_MOVE_DOWN {
match arg {
"" => Ok(Self::CursorMoveDown(1)),
arg => match arg.trim().parse::<usize>() {
Ok(s) => Ok(Self::CursorMoveDown(s)),
Err(e) => Err(JoshutoError::new(
JoshutoErrorKind::ParseError,
e.to_string(),
)),
},
}
} else if command == CMD_CURSOR_MOVE_PAGEUP {
let p = arg.trim().parse::<f64>().unwrap_or(1.);
Ok(Self::CursorMovePageUp(p))
} else if command == CMD_CURSOR_MOVE_PAGEDOWN {
let p = arg.trim().parse::<f64>().unwrap_or(1.);
Ok(Self::CursorMovePageDown(p))
} else if command == CMD_CURSOR_MOVE_UP {
match arg {
"" => Ok(Self::CursorMoveUp(1)),
arg => match arg.trim().parse::<usize>() {
Ok(s) => Ok(Self::CursorMoveUp(s)),
Err(e) => Err(JoshutoError::new(
JoshutoErrorKind::ParseError,
e.to_string(),
)),
},
}
} else if command == CMD_PARENT_CURSOR_MOVE_DOWN {
match arg {
"" => Ok(Self::ParentCursorMoveDown(1)),
arg => match arg.trim().parse::<usize>() {
Ok(s) => Ok(Self::ParentCursorMoveDown(s)),
Err(e) => Err(JoshutoError::new(
JoshutoErrorKind::ParseError,
e.to_string(),
)),
},
}
} else if command == CMD_PARENT_CURSOR_MOVE_UP {
match arg {
"" => Ok(Self::ParentCursorMoveUp(1)),
arg => match arg.trim().parse::<usize>() {
Ok(s) => Ok(Self::ParentCursorMoveUp(s)),
Err(e) => Err(JoshutoError::new(
JoshutoErrorKind::ParseError,
e.to_string(),
)),
},
}
} else if command == CMD_PREVIEW_CURSOR_MOVE_DOWN {
match arg {
"" => Ok(Self::PreviewCursorMoveDown(1)),
arg => match arg.trim().parse::<usize>() {
Ok(s) => Ok(Self::PreviewCursorMoveDown(s)),
Err(e) => Err(JoshutoError::new(
JoshutoErrorKind::ParseError,
e.to_string(),
)),
},
}
} else if command == CMD_PREVIEW_CURSOR_MOVE_UP {
match arg {
"" => Ok(Self::PreviewCursorMoveUp(1)),
arg => match arg.trim().parse::<usize>() {
Ok(s) => Ok(Self::PreviewCursorMoveUp(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 {
Ok(Self::NewDirectory(path::PathBuf::from(arg)))
}
} else if command == CMD_OPEN_FILE_WITH {
match arg {
"" => Ok(Self::OpenFileWith(None)),
arg => match arg.trim().parse::<usize>() {
Ok(s) => Ok(Self::OpenFileWith(Some(s))),
Err(e) => Err(JoshutoError::new(
JoshutoErrorKind::ParseError,
e.to_string(),
)),
},
}
} else if command == CMD_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))
} 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(path))
}
}
} else if command == CMD_SEARCH_STRING {
match arg {
"" => Err(JoshutoError::new(
JoshutoErrorKind::InvalidParameters,
format!("{}: Expected 1, got 0", command),
)),
arg => Ok(Self::SearchString(arg.to_string())),
}
} else if command == CMD_SEARCH_INCREMENTAL {
Ok(Self::SearchIncremental(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(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.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(s, 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_TAB_SWITCH {
match arg.parse::<i32>() {
Ok(s) => Ok(Self::TabSwitch(s)),
Err(e) => Err(JoshutoError::new(
JoshutoErrorKind::InvalidParameters,
format!("{}: {}", command, e),
)),
}
} else if command == CMD_TAB_SWITCH_INDEX {
match arg.parse::<u32>() {
Ok(s) => Ok(Self::TabSwitchIndex(s)),
Err(e) => Err(JoshutoError::new(
JoshutoErrorKind::InvalidParameters,
format!("{}: {}", command, e),
)),
}
} else if command == CMD_TOUCH_FILE {
Ok(Self::TouchFile(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::<usize>() {
Ok(i) => Ok(Self::Flat(i + 1)),
Err(e) => Err(JoshutoError::new(
JoshutoErrorKind::InvalidParameters,
format!("{}: {}", command, e),
)),
}
} else {
Err(JoshutoError::new(
JoshutoErrorKind::UnrecognizedCommand,
format!("Unrecognized command '{}'", command),
))
}
}
}