$_ZO_ECHO must be set to `1` to be enabled

This commit is contained in:
Ajeet D'Souza 2020-05-03 02:42:36 +05:30
parent e4dae1dd54
commit 4731533352
8 changed files with 436 additions and 413 deletions

View File

@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Interactive mode in `zoxide` no longer throws an error if `fzf` exits gracefully. - Interactive mode in `zoxide` no longer throws an error if `fzf` exits gracefully.
- Canonicalize to regular paths instead of UNC paths on Windows. - Canonicalize to regular paths instead of UNC paths on Windows.
- `zoxide init` now uses PWD hooks by default for better performance. - `zoxide init` now uses PWD hooks by default for better performance.
- `$_ZO_ECHO` now only works when set to `1`.
### Fixed ### Fixed

View File

@ -1,413 +0,0 @@
use anyhow::{anyhow, Result};
use clap::arg_enum;
use structopt::StructOpt;
use uuid::Uuid;
use std::borrow::Cow;
use std::io::{self, Write};
#[derive(Debug, StructOpt)]
#[structopt(about = "Generates shell configuration")]
pub struct Init {
#[structopt(possible_values = &Shell::variants(), case_insensitive = true)]
shell: Shell,
#[structopt(
long,
help = "Changes the name of the 'z' command",
default_value = "z"
)]
z_cmd: String,
#[structopt(
long,
help = "Prevents zoxide from defining any commands other than 'z'"
)]
no_define_aliases: bool,
#[structopt(
long,
help = "Chooses event on which an entry is added to the database",
possible_values = &Hook::variants(),
default_value = "pwd",
case_insensitive = true
)]
hook: Hook,
}
impl Init {
pub fn run(&self) -> Result<()> {
let config = match self.shell {
Shell::bash => BASH_CONFIG,
Shell::fish => FISH_CONFIG,
Shell::posix => POSIX_CONFIG,
Shell::zsh => ZSH_CONFIG,
};
let stdout = io::stdout();
let mut handle = stdout.lock();
let z = config.z;
writeln!(handle, "{}", z(&self.z_cmd)).unwrap();
if !self.no_define_aliases {
let alias = config.alias;
writeln!(handle, "{}", alias(&self.z_cmd)).unwrap();
}
match self.hook {
Hook::none => (),
Hook::prompt => writeln!(handle, "{}", config.hook.prompt).unwrap(),
Hook::pwd => {
let hook_pwd = config.hook.pwd;
writeln!(handle, "{}", hook_pwd()?).unwrap();
}
}
Ok(())
}
}
arg_enum! {
#[allow(non_camel_case_types)]
#[derive(Debug)]
enum Shell {
bash,
fish,
posix,
zsh,
}
}
arg_enum! {
#[allow(non_camel_case_types)]
#[derive(Debug)]
enum Hook {
none,
prompt,
pwd,
}
}
const BASH_CONFIG: ShellConfig = ShellConfig {
z: bash_z,
alias: bash_alias,
hook: HookConfig {
prompt: BASH_HOOK_PROMPT,
pwd: bash_hook_pwd,
},
};
const FISH_CONFIG: ShellConfig = ShellConfig {
z: fish_z,
alias: fish_alias,
hook: HookConfig {
prompt: FISH_HOOK_PROMPT,
pwd: fish_hook_pwd,
},
};
const POSIX_CONFIG: ShellConfig = ShellConfig {
z: posix_z,
alias: posix_alias,
hook: HookConfig {
prompt: POSIX_HOOK_PROMPT,
pwd: posix_hook_pwd,
},
};
const ZSH_CONFIG: ShellConfig = ShellConfig {
z: zsh_z,
alias: zsh_alias,
hook: HookConfig {
prompt: ZSH_HOOK_PROMPT,
pwd: zsh_hook_pwd,
},
};
struct ShellConfig {
z: fn(&str) -> String,
alias: fn(&str) -> String,
hook: HookConfig,
}
struct HookConfig {
prompt: &'static str,
pwd: fn() -> Result<Cow<'static, str>>,
}
fn fish_z(z_cmd: &str) -> String {
format!(
r#"
function _z_cd
cd $argv
or return $status
commandline -f repaint
if test -n "$_ZO_ECHO"
echo $PWD
end
end
function {}
set argc (count $argv)
if test $argc -eq 0
_z_cd $HOME
or return $status
else if test $argc -eq 1 -a $argv[1] = '-'
_z_cd -
or return $status
else
# FIXME: use string-collect from fish 3.1.0 once it has wider adoption
set -l IFS ''
set -l result (zoxide query $argv)
if test -d $result; and string length -q -- $result
_z_cd $result
or return $status
else if test -n "$result"
echo $result
end
end
end
"#,
z_cmd
)
}
fn posix_z(z_cmd: &str) -> String {
format!(
r#"
_z_cd() {{
cd "$@" || return "$?"
if [ -n "$_ZO_ECHO" ]; then
echo "$PWD"
fi
}}
{}() {{
if [ "$#" -eq 0 ]; then
_z_cd ~ || return "$?"
elif [ "$#" -eq 1 ] && [ "$1" = '-' ]; then
if [ -n "$OLDPWD" ]; then
_z_cd "$OLDPWD" || return "$?"
else
echo 'zoxide: $OLDPWD is not set'
return 1
fi
else
result="$(zoxide query "$@")" || return "$?"
if [ -d "$result" ]; then
_z_cd "$result" || return "$?"
elif [ -n "$result" ]; then
echo "$result"
fi
fi
}}
"#,
z_cmd
)
}
use posix_z as bash_z;
use posix_z as zsh_z;
fn fish_alias(z_cmd: &str) -> String {
format!(
r#"
abbr -a zi '{} -i'
abbr -a za 'zoxide add'
abbr -a zq 'zoxide query'
abbr -a zqi 'zoxide query -i'
abbr -a zr 'zoxide remove'
abbr -a zri 'zoxide remove -i'
"#,
z_cmd
)
}
fn posix_alias(z_cmd: &str) -> String {
format!(
r#"
alias zi='{} -i'
alias za='zoxide add'
alias zq='zoxide query'
alias zqi='zoxide query -i'
alias zr='zoxide remove'
alias zri='zoxide remove -i'
"#,
z_cmd
)
}
use posix_alias as bash_alias;
use posix_alias as zsh_alias;
const BASH_HOOK_PROMPT: &str = r#"
_zoxide_hook() {
zoxide add
}
case "$PROMPT_COMMAND" in
*_zoxide_hook*) ;;
*) PROMPT_COMMAND="_zoxide_hook${PROMPT_COMMAND:+;${PROMPT_COMMAND}}" ;;
esac
"#;
const FISH_HOOK_PROMPT: &str = r#"
function _zoxide_hook --on-event fish_prompt
zoxide add
end
"#;
const POSIX_HOOK_PROMPT: &str = r#"
_zoxide_hook() {
zoxide add
}
case "$PS1" in
*\$\(_zoxide_hook\)*) ;;
*) PS1="\$(_zoxide_hook)${PS1}" ;;
esac
"#;
const ZSH_HOOK_PROMPT: &str = r#"
_zoxide_hook() {
zoxide add
}
[[ -n "${precmd_functions[(r)_zoxide_hook]}" ]] || {
precmd_functions+=(_zoxide_hook)
}
"#;
const fn bash_hook_pwd() -> Result<Cow<'static, str>> {
const HOOK_PWD: &str = r#"
_zoxide_hook() {
if [ -z "${_ZO_PWD}" ]; then
_ZO_PWD="${PWD}"
elif [ "${_ZO_PWD}" != "${PWD}" ]; then
_ZO_PWD="${PWD}"
zoxide add
fi
}
case "$PROMPT_COMMAND" in
*_zoxide_hook*) ;;
*) PROMPT_COMMAND="_zoxide_hook${PROMPT_COMMAND:+;${PROMPT_COMMAND}}" ;;
esac
"#;
Ok(Cow::Borrowed(HOOK_PWD))
}
const fn fish_hook_pwd() -> Result<Cow<'static, str>> {
const HOOK_PWD: &str = r#"
function _zoxide_hook --on-variable PWD
zoxide add
end
"#;
Ok(Cow::Borrowed(HOOK_PWD))
}
fn posix_hook_pwd() -> Result<Cow<'static, str>> {
let mut tmp_path = std::env::temp_dir();
tmp_path.push("zoxide");
let tmp_path_str = tmp_path
.to_str()
.ok_or_else(|| anyhow!("invalid Unicode in zoxide tmp path"))?;
let pwd_path = tmp_path.join(format!("pwd-{}", Uuid::new_v4()));
let pwd_path_str = pwd_path
.to_str()
.ok_or_else(|| anyhow!("invalid Unicode in zoxide pwd path"))?;
let hook_pwd = format!(
r#"
# PWD hooks in POSIX use a temporary file, located at `$_ZO_PWD_PATH`, to track
# changes in the current directory. These files are removed upon restart,
# but they should ideally also be cleaned up once the shell exits using traps.
#
# This can be done as follows:
#
# trap '_zoxide_cleanup' EXIT HUP KILL TERM
# trap '_zoxide_cleanup; trap - INT; kill -s INT "$$"' INT
# trap '_zoxide_cleanup; trap - QUIT; kill -s QUIT "$$"' QUIT
#
# By default, traps are not set up because they override all previous traps.
# It is therefore up to the user to add traps to their shell configuration.
_ZO_TMP_PATH={}
_ZO_PWD_PATH={}
_zoxide_cleanup() {{
rm -f "$_ZO_PWD_PATH"
}}
_zoxide_setpwd() {{
mkdir -p "$_ZO_TMP_PATH"
echo "$PWD" > "$_ZO_PWD_PATH"
}}
_zoxide_setpwd
_zoxide_hook() {{
_ZO_OLDPWD="$(cat "$_ZO_PWD_PATH")"
if [ -z "$_ZO_OLDPWD" ] || [ "$_ZO_OLDPWD" != "$PWD" ]; then
_zoxide_setpwd && zoxide add > /dev/null
fi
}}
case "$PS1" in
*\$\(_zoxide_hook\)*) ;;
*) PS1="\$(_zoxide_hook)${{PS1}}" ;;
esac"#,
posix_quote(tmp_path_str),
posix_quote(pwd_path_str),
);
Ok(Cow::Owned(hook_pwd))
}
const fn zsh_hook_pwd() -> Result<Cow<'static, str>> {
const HOOK_PWD: &str = r#"
_zoxide_hook() {
zoxide add
}
chpwd_functions=(${chpwd_functions[@]} "_zoxide_hook")
"#;
Ok(Cow::Borrowed(HOOK_PWD))
}
fn posix_quote(string: &str) -> String {
let mut quoted = String::with_capacity(string.len() + 2);
quoted.push('\'');
for ch in string.chars() {
match ch {
'\\' => quoted.push_str(r"\\"),
'\'' => quoted.push_str(r"'\''"),
_ => quoted.push(ch),
}
}
quoted.push('\'');
quoted
}

