Skip to content

Commit

Permalink
Zoxide related features (#506)
Browse files Browse the repository at this point in the history
* make `:z` act more like zoxide

before checking the zoxide database, check if the query is a directory
that can be navigated to as by `:cd`
also interpret `~` and `-` correctly

* optionally track all movement in zoxide

Add new config option `zoxide_update` with default false. When manually
enabled, all directory navigation (via manually navigating or `:cd` and
`:z`) will update the `zoxide` database.
Since navigation via `:z` always updates the database, ensure that it
doesn't update it twice.

* support arguments for zoxide interactive

support for `:zi <args>` where arguments are used to prefilter the
matches provided in the interactive zoxide prompt

* allow command aliases with arguments

note: only aliases that do not contain spaces can be used with arguments
since either the entire command or only the first word are checked
against the aliases
  • Loading branch information
chrjabs authored Apr 3, 2024
1 parent 2392951 commit d949668
Show file tree
Hide file tree
Showing 13 changed files with 93 additions and 13 deletions.
1 change: 1 addition & 0 deletions config/joshuto.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ watch_files = true
xdg_open = false
xdg_open_fork = false
case_insensitive_ext = false
zoxide_update = false

custom_commands = []

Expand Down
3 changes: 3 additions & 0 deletions docs/configuration/joshuto.toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ focus_on_create = true
# The maximum file size to show a preview for
max_preview_size = 2097152 # 2MB

# Update the zoxide database with every navigation type instead of only with the z command
zoxide_update = false

# Define custom commands (using shell) with parameters like %text, %s etc.
custom_commands = [
{ name = "rgfzf", command = "/home/<USER>/.config/joshuto/rgfzf '%text' %s" },
Expand Down
6 changes: 5 additions & 1 deletion src/commands/change_directory.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::path;

use crate::commands::reload;
use crate::commands::{reload, zoxide};
use crate::context::AppContext;
use crate::error::AppResult;
use crate::history::{generate_entries_to_root, DirectoryHistory};
Expand All @@ -10,6 +10,10 @@ use crate::util::cwd;
pub fn cd(path: &path::Path, context: &mut AppContext) -> std::io::Result<()> {
cwd::set_current_dir(path)?;
context.tab_context_mut().curr_tab_mut().set_cwd(path);
if context.config_ref().zoxide_update {
debug_assert!(path.is_absolute());
zoxide::zoxide_add(path.to_str().expect("cannot convert path to string"))?;
}
Ok(())
}

Expand Down
10 changes: 9 additions & 1 deletion src/commands/command_line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,20 @@ pub fn read_and_execute(
.suffix(suffix)
.get_input(backend, context, &mut listener);

if let Some(s) = user_input {
if let Some(mut s) = user_input {
let mut trimmed = s.trim_start();
let _ = context.commandline_context_mut().history_mut().add(trimmed);

let (command, arg) = match trimmed.find(' ') {
Some(i) => (&trimmed[..i], &trimmed[i..]),
None => (trimmed, ""),
};

if let Some(alias) = context.config_ref().cmd_aliases.get(trimmed) {
trimmed = alias;
} else if let Some(alias) = context.config_ref().cmd_aliases.get(command) {
s.replace_range(..s.len() - arg.len(), alias);
trimmed = &s;
}

let command = Command::from_str(trimmed)?;
Expand Down
33 changes: 28 additions & 5 deletions src/commands/zoxide.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,34 @@ use crate::ui::AppBackend;
pub fn zoxide_query(context: &mut AppContext, args: &str) -> AppResult {
let cwd = std::env::current_dir()?;

let path = Path::new(args);
if change_directory::change_directory(context, path).is_ok() {
if !context.config_ref().zoxide_update {
let cwd = context
.tab_context_ref()
.curr_tab_ref()
.cwd()
.to_str()
.expect("path cannot be converted to string");
zoxide_add(cwd)?;
}
return Ok(());
}

let zoxide_output = Command::new("zoxide")
.arg("query")
.arg("--exclude")
.arg(&cwd)
.arg("--")
.args(args.split(' ').collect::<Vec<&str>>())
.args(args.split(' '))
.output()?;

if zoxide_output.status.success() {
if let Ok(zoxide_str) = std::str::from_utf8(&zoxide_output.stdout) {
let zoxide_path = &zoxide_str[..zoxide_str.len() - 1];
zoxide_add(zoxide_path)?;
if !context.config_ref().zoxide_update {
zoxide_add(zoxide_path)?;
}

let path = Path::new(zoxide_path);
context
Expand All @@ -37,13 +53,18 @@ pub fn zoxide_query(context: &mut AppContext, args: &str) -> AppResult {
Ok(())
}

pub fn zoxide_query_interactive(context: &mut AppContext, backend: &mut AppBackend) -> AppResult {
pub fn zoxide_query_interactive(
context: &mut AppContext,
backend: &mut AppBackend,
args: &str,
) -> AppResult {
backend.terminal_drop();

let zoxide_process = Command::new("zoxide")
.arg("query")
.arg("-i")
.arg("--")
.args(args.split(' '))
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
Expand All @@ -54,7 +75,9 @@ pub fn zoxide_query_interactive(context: &mut AppContext, backend: &mut AppBacke
if zoxide_output.status.success() {
if let Ok(zoxide_str) = std::str::from_utf8(&zoxide_output.stdout) {
let zoxide_path = &zoxide_str[..zoxide_str.len() - 1];
zoxide_add(zoxide_path)?;
if !context.config_ref().zoxide_update {
zoxide_add(zoxide_path)?;
}

let path = Path::new(zoxide_path);
context
Expand All @@ -70,7 +93,7 @@ pub fn zoxide_query_interactive(context: &mut AppContext, backend: &mut AppBacke
Ok(())
}

fn zoxide_add(s: &str) -> io::Result<()> {
pub fn zoxide_add(s: &str) -> io::Result<()> {
Command::new("zoxide").arg("add").arg(s).output()?;
Ok(())
}
2 changes: 2 additions & 0 deletions src/config/clean/app/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub struct AppConfig {
pub focus_on_create: bool,
pub mouse_support: bool,
pub cmd_aliases: HashMap<String, String>,
pub zoxide_update: bool,
pub _display_options: DisplayOption,
pub _preview_options: PreviewOption,
pub _search_options: SearchOption,
Expand Down Expand Up @@ -90,6 +91,7 @@ impl From<AppConfigRaw> for AppConfig {
cmd_aliases: raw.cmd_aliases,
focus_on_create: raw.focus_on_create,
mouse_support: raw.mouse_support,
zoxide_update: raw.zoxide_update,
_display_options: DisplayOption::from(raw.display_options),
_preview_options: PreviewOption::from(raw.preview_options),
_search_options: SearchOption::from(raw.search_options),
Expand Down
2 changes: 2 additions & 0 deletions src/config/raw/app/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ pub struct AppConfigRaw {
#[serde(default = "default_true")]
pub mouse_support: bool,
#[serde(default)]
pub zoxide_update: bool,
#[serde(default)]
pub cmd_aliases: HashMap<String, String>,
#[serde(default, rename = "display")]
pub display_options: DisplayOptionRaw,
Expand Down
2 changes: 1 addition & 1 deletion src/key_command/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ pub enum Command {
processor: PostProcessor,
},
Zoxide(String),
ZoxideInteractive,
ZoxideInteractive(String),

CustomSearch(Vec<String>),
CustomSearchInteractive(Vec<String>),
Expand Down
2 changes: 1 addition & 1 deletion src/key_command/impl_appcommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ impl AppCommand for Command {
Self::SubdirFzf => CMD_SUBDIR_FZF,
Self::SelectFzf { .. } => CMD_SELECT_FZF,
Self::Zoxide(_) => CMD_ZOXIDE,
Self::ZoxideInteractive => CMD_ZOXIDE_INTERACTIVE,
Self::ZoxideInteractive(_) => CMD_ZOXIDE_INTERACTIVE,

Self::CustomSearch(_) => CMD_CUSTOM_SEARCH,
Self::CustomSearchInteractive(_) => CMD_CUSTOM_SEARCH_INTERACTIVE,
Expand Down
4 changes: 3 additions & 1 deletion src/key_command/impl_appexecute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,9 @@ impl AppExecute for Command {
Self::SubdirFzf => subdir_fzf::subdir_fzf(context, backend),
Self::SelectFzf { options } => select_fzf::select_fzf(context, backend, options),
Self::Zoxide(arg) => zoxide::zoxide_query(context, arg),
Self::ZoxideInteractive => zoxide::zoxide_query_interactive(context, backend),
Self::ZoxideInteractive(args) => {
zoxide::zoxide_query_interactive(context, backend, args)
}

Self::BookmarkAdd => bookmark::add_bookmark(context, backend),
Self::BookmarkChangeDirectory => bookmark::change_directory_bookmark(context, backend),
Expand Down
2 changes: 1 addition & 1 deletion src/key_command/impl_comment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ impl CommandComment for Command {
Self::SubdirFzf => "Switch to a child directory via fzf",
Self::SelectFzf { .. } => "Select via fzf",
Self::Zoxide(_) => "Zoxide",
Self::ZoxideInteractive => "Zoxide interactive",
Self::ZoxideInteractive(_) => "Zoxide interactive",

Self::BookmarkAdd => "Add a bookmark",
Self::BookmarkChangeDirectory => "Navigate to a bookmark",
Expand Down
29 changes: 27 additions & 2 deletions src/key_command/impl_from_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,11 @@ impl std::str::FromStr for Command {
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);
simple_command_conversion_case!(
command,
CMD_ZOXIDE_INTERACTIVE,
Self::ZoxideInteractive(arg.to_string())
);

if command == CMD_QUIT {
match arg {
Expand Down Expand Up @@ -602,6 +605,28 @@ impl std::str::FromStr for Command {
Ok(Self::FilterString {
pattern: arg.to_string(),
})
} else if command == CMD_ZOXIDE {
match arg {
"" => match HOME_DIR.as_ref() {
Some(s) => Ok(Self::ChangeDirectory { path: s.clone() }),
None => Err(AppError::new(
AppErrorKind::EnvVarNotPresent,
format!("{}: Cannot find home directory", command),
)),
},
".." => Ok(Self::ParentDirectory),
"-" => Ok(Self::PreviousDirectory),
arg => {
let (head, tail) = match arg.find(' ') {
Some(i) => (&arg[..i], &arg[i..]),
None => (arg, ""),
};
let head = unix::expand_shell_string_cow(head);
let mut args = String::from(head);
args.push_str(tail);
Ok(Self::Zoxide(args))
}
}
} else {
Err(AppError::new(
AppErrorKind::UnrecognizedCommand,
Expand Down
10 changes: 10 additions & 0 deletions src/util/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ pub fn mode_to_string(mode: u32) -> String {
mode_str
}

pub fn expand_shell_string_cow(s: &str) -> std::borrow::Cow<'_, str> {
let dir = dirs_next::home_dir();
let os_str = dir.map(|s| s.as_os_str().to_owned());
let context_func = || {
let cow_str = os_str.as_ref().map(|s| s.to_string_lossy());
cow_str
};
shellexpand::tilde_with_context(s, context_func)
}

pub fn expand_shell_string(s: &str) -> path::PathBuf {
let dir = dirs_next::home_dir();
let os_str = dir.map(|s| s.as_os_str().to_owned());
Expand Down

0 comments on commit d949668

Please sign in to comment.