summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTomasz Durda <tomekdur@wp.pl>2023-11-04 16:30:23 +0100
committerGitHub <noreply@github.com>2023-11-04 11:30:23 -0400
commit960decfcb89de5601f483d559096afe3481ae10c (patch)
tree83b708cf0897aee98144274cc8ab6ae1c3c4674a
parentee50d175d8c14cc7420e922864e6c5289c2785f3 (diff)
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 <edotdurda@e-science.pl> Co-authored-by: Jeff Zhao <jeff.no.zhao@gmail.com>
-rw-r--r--config/joshuto.toml1
-rwxr-xr-xdocs/configuration/custom_commands/git_ignored21
-rwxr-xr-xdocs/configuration/custom_commands/git_untracked20
-rwxr-xr-xdocs/configuration/custom_commands/joshuto_git_conflicts20
-rwxr-xr-xdocs/configuration/custom_commands/joshuto_git_root2
-rwxr-xr-xdocs/configuration/custom_commands/joshuto_rg2
-rwxr-xr-xdocs/configuration/custom_commands/joshuto_rgfzf5
-rw-r--r--docs/configuration/joshuto.toml.md7
-rw-r--r--docs/configuration/keymap.toml.md9
-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
22 files changed, 292 insertions, 29 deletions
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<cnt;i++)); do
+ FILES[i]=$(realpath --relative-to "$CURRENT_PATH" "${GIT_PATH}/${FILES[i]}")
+done
+
+cd $CURRENT_PATH
+
+echo "${FILES[*]}" \
+ | fzf --ansi --preview 'bat -n $(echo {})' \
+ | cut -d ":" -f1
diff --git a/docs/configuration/custom_commands/git_untracked b/docs/configuration/custom_commands/git_untracked
new file mode 100755
index 0000000..fedae42
--- /dev/null
+++ b/docs/configuration/custom_commands/git_untracked
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+CURRENT_PATH="$PWD"
+GIT_PATH="$(git rev-parse --show-toplevel)"
+
+cd $GIT_PATH
+GIT_PATH="$PWD"
+
+IFS=$'\n' FILES=($(git ls-files . --exclude-standard --others))
+
+cnt=${#FILES[@]}
+for ((i=0;i<cnt;i++)); do
+ FILES[i]=$(realpath --relative-to "$CURRENT_PATH" "${GIT_PATH}/${FILES[i]}")
+done
+
+cd $CURRENT_PATH
+
+echo "${FILES[*]}" \
+ | fzf --ansi --preview 'bat -n $(echo {})' \
+ | cut -d ":" -f1
diff --git a/docs/configuration/custom_commands/joshuto_git_conflicts b/docs/configuration/custom_commands/joshuto_git_conflicts
new file mode 100755
index 0000000..7721e88
--- /dev/null
+++ b/docs/configuration/custom_commands/joshuto_git_conflicts
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+CURRENT_PATH="$PWD"
+GIT_PATH="$(git rev-parse --show-toplevel)"
+
+cd $GIT_PATH
+GIT_PATH="$PWD"
+
+IFS=$'\n' FILES=($(git diff --name-only --diff-filter=U --relative))
+
+cnt=${#FILES[@]}
+for ((i=0;i<cnt;i++)); do
+ FILES[i]=$(realpath --relative-to "$CURRENT_PATH" "${GIT_PATH}/${FILES[i]}")
+done
+
+cd $CURRENT_PATH
+
+echo "${FILES[*]}" \
+ | fzf --ansi --preview 'bat -n $(echo {})' \
+ | cut -d ":" -f1
diff --git a/docs/configuration/custom_commands/joshuto_git_root b/docs/configuration/custom_commands/joshuto_git_root
new file mode 100755
index 0000000..0b230ca
--- /dev/null
+++ b/docs/configuration/custom_commands/joshuto_git_root
@@ -0,0 +1,2 @@
+#!/bin/bash
+echo "$(git rev-parse --show-toplevel)/.git"
diff --git a/docs/configuration/custom_commands/joshuto_rg b/docs/configuration/custom_commands/joshuto_rg
new file mode 100755
index 0000000..4562acc
--- /dev/null
+++ b/docs/configuration/custom_commands/joshuto_rg
@@ -0,0 +1,2 @@
+#!/bin/bash
+rg -l "$@" | tail -n 1
diff --git a/docs/configuration/custom_commands/joshuto_rgfzf b/docs/configuration/custom_commands/joshuto_rgfzf
new file mode 100755
index 0000000..1c0b7e0
--- /dev/null
+++ b/docs/configuration/custom_commands/joshuto_rgfzf
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+rg -n -H --color=never "$@" \
+ | fzf --ansi --preview 'bat -n $(echo {} | cut -d ":" -f1) --line-range="$(echo {} | cut -d ":" -f2):"' \
+ | cut -d ":" -f1
diff --git a/docs/configuration/joshuto.toml.md b/docs/configuration/joshuto.toml.md
index 1fc6e6e..7c90fc2 100644
--- a/docs/configuration/joshuto.toml.md
+++ b/docs/configuration/joshuto.toml.md
@@ -30,6 +30,12 @@ focus_on_create = true
# The maximum file size to show a preview for
max_preview_size = 2097152 # 2MB
+# Define custom commands (using shell) with parameters like %text, %s etc.
+custom_commands = [
+ { name = "rgfzf", command = "/home/<USER>/.config/joshuto/rgfzf '%text' %s" },
+ { name = "rg", command = "/home/<USER>/.config/joshuto/rg '%text' %s" }
+]
+
# Configurations related to the display
[display]
# Different view layouts
@@ -128,4 +134,5 @@ fzf_case_sensitivity = "insensitive"
[tab]
# inherit, home, root
home_page = "home"
+
```
diff --git a/docs/configuration/keymap.toml.md b/docs/configuration/keymap.toml.md
index c83ebf7..0a3b791 100644
--- a/docs/configuration/keymap.toml.md
+++ b/docs/configuration/keymap.toml.md
@@ -405,6 +405,15 @@ An example:
:set_case_sensitivity --type=fzf sensitive
```
+### `custom_search`
+
+Define search command using [`custom_command`]()
+
+### `custom_search_interactive`
+
+Similar to `select` and `custom_search`. Allows user to execute `custom_command` and
+then interactively operate on the results.
+
## Bookmarks
### `add_bookmark`: adds a bookmark to the `bookmarks.toml` file
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);