View File

@ -0,0 +1,90 @@
mod shell;
use anyhow::Result;
use clap::arg_enum;
use structopt::StructOpt;
use std::io::{self, Write};
#[derive(Debug, StructOpt)]
#[structopt(about = "Generates shell configuration")]
pub struct Init {
#[structopt(possible_values = &Shell::variants(), case_insensitive = true)]
shell: Shell,
#[structopt(
long,
help = "Changes the name of the 'z' command",
default_value = "z"
)]
z_cmd: String,
#[structopt(
long,
help = "Prevents zoxide from defining any commands other than 'z'"
)]
no_define_aliases: bool,
#[structopt(
long,
help = "Chooses event on which an entry is added to the database",
possible_values = &Hook::variants(),
default_value = "pwd",
case_insensitive = true
)]
hook: Hook,
}
impl Init {
pub fn run(&self) -> Result<()> {
let config = match self.shell {
Shell::bash => shell::bash::CONFIG,
Shell::fish => shell::fish::CONFIG,
Shell::posix => shell::posix::CONFIG,
Shell::zsh => shell::zsh::CONFIG,
};
let stdout = io::stdout();
let mut handle = stdout.lock();
let z = config.z;
writeln!(handle, "{}", z(&self.z_cmd)).unwrap();
if !self.no_define_aliases {
let alias = config.alias;
writeln!(handle, "{}", alias(&self.z_cmd)).unwrap();
}
match self.hook {
Hook::none => (),
Hook::prompt => writeln!(handle, "{}", config.hook.prompt).unwrap(),
Hook::pwd => {
let hook_pwd = config.hook.pwd;
writeln!(handle, "{}", hook_pwd()?).unwrap();
}
}
Ok(())
}
}
arg_enum! {
#[allow(non_camel_case_types)]
#[derive(Debug)]
enum Shell {
bash,
fish,
posix,
zsh,
}
}
arg_enum! {
#[allow(non_camel_case_types)]
#[derive(Debug)]
enum Hook {
none,
prompt,
pwd,
}
}

