summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/commands/custom_search.rs110
-rw-r--r--src/commands/mod.rs1
-rw-r--r--src/commands/sub_process.rs39
-rw-r--r--src/config/clean/app/config.rs8
-rw-r--r--src/config/clean/keymap/config.rs34
-rw-r--r--src/config/raw/app/config.rs8
-rw-r--r--src/config/raw/keymap/config.rs5
-rw-r--r--src/key_command/command.rs3
-rw-r--r--src/key_command/constants.rs2
-rw-r--r--src/key_command/impl_appcommand.rs3
-rw-r--r--src/key_command/impl_appexecute.rs7
-rw-r--r--src/key_command/impl_comment.rs4
-rw-r--r--src/key_command/impl_from_str.rs10
13 files changed, 205 insertions, 29 deletions
diff --git a/src/commands/custom_search.rs b/src/commands/custom_search.rs
new file mode 100644
index 0000000..422143e
--- /dev/null
+++ b/src/commands/custom_search.rs
@@ -0,0 +1,110 @@
+use super::change_directory::change_directory;
+use super::sub_process::current_filenames;
+use crate::commands::cursor_move;
+use crate::context::AppContext;
+use crate::error::{AppError, AppErrorKind, AppResult};
+use crate::ui::AppBackend;
+use shell_words::split;
+use std::process::{Command, Stdio};
+
+pub fn custom_search(
+ context: &mut AppContext,
+ backend: &mut AppBackend,
+ words: &[String],
+ interactive: bool,
+) -> AppResult {
+ let custom_command = context
+ .config_ref()
+ .custom_commands
+ .as_slice()
+ .iter()
+ .find(|x| x.name == words[0])
+ .ok_or(AppError::new(
+ AppErrorKind::InvalidParameters,
+ "No custom command with given name".into(),
+ ))?
+ .command
+ .clone();
+
+ let current_filenames = current_filenames(context);
+
+ let text = custom_command.replace("%s", &current_filenames.join(" "));
+ let text = text.replace(
+ "%text",
+ &words
+ .iter()
+ .skip(1)
+ .cloned()
+ .collect::<Vec<String>>()
+ .join(" "),
+ );
+ let mut command_with_args: Vec<String> = split(&text).map_err(|_| {
+ AppError::new(
+ AppErrorKind::InvalidParameters,
+ "Command cannot be splitted".into(),
+ )
+ })?;
+
+ let mut cmd = Command::new(command_with_args.remove(0));
+ command_with_args.into_iter().for_each(|x| {
+ cmd.arg(x);
+ });
+
+ let cmd_result = if interactive {
+ backend.terminal_drop();
+ let cmd_result = cmd
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .spawn()?
+ .wait_with_output()?;
+ backend.terminal_restore()?;
+ cmd_result
+ } else {
+ cmd.output()?
+ };
+
+ if cmd_result.status.success() {
+ let returned_text = std::str::from_utf8(&cmd_result.stdout)
+ .map_err(|_| {
+ AppError::new(
+ AppErrorKind::ParseError,
+ "Could not get command result as utf8".into(),
+ )
+ })?
+ .trim_end();
+
+ let path = std::path::Path::new(returned_text);
+ change_directory(
+ context,
+ path.parent().ok_or(AppError::new(
+ AppErrorKind::ParseError,
+ "Could not get parent directory".into(),
+ ))?,
+ )?;
+
+ if let Some(current_dir_items) = context.tab_context_ref().curr_tab_ref().curr_list_ref() {
+ let position = current_dir_items
+ .iter()
+ .enumerate()
+ .find(|x| x.1.file_name() == path.file_name().unwrap_or_default())
+ .map(|x| x.0)
+ .unwrap_or_default();
+
+ cursor_move::cursor_move(context, position);
+ }
+
+ Ok(())
+ } else {
+ let returned_text = std::str::from_utf8(&cmd_result.stderr).map_err(|_| {
+ AppError::new(
+ AppErrorKind::ParseError,
+ "Could not get command result as utf8".into(),
+ )
+ })?;
+
+ Err(AppError::new(
+ AppErrorKind::ParseError,
+ format!("Command failed: {}", returned_text),
+ ))
+ }
+}
diff --git a/src/commands/mod.rs b/src/commands/mod.rs
index 8b56f5a..da39d43 100644
--- a/src/commands/mod.rs
+++ b/src/commands/mod.rs
@@ -4,6 +4,7 @@ pub mod case_sensitivity;
pub mod change_directory;
pub mod command_line;
pub mod cursor_move;
+pub mod custom_search;
pub mod delete_files;
pub mod escape;
pub mod file_ops;
diff --git a/src/commands/sub_process.rs b/src/commands/sub_process.rs
index fb1afb8..9f97af1 100644
--- a/src/commands/sub_process.rs
+++ b/src/commands/sub_process.rs
@@ -5,6 +5,27 @@ use std::process::{Command, Stdio};
use super::reload;
+pub fn current_filenames(context: &AppContext) -> Vec<&str> {
+ let mut result = Vec::new();
+ if let Some(curr_list) = context.tab_context_ref().curr_tab_ref().curr_list_ref() {
+ let mut i = 0;
+ curr_list
+ .iter_selected()
+ .map(|e| e.file_name())
+ .for_each(|file_name| {
+ result.push(file_name);
+ i += 1;
+ });
+ if i == 0 {
+ if let Some(entry) = curr_list.curr_entry_ref() {
+ result.push(entry.file_name());
+ }
+ }
+ }
+
+ result
+}
+
fn execute_sub_process(
context: &mut AppContext,
words: &[String],
@@ -14,21 +35,9 @@ fn execute_sub_process(
for word in words.iter().skip(1) {
match (*word).as_str() {
"%s" => {
- if let Some(curr_list) = context.tab_context_ref().curr_tab_ref().curr_list_ref() {
- let mut i = 0;
- curr_list
- .iter_selected()
- .map(|e| e.file_name())
- .for_each(|file_name| {
- command.arg(file_name);
- i += 1;
- });
- if i == 0 {
- if let Some(entry) = curr_list.curr_entry_ref() {
- command.arg(entry.file_name());
- }
- }
- }
+ current_filenames(context).into_iter().for_each(|x| {
+ command.arg(x);
+ });
}
s => {
command.arg(s);
diff --git a/src/config/clean/app/config.rs b/src/config/clean/app/config.rs
index fb8ceda..0995e8d 100644
--- a/src/config/clean/app/config.rs
+++ b/src/config/clean/app/config.rs
@@ -1,7 +1,11 @@
use std::collections::HashMap;
use crate::{
- config::{parse_config_or_default, raw::app::AppConfigRaw, TomlConfigFile},
+ config::{
+ parse_config_or_default,
+ raw::app::{AppConfigRaw, CustomCommand},
+ TomlConfigFile,
+ },
error::AppResult,
};
@@ -16,6 +20,7 @@ pub struct AppConfig {
pub xdg_open: bool,
pub xdg_open_fork: bool,
pub watch_files: bool,
+ pub custom_commands: Vec<CustomCommand>,
pub focus_on_create: bool,
pub cmd_aliases: HashMap<String, String>,
pub _display_options: DisplayOption,
@@ -84,6 +89,7 @@ impl From<AppConfigRaw> for AppConfig {
_preview_options: PreviewOption::from(raw.preview_options),
_search_options: SearchOption::from(raw.search_options),
_tab_options: TabOption::from(raw.tab_options),
+ custom_commands: raw.custom_commands,
}
}
}
diff --git a/src/config/clean/keymap/config.rs b/src/config/clean/keymap/config.rs
index 23d7b06..55e1bf9 100644
--- a/src/config/clean/keymap/config.rs
+++ b/src/config/clean/keymap/config.rs
@@ -46,23 +46,31 @@ fn command_keymaps_vec_to_map(keymaps: &[CommandKeymap]) -> HashMap<Event, Comma
let mut hashmap = HashMap::new();
for keymap in keymaps {
- if keymap.commands.is_empty() {
+ if keymap.commands.is_empty() && keymap.command.is_none() {
eprintln!("Keymap `commands` cannot be empty");
continue;
}
- let commands: Vec<Command> = keymap
- .commands
- .iter()
- .filter_map(|cmd_str| match Command::from_str(cmd_str) {
- Ok(s) => Some(s),
- Err(err) => {
- eprintln!("Keymap error: {}", err);
- None
- }
- })
- .collect();
+ let commands: Vec<Command> = match &keymap.command {
+ Some(command) => vec![command.clone()],
+ None => keymap.commands.clone(),
+ }
+ .iter()
+ .filter_map(|cmd_str| match Command::from_str(cmd_str) {
+ Ok(s) => Some(s),
+ Err(err) => {
+ eprintln!("Keymap error: {}", err);
+ None
+ }
+ })
+ .collect();
+
+ let expected_len = if keymap.command.is_none() {
+ keymap.commands.len()
+ } else {
+ 1
+ };
- if commands.len() != keymap.commands.len() {
+ if commands.len() != expected_len {
eprintln!("Failed to parse commands: {:?}", keymap.commands);
continue;
}
diff --git a/src/config/raw/app/config.rs b/src/config/raw/app/config.rs
index feac4f2..cf2b758 100644
--- a/src/config/raw/app/config.rs
+++ b/src/config/raw/app/config.rs
@@ -14,6 +14,12 @@ const fn default_scroll_offset() -> usize {
6
}
+#[derive(Debug, Deserialize, Clone)]
+pub struct CustomCommand {
+ pub name: String,
+ pub command: String,
+}
+
#[derive(Clone, Debug, Deserialize)]
pub struct AppConfigRaw {
#[serde(default = "default_scroll_offset")]
@@ -38,4 +44,6 @@ pub struct AppConfigRaw {
pub search_options: SearchOptionRaw,
#[serde(default, rename = "tab")]
pub tab_options: TabOptionRaw,
+ #[serde(default)]
+ pub custom_commands: Vec<CustomCommand>,
}
diff --git a/src/config/raw/keymap/config.rs b/src/config/raw/keymap/config.rs
index 3eae82d..8d958d6 100644
--- a/src/config/raw/keymap/config.rs
+++ b/src/config/raw/keymap/config.rs
@@ -3,7 +3,12 @@ use serde::Deserialize;
#[derive(Clone, Debug, Deserialize)]
pub struct CommandKeymap {
pub keys: Vec<String>,
+
+ #[serde(default)]
pub commands: Vec<String>,
+ #[serde(default)]
+ pub command: Option<String>,
+
pub description: Option<String>,
}
diff --git a/src/key_command/command.rs b/src/key_command/command.rs
index d218632..fad1530 100644
--- a/src/key_command/command.rs
+++ b/src/key_command/command.rs
@@ -184,6 +184,9 @@ pub enum Command {
Zoxide(String),
ZoxideInteractive,
+ CustomSearch(Vec<String>),
+ CustomSearchInteractive(Vec<String>),
+
BookmarkAdd,
BookmarkChangeDirectory,
}
diff --git a/src/key_command/constants.rs b/src/key_command/constants.rs
index 1edfb42..0656a58 100644
--- a/src/key_command/constants.rs
+++ b/src/key_command/constants.rs
@@ -93,6 +93,8 @@ cmd_constants![
(CMD_FILTER_STRING, "filter"),
(CMD_BOOKMARK_ADD, "add_bookmark"),
(CMD_BOOKMARK_CHANGE_DIRECTORY, "cd_bookmark"),
+ (CMD_CUSTOM_SEARCH, "custom_search"),
+ (CMD_CUSTOM_SEARCH_INTERACTIVE, "custom_search_interactive"),
];
pub fn complete_command(partial_command: &str) -> Vec<Pair> {
diff --git a/src/key_command/impl_appcommand.rs b/src/key_command/impl_appcommand.rs
index 4b86608..dd7ab84 100644
--- a/src/key_command/impl_appcommand.rs
+++ b/src/key_command/impl_appcommand.rs
@@ -101,6 +101,9 @@ impl AppCommand for Command {
Self::Zoxide(_) => CMD_ZOXIDE,
Self::ZoxideInteractive => CMD_ZOXIDE_INTERACTIVE,
+ Self::CustomSearch(_) => CMD_CUSTOM_SEARCH,
+ Self::CustomSearchInteractive(_) => CMD_CUSTOM_SEARCH_INTERACTIVE,
+
Self::BookmarkAdd => CMD_BOOKMARK_ADD,
Self::BookmarkChangeDirectory => CMD_BOOKMARK_CHANGE_DIRECTORY,
}
diff --git a/src/key_command/impl_appexecute.rs b/src/key_command/impl_appexecute.rs
index b4033c2..c90fc2b 100644
--- a/src/key_command/impl_appexecute.rs
+++ b/src/key_command/impl_appexecute.rs
@@ -169,6 +169,13 @@ impl AppExecute for Command {
Self::BookmarkAdd => bookmark::add_bookmark(context, backend),
Self::BookmarkChangeDirectory => bookmark::change_directory_bookmark(context, backend),
+
+ Self::CustomSearch(words) => {
+ custom_search::custom_search(context, backend, words.as_slice(), false)
+ }
+ Self::CustomSearchInteractive(words) => {
+ custom_search::custom_search(context, backend, words.as_slice(), true)
+ }
}
}
}
diff --git a/src/key_command/impl_comment.rs b/src/key_command/impl_comment.rs
index 3922b32..0483e85 100644
--- a/src/key_command/impl_comment.rs
+++ b/src/key_command/impl_comment.rs
@@ -140,6 +140,10 @@ impl CommandComment for Command {
Self::BookmarkAdd => "Add a bookmark",
Self::BookmarkChangeDirectory => "Navigate to a bookmark",
+ Self::CustomSearch(_) => "Find file based on the custom command",
+ Self::CustomSearchInteractive(_) => {
+ "Interactively find file based on the custom command"
+ }
}
}
}
diff --git a/src/key_command/impl_from_str.rs b/src/key_command/impl_from_str.rs
index 33fdfef..325003f 100644
--- a/src/key_command/impl_from_str.rs
+++ b/src/key_command/impl_from_str.rs
@@ -100,6 +100,16 @@ impl std::str::FromStr for Command {
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_CUSTOM_SEARCH,
+ Self::CustomSearch(arg.split(' ').map(|x| x.to_string()).collect())
+ );
+ simple_command_conversion_case!(
+ command,
+ CMD_CUSTOM_SEARCH_INTERACTIVE,
+ Self::CustomSearchInteractive(arg.split(' ').map(|x| x.to_string()).collect())
+ );
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);