From 960decfcb89de5601f483d559096afe3481ae10c Mon Sep 17 00:00:00 2001 From: Tomasz Durda Date: Sat, 4 Nov 2023 16:30:23 +0100 Subject: custom_commands + fallback for the older configurations (#446) * custom_commands + fallback for the older configurations 1. Added custom_commands 2. Implemented custom_search 3. Implemented custom_serach_interactive 4. Added fallback for the command in the keymaps * Docs + missing file * Added two more joshuto scripts --------- Co-authored-by: Tomasz Durda Co-authored-by: Jeff Zhao --- config/joshuto.toml | 1 + docs/configuration/custom_commands/git_ignored | 21 ++++ docs/configuration/custom_commands/git_untracked | 20 ++++ .../custom_commands/joshuto_git_conflicts | 20 ++++ .../configuration/custom_commands/joshuto_git_root | 2 + docs/configuration/custom_commands/joshuto_rg | 2 + docs/configuration/custom_commands/joshuto_rgfzf | 5 + docs/configuration/joshuto.toml.md | 7 ++ docs/configuration/keymap.toml.md | 9 ++ src/commands/custom_search.rs | 110 +++++++++++++++++++++ src/commands/mod.rs | 1 + src/commands/sub_process.rs | 39 +++++--- src/config/clean/app/config.rs | 8 +- src/config/clean/keymap/config.rs | 34 ++++--- src/config/raw/app/config.rs | 8 ++ src/config/raw/keymap/config.rs | 5 + src/key_command/command.rs | 3 + src/key_command/constants.rs | 2 + src/key_command/impl_appcommand.rs | 3 + src/key_command/impl_appexecute.rs | 7 ++ src/key_command/impl_comment.rs | 4 + src/key_command/impl_from_str.rs | 10 ++ 22 files changed, 292 insertions(+), 29 deletions(-) create mode 100755 docs/configuration/custom_commands/git_ignored create mode 100755 docs/configuration/custom_commands/git_untracked create mode 100755 docs/configuration/custom_commands/joshuto_git_conflicts create mode 100755 docs/configuration/custom_commands/joshuto_git_root create mode 100755 docs/configuration/custom_commands/joshuto_rg create mode 100755 docs/configuration/custom_commands/joshuto_rgfzf create mode 100644 src/commands/custom_search.rs diff --git a/config/joshuto.toml b/config/joshuto.toml index 0543241..b7d4626 100644 --- a/config/joshuto.toml +++ b/config/joshuto.toml @@ -6,6 +6,7 @@ watch_files = true xdg_open = false xdg_open_fork = false +custom_commands = [] [display] # default, hsplit diff --git a/docs/configuration/custom_commands/git_ignored b/docs/configuration/custom_commands/git_ignored new file mode 100755 index 0000000..fa35764 --- /dev/null +++ b/docs/configuration/custom_commands/git_ignored @@ -0,0 +1,21 @@ +#!/bin/bash + +CURRENT_PATH="$PWD" +GIT_PATH="$(git rev-parse --show-toplevel)" + +cd $GIT_PATH +GIT_PATH="$PWD" + +IFS=$'\n' FILES=($(git ls-files . --ignored --exclude-standard --others)) + + +cnt=${#FILES[@]} +for ((i=0;i 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", ¤t_filenames.join(" ")); + let text = text.replace( + "%text", + &words + .iter() + .skip(1) + .cloned() + .collect::>() + .join(" "), + ); + let mut command_with_args: Vec = 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, pub focus_on_create: bool, pub cmd_aliases: HashMap, pub _display_options: DisplayOption, @@ -84,6 +89,7 @@ impl From 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 = 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 = 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, } 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, + + #[serde(default)] pub commands: Vec, + #[serde(default)] + pub command: Option, + pub description: Option, } 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), + CustomSearchInteractive(Vec), + 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 { 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); -- cgit v1.2.3