View File

@ -0,0 +1,45 @@
use super::{posix, HookConfig, ShellConfig};
use anyhow::Result;
use std::borrow::Cow;
pub const CONFIG: ShellConfig = ShellConfig {
z: posix::CONFIG.z,
alias: posix::CONFIG.alias,
hook: HookConfig {
prompt: HOOK_PROMPT,
pwd: hook_pwd,
},
};
const HOOK_PROMPT: &str = r#"
_zoxide_hook() {
zoxide add
}
case "$PROMPT_COMMAND" in
*_zoxide_hook*) ;;
*) PROMPT_COMMAND="_zoxide_hook${PROMPT_COMMAND:+;${PROMPT_COMMAND}}" ;;
esac
"#;
const fn hook_pwd() -> Result<Cow<'static, str>> {
const HOOK_PWD: &str = r#"
_zoxide_hook() {
if [ -z "${_ZO_PWD}" ]; then
_ZO_PWD="${PWD}"
elif [ "${_ZO_PWD}" != "${PWD}" ]; then
_ZO_PWD="${PWD}"
zoxide add
fi
}
case "$PROMPT_COMMAND" in
*_zoxide_hook*) ;;
*) PROMPT_COMMAND="_zoxide_hook${PROMPT_COMMAND:+;${PROMPT_COMMAND}}" ;;
esac
"#;
Ok(Cow::Borrowed(HOOK_PWD))
}

View File

@ -0,0 +1,90 @@
use super::{ShellConfig, HookConfig};
use anyhow::Result;
use std::borrow::Cow;
pub const CONFIG: ShellConfig = ShellConfig {
z,
alias,
hook: HookConfig {
prompt: HOOK_PROMPT,
pwd: hook_pwd,
},
};
fn z(z_cmd: &str) -> String {
format!(
r#"
function _z_cd
cd $argv
or return $status
commandline -f repaint
if test "$_ZO_ECHO" = "1"
echo $PWD
end
end
function {}
set argc (count $argv)
if test $argc -eq 0
_z_cd $HOME
or return $status
else if test $argc -eq 1 -a $argv[1] = '-'
_z_cd -
or return $status
else
# FIXME: use string-collect from fish 3.1.0 once it has wider adoption
set -l IFS ''
set -l result (zoxide query $argv)
if test -d $result; and string length -q -- $result
_z_cd $result
or return $status
else if test -n "$result"
echo $result
end
end
end
"#,
z_cmd
)
}
fn alias(z_cmd: &str) -> String {
format!(
r#"
abbr -a zi '{} -i'
abbr -a za 'zoxide add'
abbr -a zq 'zoxide query'
abbr -a zqi 'zoxide query -i'
abbr -a zr 'zoxide remove'
abbr -a zri 'zoxide remove -i'
"#,
z_cmd
)
}
const HOOK_PROMPT: &str = r#"
function _zoxide_hook --on-event fish_prompt
zoxide add
end
"#;
const fn hook_pwd() -> Result<Cow<'static, str>> {
const HOOK_PWD: &str = r#"
function _zoxide_hook --on-variable PWD
zoxide add
end
"#;
Ok(Cow::Borrowed(HOOK_PWD))
}

View File

@ -0,0 +1,19 @@
pub mod bash;
pub mod fish;
pub mod posix;
pub mod zsh;
use anyhow::Result;
use std::borrow::Cow;
pub struct ShellConfig {
pub z: fn(&str) -> String,
pub alias: fn(&str) -> String,
pub hook: HookConfig,
}
pub struct HookConfig {
pub prompt: &'static str,
pub pwd: fn() -> Result<Cow<'static, str>>,
}

View File

