summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJiayi Zhao <jeff.no.zhao@gmail.com>2020-05-13 09:31:56 -0400
committerJiayi Zhao <jeff.no.zhao@gmail.com>2020-05-13 09:31:56 -0400
commit95bc120687723a8c9e525b7448c5fd71235a329e (patch)
tree84a1e9fe847b366fa1beff8afaa7cba8865bac1c /src
parentddb0669bbf0b5bb9e039094e58e69a5a81f13494 (diff)
rework command parsing and add shell command
- This change will break compatibility with previous keymap.toml configs - 'shell' command allows users to run a shell command inside joshuto
Diffstat (limited to 'src')
-rw-r--r--src/commands/command_line.rs13
-rw-r--r--src/commands/mod.rs210
-rw-r--r--src/commands/new_directory.rs10
-rw-r--r--src/commands/shell.rs44
-rw-r--r--src/commands/sort.rs33
-rw-r--r--src/config/keymap.rs4
6 files changed, 154 insertions, 160 deletions
diff --git a/src/commands/command_line.rs b/src/commands/command_line.rs
index 0714ed2..577d988 100644
--- a/src/commands/command_line.rs
+++ b/src/commands/command_line.rs
@@ -31,17 +31,8 @@ impl CommandLine {
if let Some(s) = user_input {
let trimmed = s.trim_start();
- match trimmed.find(' ') {
- Some(ind) => {
- let (cmd, xs) = trimmed.split_at(ind);
- let xs = xs.trim_start();
- let args: Vec<String> = vec![String::from(xs)];
- let command = commands::from_args(cmd.to_string(), args)?;
- command.execute(context, backend)
- }
- None => commands::from_args(String::from(trimmed), Vec::new())?
- .execute(context, backend),
- }
+ let command = commands::parse_command(trimmed)?;
+ command.execute(context, backend)
} else {
Ok(())
}
diff --git a/src/commands/mod.rs b/src/commands/mod.rs
index 953da26..f276d8d 100644
--- a/src/commands/mod.rs
+++ b/src/commands/mod.rs
@@ -13,6 +13,7 @@ mod rename_file;
mod search;
mod selection;
mod set_mode;
+mod shell;
mod show_hidden;
mod sort;
mod tab_operations;
@@ -37,8 +38,9 @@ pub use self::rename_file::{RenameFile, RenameFileAppend, RenameFilePrepend};
pub use self::search::{Search, SearchNext, SearchPrev};
pub use self::selection::SelectFiles;
pub use self::set_mode::SetMode;
+pub use self::shell::ShellCommand;
pub use self::show_hidden::ToggleHiddenFiles;
-pub use self::sort::Sort;
+pub use self::sort::{Sort,SortReverse};
pub use self::tab_operations::{CloseTab, NewTab};
pub use self::tab_switch::TabSwitch;
@@ -74,87 +76,61 @@ pub trait JoshutoRunnable {
pub trait JoshutoCommand: JoshutoRunnable + std::fmt::Display + std::fmt::Debug {}
-pub fn from_args(command: String, args: Vec<String>) -> JoshutoResult<Box<dyn JoshutoCommand>> {
- match command.as_str() {
+pub fn parse_command(s: &str) -> JoshutoResult<Box<dyn JoshutoCommand>> {
+ let (command, arg) = match s.find(' ') {
+ Some(i) => (&s[..i], s[i+1..].trim_start()),
+ None => (s, ""),
+ };
+
+ match command {
"bulk_rename" => Ok(Box::new(self::BulkRename::new())),
- "cd" => match args.len() {
- 0 => match HOME_DIR.as_ref() {
+ "cd" => match arg {
+ "" => match HOME_DIR.as_ref() {
Some(s) => Ok(Box::new(self::ChangeDirectory::new(s.clone()))),
None => Err(JoshutoError::new(
JoshutoErrorKind::EnvVarNotPresent,
format!("{}: Cannot find home directory", command),
)),
},
- 1 => match args[0].as_str() {
- ".." => Ok(Box::new(self::ParentDirectory::new())),
- arg => Ok(Box::new(self::ChangeDirectory::new(PathBuf::from(arg)))),
- },
- i => Err(JoshutoError::new(
- JoshutoErrorKind::IOInvalidData,
- format!("{}: Expected 1 argument, got {}", command, i),
- )),
- },
+ ".." => Ok(Box::new(self::ParentDirectory::new())),
+ arg => Ok(Box::new(self::ChangeDirectory::new(PathBuf::from(arg)))),
+ }
"close_tab" => Ok(Box::new(self::CloseTab::new())),
"copy_files" => Ok(Box::new(self::CopyFiles::new())),
- "console" => match args.len() {
- 0 => Ok(Box::new(self::CommandLine::new(
- "".to_string(),
- "".to_string(),
- ))),
- 1 => Ok(Box::new(self::CommandLine::new(
- args[0].clone(),
- "".to_string(),
- ))),
- i => Err(JoshutoError::new(
- JoshutoErrorKind::IOInvalidData,
- format!("{}: Expected 0 or 2 arguments, got {}", command, i),
- )),
- },
- "cursor_move_down" => match args.len() {
- 0 => Ok(Box::new(self::CursorMoveDown::new(1))),
- 1 => match args[0].parse::<usize>() {
+ "console" => Ok(Box::new(self::CommandLine::new(arg.to_owned(), "".to_owned()))),
+ "cursor_move_home" => Ok(Box::new(self::CursorMoveHome::new())),
+ "cursor_move_end" => Ok(Box::new(self::CursorMoveEnd::new())),
+ "cursor_move_page_up" => Ok(Box::new(self::CursorMovePageUp::new())),
+ "cursor_move_page_down" => Ok(Box::new(self::CursorMovePageDown::new())),
+ "cursor_move_down" => match arg {
+ "" => Ok(Box::new(self::CursorMoveDown::new(1))),
+ arg => match arg.parse::<usize>() {
Ok(s) => Ok(Box::new(self::CursorMoveDown::new(s))),
Err(e) => Err(JoshutoError::new(
JoshutoErrorKind::ParseError,
e.to_string(),
)),
},
- i => Err(JoshutoError::new(
- JoshutoErrorKind::IOInvalidData,
- format!("{}: Expected 0 or 1 arguments, got {}", command, i),
- )),
- },
- "cursor_move_up" => match args.len() {
- 0 => Ok(Box::new(self::CursorMoveUp::new(1))),
- 1 => match args[0].parse::<usize>() {
+ }
+ "cursor_move_up" => match arg {
+ "" => Ok(Box::new(self::CursorMoveUp::new(1))),
+ arg => match arg.parse::<usize>() {
Ok(s) => Ok(Box::new(self::CursorMoveUp::new(s))),
Err(e) => Err(JoshutoError::new(
JoshutoErrorKind::ParseError,
- format!("{}: {}", command, e.to_string()),
+ e.to_string(),
)),
},
- i => Err(JoshutoError::new(
- JoshutoErrorKind::IOInvalidData,
- format!("{}: Expected 0 or 1 arguments, got {}", command, i),
- )),
- },
- "cursor_move_home" => Ok(Box::new(self::CursorMoveHome::new())),
- "cursor_move_end" => Ok(Box::new(self::CursorMoveEnd::new())),
- "cursor_move_page_up" => Ok(Box::new(self::CursorMovePageUp::new())),
- "cursor_move_page_down" => Ok(Box::new(self::CursorMovePageDown::new())),
+ }
"cut_files" => Ok(Box::new(self::CutFiles::new())),
"delete_files" => Ok(Box::new(self::DeleteFiles::new())),
"force_quit" => Ok(Box::new(self::ForceQuit::new())),
- "mkdir" => {
- if args.is_empty() {
- Err(JoshutoError::new(
- JoshutoErrorKind::IOInvalidData,
- format!("{}: missing additional parameter", command),
- ))
- } else {
- let paths: Vec<PathBuf> = args.iter().map(PathBuf::from).collect();
- Ok(Box::new(self::NewDirectory::new(paths)))
- }
+ "mkdir" => match arg {
+ "" => Err(JoshutoError::new(
+ JoshutoErrorKind::IOInvalidData,
+ format!("{}: missing additional parameter", command),
+ )),
+ arg => Ok(Box::new(self::NewDirectory::new(PathBuf::from(arg)))),
}
"new_tab" => Ok(Box::new(self::NewTab::new())),
@@ -162,8 +138,8 @@ pub fn from_args(command: String, args: Vec<String>) -> JoshutoResult<Box<dyn Jo
"open_file_with" => Ok(Box::new(self::OpenFileWith::new())),
"paste_files" => {
let mut options = Options::default();
- for arg in args {
- match arg.as_str() {
+ for arg in arg.split_whitespace() {
+ match arg {
"--overwrite" => options.overwrite = true,
"--skip_exist" => options.skip_exist = true,
_ => {
@@ -178,32 +154,32 @@ pub fn from_args(command: String, args: Vec<String>) -> JoshutoResult<Box<dyn Jo
}
"quit" => Ok(Box::new(self::Quit::new())),
"reload_dir_list" => Ok(Box::new(self::ReloadDirList::new())),
- "rename" => match args.len() {
- 1 => {
- let path: PathBuf = PathBuf::from(args[0].as_str());
- Ok(Box::new(self::RenameFile::new(path)))
- }
- i => Err(JoshutoError::new(
+ "rename" => match arg {
+ "" => Err(JoshutoError::new(
JoshutoErrorKind::IOInvalidData,
- format!("rename_file: Expected 1, got {}", i),
+ format!("rename_file: Expected 1, got 0"),
)),
+ arg => {
+ let path: PathBuf = PathBuf::from(arg);
+ Ok(Box::new(self::RenameFile::new(path)))
+ }
},
"rename_append" => Ok(Box::new(self::RenameFileAppend::new())),
"rename_prepend" => Ok(Box::new(self::RenameFilePrepend::new())),
- "search" => match args.len() {
- 1 => Ok(Box::new(self::Search::new(args[0].as_str()))),
- i => Err(JoshutoError::new(
+ "search" => match arg {
+ "" => Err(JoshutoError::new(
JoshutoErrorKind::IOInvalidData,
- format!("{}: Expected 1, got {}", command, i),
+ format!("{}: Expected 1, got 0", command),
)),
+ arg => Ok(Box::new(self::Search::new(arg))),
},
"search_next" => Ok(Box::new(self::SearchNext::new())),
"search_prev" => Ok(Box::new(self::SearchPrev::new())),
"select_files" => {
let mut toggle = false;
let mut all = false;
- for arg in args {
- match arg.as_str() {
+ for arg in arg.split_whitespace() {
+ match arg {
"--toggle" => toggle = true,
"--all" => all = true,
_ => {
@@ -217,38 +193,28 @@ pub fn from_args(command: String, args: Vec<String>) -> JoshutoResult<Box<dyn Jo
Ok(Box::new(self::SelectFiles::new(toggle, all)))
}
"set_mode" => Ok(Box::new(self::SetMode::new())),
- "sort" => {
- if args.len() == 1 {
- match args[0].as_str() {
- "lexical" => Ok(Box::new(self::Sort::new(SortType::Lexical))),
- "mtime" => Ok(Box::new(self::Sort::new(SortType::Mtime))),
- "natural" => Ok(Box::new(self::Sort::new(SortType::Natural))),
- a => Err(JoshutoError::new(
- JoshutoErrorKind::IOInvalidData,
- format!("sort: Unknown option {}", a),
- )),
- }
- } else {
- Err(JoshutoError::new(
+ "shell" => Ok(Box::new(self::ShellCommand::new(arg.to_owned()))),
+ "sort" => match arg {
+ "reverse" => Ok(Box::new(self::SortReverse::new())),
+ arg => match SortType::parse(arg) {
+ Some(s) => Ok(Box::new(self::Sort::new(s))),
+ None => Err(JoshutoError::new(
JoshutoErrorKind::IOInvalidData,
- format!("sort: Expected 1, got {}", args.len()),
- ))
- }
- }
- "tab_switch" => {
- if args.len() == 1 {
- match args[0].parse::<i32>() {
- Ok(s) => Ok(Box::new(self::TabSwitch::new(s))),
- Err(e) => Err(JoshutoError::new(
- JoshutoErrorKind::IOInvalidData,
- format!("{}: {}", command, e.to_string()),
- )),
- }
- } else {
- Err(JoshutoError::new(
+ format!("sort: Unknown option {}", arg),
+ )),
+ },
+ },
+ "tab_switch" => match arg {
+ "" => Err(JoshutoError::new(
+ JoshutoErrorKind::IOInvalidData,
+ format!("{}: {}", command, "No option provided"),
+ )),
+ arg => match arg.parse::<i32>() {
+ Ok(s) => Ok(Box::new(self::TabSwitch::new(s))),
+ Err(e) => Err(JoshutoError::new(
JoshutoErrorKind::IOInvalidData,
- format!("{}: {}", command, "No option provided"),
- ))
+ format!("{}: {}", command, e.to_string()),
+ )),
}
}
"toggle_hidden" => Ok(Box::new(self::ToggleHiddenFiles::new())),
@@ -258,39 +224,3 @@ pub fn from_args(command: String, args: Vec<String>) -> JoshutoResult<Box<dyn Jo
)),
}
}
-
-/*
-pub fn split_shell_style(line: &str) -> Vec<&str> {
- let mut args: Vec<&str> = Vec::new();
- let mut char_ind = line.char_indices();
-
- while let Some((i, ch)) = char_ind.next() {
- if ch.is_whitespace() {
- continue;
- }
- if ch == '\'' {
- while let Some((j, ch)) = char_ind.next() {
- if ch == '\'' {
- args.push(&line[i + 1..j]);
- break;
- }
- }
- } else if ch == '"' {
- while let Some((j, ch)) = char_ind.next() {
- if ch == '"' {
- args.push(&line[i + 1..j]);
- break;
- }
- }
- } else {
- while let Some((j, ch)) = char_ind.next() {
- if ch.is_whitespace() {
- args.push(&line[i..j]);
- break;
- }
- }
- }
- }
- args
-}
-*/
diff --git a/src/commands/new_directory.rs b/src/commands/new_directory.rs
index 5152374..f1ce5e4 100644
--- a/src/commands/new_directory.rs
+++ b/src/commands/new_directory.rs
@@ -9,12 +9,12 @@ use crate::util::load_child::LoadChild;
#[derive(Clone, Debug)]
pub struct NewDirectory {
- paths: Vec<path::PathBuf>,
+ path: path::PathBuf,
}
impl NewDirectory {
- pub fn new(paths: Vec<path::PathBuf>) -> Self {
- NewDirectory { paths }
+ pub fn new(path: path::PathBuf) -> Self {
+ NewDirectory { path }
}
pub const fn command() -> &'static str {
"mkdir"
@@ -31,9 +31,7 @@ impl std::fmt::Display for NewDirectory {
impl JoshutoRunnable for NewDirectory {
fn execute(&self, context: &mut JoshutoContext, _: &mut TuiBackend) -> JoshutoResult<()> {
- for path in &self.paths {
- std::fs::create_dir_all(path)?;
- }
+ std::fs::create_dir_all(&self.path)?;
let options = &context.config_t.sort_option;
let curr_path = context.tabs[context.curr_tab_index].curr_path.clone();
diff --git a/src/commands/shell.rs b/src/commands/shell.rs
new file mode 100644
index 0000000..19e901d
--- /dev/null
+++ b/src/commands/shell.rs
@@ -0,0 +1,44 @@
+use std::process;
+
+use crate::commands::{self, JoshutoCommand, JoshutoRunnable};
+use crate::context::JoshutoContext;
+use crate::error::JoshutoResult;
+use crate::ui::widgets::TuiTextField;
+use crate::ui::TuiBackend;
+
+#[derive(Clone, Debug)]
+pub struct ShellCommand {
+ pub command: String,
+}
+
+impl ShellCommand {
+ pub fn new(command: String) -> Self {
+ Self { command }
+ }
+ pub const fn command() -> &'static str {
+ "console"
+ }
+
+ pub fn shell_command(command: &str) -> std::io::Result<()> {
+ let mut command = process::Command::new("sh").arg("-c").arg(command).spawn()?;
+ Ok(())
+ }
+}
+
+impl JoshutoCommand for ShellCommand {}
+
+impl std::fmt::Display for ShellCommand {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{}: sh -c '{}'", Self::command(), self.command)
+ }
+}
+
+impl JoshutoRunnable for ShellCommand {
+ fn execute(&self, context: &mut JoshutoContext, backend: &mut TuiBackend) -> JoshutoResult<()> {
+ backend.terminal_drop();
+ let res = Self::shell_command(self.command.as_str());
+ backend.terminal_restore()?;
+ res?;
+ Ok(())
+ }
+}
diff --git a/src/commands/sort.rs b/src/commands/sort.rs
index 7209cb4..93882f6 100644
--- a/src/commands/sort.rs
+++ b/src/commands/sort.rs
@@ -40,3 +40,36 @@ impl JoshutoRunnable for Sort {
Ok(())
}
}
+
+#[derive(Clone, Debug)]
+pub struct SortReverse;
+
+impl SortReverse {
+ pub const fn new() -> Self {
+ Self {}
+ }
+ pub const fn command() -> &'static str {
+ "sort"
+ }
+}
+
+impl JoshutoCommand for SortReverse {}
+
+impl std::fmt::Display for SortReverse {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{} reverse", Self::command())
+ }
+}
+
+impl JoshutoRunnable for SortReverse {
+ fn execute(&self, context: &mut JoshutoContext, _: &mut TuiBackend) -> JoshutoResult<()> {
+ context.config_t.sort_option.reverse = !context.config_t.sort_option.reverse;
+ for tab in context.tabs.iter_mut() {
+ tab.history.depreciate_all_entries();
+ }
+ ReloadDirList::soft_reload(context.curr_tab_index, context)?;
+ LoadChild::load_child(context)?;
+ Ok(())
+ }
+}
+
diff --git a/src/config/keymap.rs b/src/config/keymap.rs
index 608b12c..328132d 100644
--- a/src/config/keymap.rs
+++ b/src/config/keymap.rs
@@ -223,8 +223,6 @@ impl ConfigStructure for JoshutoCommandMapping {
#[derive(Debug, Deserialize)]
struct JoshutoMapCommand {
pub command: String,
- #[serde(default)]
- pub args: Vec<String>,
pub keys: Vec<String>,
}
@@ -238,7 +236,7 @@ impl Flattenable<JoshutoCommandMapping> for JoshutoRawCommandMapping {
fn flatten(self) -> JoshutoCommandMapping {
let mut keymaps = JoshutoCommandMapping::new();
for m in self.mapcommand {
- match commands::from_args(m.command, m.args) {
+ match commands::parse_command(m.command.as_str()) {
Ok(command) => {
let keycodes: Vec<Key> = m
.keys