Skip to content

Commit

Permalink
custom_commands + fallback for the older configurations (#446)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
Co-authored-by: Jeff Zhao <[email protected]>
  • Loading branch information
3 people authored Nov 4, 2023
1 parent ee50d17 commit 960decf
Show file tree
Hide file tree
Showing 22 changed files with 292 additions and 29 deletions.
1 change: 1 addition & 0 deletions config/joshuto.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ watch_files = true
xdg_open = false
xdg_open_fork = false

custom_commands = []

[display]
# default, hsplit
Expand Down
21 changes: 21 additions & 0 deletions docs/configuration/custom_commands/git_ignored
Original file line number Diff line number Diff line change
@@ -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
20 changes: 20 additions & 0 deletions docs/configuration/custom_commands/git_untracked
Original file line number Diff line number Diff line change
@@ -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
20 changes: 20 additions & 0 deletions docs/configuration/custom_commands/joshuto_git_conflicts
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions docs/configuration/custom_commands/joshuto_git_root
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
echo "$(git rev-parse --show-toplevel)/.git"
2 changes: 2 additions & 0 deletions docs/configuration/custom_commands/joshuto_rg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
rg -l "$@" | tail -n 1
5 changes: 5 additions & 0 deletions docs/configuration/custom_commands/joshuto_rgfzf
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions docs/configuration/joshuto.toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -128,4 +134,5 @@ fzf_case_sensitivity = "insensitive"
[tab]
# inherit, home, root
home_page = "home"

```
9 changes: 9 additions & 0 deletions docs/configuration/keymap.toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
110 changes: 110 additions & 0 deletions src/commands/custom_search.rs
Original file line number Diff line number Diff line change
@@ -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),
))
}
}
1 change: 1 addition & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
39 changes: 24 additions & 15 deletions src/commands/sub_process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand All @@ -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);
Expand Down
8 changes: 7 additions & 1 deletion src/config/clean/app/config.rs
Original file line number Diff line number Diff line change
@@ -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,
};

Expand All @@ -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,
Expand Down Expand Up @@ -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,
}
}
}
34 changes: 21 additions & 13 deletions src/config/clean/keymap/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
8 changes: 8 additions & 0 deletions src/config/raw/app/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand All @@ -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>,
}
5 changes: 5 additions & 0 deletions src/config/raw/keymap/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
}

Expand Down
Loading

0 comments on commit 960decf

Please sign in to comment.