@ -0,0 +1,155 @@
use super::{HookConfig, ShellConfig};
use anyhow::{anyhow, Result};
use uuid::Uuid;
use std::borrow::Cow;
pub const CONFIG: ShellConfig = ShellConfig {
z,
alias,
hook: HookConfig {
prompt: HOOK_PROMPT,
pwd: hook_pwd,
},
};
fn z(z_cmd: &str) -> String {
format!(
r#"
_z_cd() {{
cd "$@" || return "$?"
if [ "$_ZO_ECHO" = "1" ]; then
echo "$PWD"
fi
}}
{}() {{
if [ "$#" -eq 0 ]; then
_z_cd ~ || return "$?"
elif [ "$#" -eq 1 ] && [ "$1" = '-' ]; then
if [ -n "$OLDPWD" ]; then
_z_cd "$OLDPWD" || return "$?"
else
echo 'zoxide: $OLDPWD is not set'
return 1
fi
else
result="$(zoxide query "$@")" || return "$?"
if [ -d "$result" ]; then
_z_cd "$result" || return "$?"
elif [ -n "$result" ]; then
echo "$result"
fi
fi
}}
"#,
z_cmd
)
}
fn alias(z_cmd: &str) -> String {
format!(
r#"
alias zi='{} -i'
alias za='zoxide add'
alias zq='zoxide query'
alias zqi='zoxide query -i'
alias zr='zoxide remove'
alias zri='zoxide remove -i'
"#,
z_cmd
)
}
const HOOK_PROMPT: &str = r#"
_zoxide_hook() {
zoxide add
}
case "$PS1" in
*\$\(_zoxide_hook\)*) ;;
*) PS1="\$(_zoxide_hook)${PS1}" ;;
esac
"#;
fn hook_pwd() -> Result<Cow<'static, str>> {
let mut tmp_path = std::env::temp_dir();
tmp_path.push("zoxide");
let tmp_path_str = tmp_path
.to_str()
.ok_or_else(|| anyhow!("invalid Unicode in zoxide tmp path"))?;
let pwd_path = tmp_path.join(format!("pwd-{}", Uuid::new_v4()));
let pwd_path_str = pwd_path
.to_str()
.ok_or_else(|| anyhow!("invalid Unicode in zoxide pwd path"))?;
let hook_pwd = format!(
r#"
# PWD hooks in POSIX use a temporary file, located at `$_ZO_PWD_PATH`, to track
# changes in the current directory. These files are removed upon restart,
# but they should ideally also be cleaned up once the shell exits using traps.
#
# This can be done as follows:
#
# trap '_zoxide_cleanup' EXIT HUP KILL TERM
# trap '_zoxide_cleanup; trap - INT; kill -s INT "$$"' INT
# trap '_zoxide_cleanup; trap - QUIT; kill -s QUIT "$$"' QUIT
#
# By default, traps are not set up because they override all previous traps.
# It is therefore up to the user to add traps to their shell configuration.
_ZO_TMP_PATH={}
_ZO_PWD_PATH={}
_zoxide_cleanup() {{
rm -f "$_ZO_PWD_PATH"
}}
_zoxide_setpwd() {{
mkdir -p "$_ZO_TMP_PATH"
echo "$PWD" > "$_ZO_PWD_PATH"
}}
_zoxide_setpwd
_zoxide_hook() {{
_ZO_OLDPWD="$(cat "$_ZO_PWD_PATH")"
if [ -z "$_ZO_OLDPWD" ] || [ "$_ZO_OLDPWD" != "$PWD" ]; then
_zoxide_setpwd && zoxide add > /dev/null
fi
}}
case "$PS1" in
*\$\(_zoxide_hook\)*) ;;
*) PS1="\$(_zoxide_hook)${{PS1}}" ;;
esac"#,
quote(tmp_path_str),
quote(pwd_path_str),
);
Ok(Cow::Owned(hook_pwd))
}
fn quote(string: &str) -> String {
let mut quoted = String::with_capacity(string.len() + 2);
quoted.push('\'');
for ch in string.chars() {
match ch {
'\\' => quoted.push_str(r"\\"),
'\'' => quoted.push_str(r"'\''"),
_ => quoted.push(ch),
}
}
quoted.push('\'');
quoted
}

View File

@ -0,0 +1,36 @@
use super::{posix, HookConfig, ShellConfig};
use anyhow::Result;
use std::borrow::Cow;
pub const CONFIG: ShellConfig = ShellConfig {
z: posix::CONFIG.z,
alias: posix::CONFIG.alias,
hook: HookConfig {
prompt: HOOK_PROMPT,
pwd: hook_pwd,
},
};
const HOOK_PROMPT: &str = r#"
_zoxide_hook() {
zoxide add
}
[[ -n "${precmd_functions[(r)_zoxide_hook]}" ]] || {
precmd_functions+=(_zoxide_hook)
}
"#;
const fn hook_pwd() -> Result<Cow<'static, str>> {
const HOOK_PWD: &str = r#"
_zoxide_hook() {
zoxide add
}
chpwd_functions=(${chpwd_functions[@]} "_zoxide_hook")
"#;
Ok(Cow::Borrowed(HOOK_PWD))
}