Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

custom_commands + fallback for the older configurations #446

Merged
merged 4 commits into from
Nov 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading