diff --git a/CHANGELOG.md b/CHANGELOG.md index 22f0a17..3262602 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Added + +- `zoxide init` now defines `__zoxide_z*` functions that can be aliased as needed. + +### Changed + +- `zoxide init --no-aliases` no longer generates `z` or `zi`. + ## [0.4.3] - 2020-07-04 ### Fixed diff --git a/src/config.rs b/src/config.rs index b42b76f..902e96a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -53,6 +53,13 @@ pub fn zo_maxage() -> Result { } } +pub fn zo_echo() -> bool { + match env::var_os("_ZO_ECHO") { + Some(var) => var == "1", + None => false, + } +} + pub fn zo_resolve_symlinks() -> bool { match env::var_os("_ZO_RESOLVE_SYMLINKS") { Some(var) => var == "1", diff --git a/src/subcommand/init/bash.rs b/src/subcommand/init/bash.rs new file mode 100644 index 0000000..cd6044a --- /dev/null +++ b/src/subcommand/init/bash.rs @@ -0,0 +1,182 @@ +use anyhow::Result; + +use std::io::Write; + +use super::{Hook, Init}; +use crate::config; + +pub fn run(writer: &mut W, options: &Init) -> Result<()> { + const NOT_CONFIGURED: &str = "\ +# -- not configured --"; + + let __zoxide_pwd = if config::zo_resolve_symlinks() { + "\ +__zoxide_pwd() { + pwd -P +}" + } else { + "\ +__zoxide_pwd() { + pwd -L +}" + }; + + let __zoxide_cd = if config::zo_echo() { + "\ +__zoxide_cd() { + cd \"$@\" || return \"$?\" + __zoxide_pwd +}" + } else { + "\ +__zoxide_cd() { + cd \"$@\" || return \"$?\" +}" + }; + + let __zoxide_hook = match options.hook { + Hook::none => NOT_CONFIGURED, + Hook::prompt => { + "\ +__zoxide_hook() { + zoxide add \"$(__zoxide_pwd)\" +}" + } + Hook::pwd => { + "\ +__zoxide_hook() { + local -r __zoxide_pwd_tmp=\"$(__zoxide_pwd)\" + if [ -z \"$__zoxide_pwd_old\" ]; then + __zoxide_pwd_old=\"$__zoxide_pwd_tmp\" + elif [ \"$__zoxide_pwd_old\" != \"$__zoxide_pwd_tmp\" ]; then + __zoxide_pwd_old=\"$__zoxide_pwd_tmp\" + zoxide add \"$__zoxide_pwd_old\" + fi +}" + } + }; + + let hook_init = match options.hook { + Hook::none => NOT_CONFIGURED, + _ => { + "\ +case \"$PROMPT_COMMAND\" in + *__zoxide_hook*) ;; + *) PROMPT_COMMAND=\"${PROMPT_COMMAND:+${PROMPT_COMMAND};}__zoxide_hook\" ;; +esac" + } + }; + + let aliases = if options.no_aliases { + NOT_CONFIGURED.into() + } else { + format!( + "\ +alias {}='__zoxide_z' +alias {cmd}i='__zoxide_zi' +alias {cmd}a='__zoxide_za' + +alias {cmd}q='__zoxide_zq' +alias {cmd}qi='__zoxide_zqi' + +alias {cmd}r='__zoxide_zr' +alias {cmd}ri='__zoxide_zri'", + cmd = options.cmd + ) + }; + + write!( + writer, + "\ +# ============================================================================= +# +# Utility functions for zoxide. +# + +# pwd based on the value of _ZO_RESOLVE_SYMLINKS. +{__zoxide_pwd} + +# cd + custom logic based on the value of _ZO_ECHO. +{__zoxide_cd} + +# ============================================================================= +# +# Hook configuration for zoxide. +# + +# Hook to add new entries to the database. +{__zoxide_hook} + +# Initialize hook. +{hook_init} + +# ============================================================================= +# +# When using zoxide with --no-aliases, alias these internal functions as +# desired. +# + +# Jump to a directory using only keywords. +__zoxide_z() {{ + if [ \"$#\" -eq 0 ]; then + __zoxide_cd ~ + elif [ \"$#\" -eq 1 ] && [ \"$1\" = '-' ]; then + if [ -n \"$OLDPWD\" ]; then + __zoxide_cd \"$OLDPWD\" + else + echo \"zoxide: \\$OLDPWD is not set\" + return 1 + fi + else + local __zoxide_result + __zoxide_result=\"$(zoxide query -- \"$@\")\" && __zoxide_cd \"$__zoxide_result\" + fi +}} + +# Jump to a directory using interactive search. +__zoxide_zi() {{ + local __zoxide_result + __zoxide_result=\"$(zoxide query -i -- \"$@\")\" && __zoxide_cd \"$__zoxide_result\" +}} + +# Add a new entry to the database. +alias __zoxide_za='zoxide add' + +# Query an entry from the database using only keywords. +alias __zoxide_zq='zoxide query' + +# Query an entry from the database using interactive selection. +alias __zoxide_zqi='zoxide query -i' + +# Remove an entry from the database using the exact path. +alias __zoxide_zr='zoxide remove' + +# Remove an entry from the database using interactive selection. +__zoxide_zri() {{ + local __zoxide_result + __zoxide_result=\"$(zoxide query -i -- \"$@\")\" && zoxide remove \"$__zoxide_result\" +}} + +# ============================================================================= +# +# Convenient aliases for zoxide. Disable these using --no-aliases. +# + +{aliases} + +# ============================================================================= +# +# To initialize zoxide with bash, add the following line to your bash +# configuration file (usually ~/.bashrc): +# +# eval \"$(zoxide init bash)\" +", + __zoxide_pwd = __zoxide_pwd, + __zoxide_cd = __zoxide_cd, + __zoxide_hook = __zoxide_hook, + hook_init = hook_init, + aliases = aliases, + )?; + + Ok(()) +} diff --git a/src/subcommand/init/fish.rs b/src/subcommand/init/fish.rs new file mode 100644 index 0000000..c98fe8d --- /dev/null +++ b/src/subcommand/init/fish.rs @@ -0,0 +1,191 @@ +use anyhow::Result; + +use std::io::Write; + +use super::{Hook, Init}; +use crate::config; + +pub fn run(writer: &mut W, options: &Init) -> Result<()> { + const NOT_CONFIGURED: &str = "\ +# -- not configured --"; + + let __zoxide_pwd = if config::zo_resolve_symlinks() { + "\ +function __zoxide_pwd + pwd -P +end" + } else { + "\ +function __zoxide_pwd + pwd -L +end" + }; + + let __zoxide_cd = if config::zo_echo() { + "\ +function __zoxide_cd + cd $argv + or return $status + + commandline -f repaint + __zoxide_pwd +end" + } else { + "\ +function __zoxide_cd + cd $argv + or return $status + + commandline -f repaint +end" + }; + + let __zoxide_hook = "\ +function __zoxide_hook + zoxide add (__zoxide_pwd) +end"; + + let hook_init = match options.hook { + Hook::none => NOT_CONFIGURED, + Hook::prompt => { + "\ +function __zoxide_hook_prompt --on-event fish_prompt + __zoxide_hook +end" + } + Hook::pwd => { + "\ +function __zoxide_hook_pwd --on-variable PWD + __zoxide_hook +end" + } + }; + + let aliases = if options.no_aliases { + NOT_CONFIGURED.into() + } else { + format!( + "\ +function {cmd} + __zoxide_z $argv +end + +function {cmd}i + __zoxide_zi $argv +end + +function {cmd}a + __zoxide_za $argv +end + +function {cmd}q + __zoxide_zq $argv +end + +function {cmd}qi + __zoxide_zqi $argv +end + +function {cmd}r + __zoxide_zr $argv +end + +function {cmd}ri + __zoxide_zri $argv +end", + cmd = options.cmd + ) + }; + + writeln!( + writer, + "\ +# ============================================================================= +# +# Utility functions for zoxide. +# + +# pwd based on the value of _ZO_RESOLVE_SYMLINKS. +{__zoxide_pwd} + +# cd + custom logic based on the value of _ZO_ECHO. +{__zoxide_cd} + +# ============================================================================= +# +# Hook configuration for zoxide. +# + +# Hook to add new entries to the database. +{__zoxide_hook} + +# Initialize hook. +{hook_init} + +# ============================================================================= +# +# When using zoxide with --no-aliases, alias these internal functions as +# desired. +# + +# Jump to a directory using only keywords. +function __zoxide_z + set argc (count $argv) + + if test $argc -eq 0 + __zoxide_cd $HOME + else if begin; test $argc -eq 1; and test $argv[1] = '-'; end + __zoxide_cd - + else + set -l __zoxide_result (zoxide query -- $argv) + and __zoxide_cd $__zoxide_result + end +end + +# Jump to a directory using interactive search. +function __zoxide_zi + set -l __zoxide_result (zoxide query -i -- $argv) + and __zoxide_cd $__zoxide_result +end + +# Add a new entry to the database. +abbr -a __zoxide_za 'zoxide add' + +# Query an entry from the database using only keywords. +abbr -a __zoxide_zq 'zoxide query' + +# Query an entry from the database using interactive selection. +abbr -a __zoxide_zqi 'zoxide query -i' + +# Remove an entry from the database using the exact path. +abbr -a __zoxide_zr 'zoxide remove' + +# Remove an entry from the database using interactive selection. +function __zoxide_zri + set -l __zoxide_result (zoxide query -i -- $argv) + and zoxide remove $__zoxide_result +end + +# ============================================================================= +# +# Convenient aliases for zoxide. Disable these using --no-aliases. +# + +{aliases} + +# ============================================================================= +# +# To initialize zoxide with fish, add the following line to your fish +# configuration file (usually ~/.config/fish/config.fish): +# +# zoxide init fish | source +", + __zoxide_pwd = __zoxide_pwd, + __zoxide_cd = __zoxide_cd, + __zoxide_hook = __zoxide_hook, + hook_init = hook_init, + aliases = aliases, + )?; + + Ok(()) +} diff --git a/src/subcommand/init/mod.rs b/src/subcommand/init/mod.rs index 29d8d52..47414ed 100644 --- a/src/subcommand/init/mod.rs +++ b/src/subcommand/init/mod.rs @@ -1,10 +1,14 @@ -mod shell; +mod bash; +mod fish; +mod posix; +mod powershell; +mod zsh; -use anyhow::Result; +use anyhow::{Context, Result}; use clap::arg_enum; use structopt::StructOpt; -use std::io::{self, Write}; +use std::io; /// Generates shell configuration #[derive(Debug, StructOpt)] @@ -17,7 +21,7 @@ pub struct Init { #[structopt(long, alias = "z-cmd", default_value = "z")] cmd: String, - /// Prevents zoxide from defining any commands other than 'z' + /// Prevents zoxide from defining any commands #[structopt(long, alias = "no-define-aliases")] no_aliases: bool, @@ -33,35 +37,17 @@ pub struct Init { 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::powershell => shell::powershell::CONFIG, - Shell::zsh => shell::zsh::CONFIG, - }; - let stdout = io::stdout(); let mut handle = stdout.lock(); - let z = config.z; - writeln!(handle, "{}", z(&self.cmd)).unwrap(); - - if !self.no_aliases { - let alias = config.alias; - writeln!(handle, "{}", alias(&self.cmd)).unwrap(); + match self.shell { + Shell::bash => bash::run(&mut handle, self), + Shell::fish => fish::run(&mut handle, self), + Shell::posix => posix::run(&mut handle, self), + Shell::powershell => powershell::run(&mut handle, self), + Shell::zsh => zsh::run(&mut handle, self), } - - 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(()) + .context("could not initialize zoxide") } } diff --git a/src/subcommand/init/posix.rs b/src/subcommand/init/posix.rs new file mode 100644 index 0000000..bd4216d --- /dev/null +++ b/src/subcommand/init/posix.rs @@ -0,0 +1,232 @@ +use super::{Hook, Init}; +use crate::config; +use crate::util; + +use std::io::Write; + +use anyhow::Result; +use uuid::Uuid; + +pub fn run(writer: &mut W, options: &Init) -> Result<()> { + const NOT_CONFIGURED: &str = "\ +# -- not configured --"; + + let __zoxide_pwd = if config::zo_resolve_symlinks() { + "\ +__zoxide_pwd() { + pwd -P +}" + } else { + "\ +__zoxide_pwd() { + pwd -L +}" + }; + + let __zoxide_cd = if config::zo_echo() { + "\ +__zoxide_cd() { + cd \"$@\" || return \"$?\" + __zoxide_pwd +}" + } else { + "\ +__zoxide_cd() { + cd \"$@\" || return \"$?\" +}" + }; + + let __zoxide_hook = match options.hook { + Hook::none => NOT_CONFIGURED.into(), + Hook::prompt => "\ +__zoxide_hook() { + zoxide add \"$(__zoxide_pwd)\" +}" + .into(), + Hook::pwd => { + let mut tmp_path = std::env::temp_dir(); + tmp_path.push("zoxide"); + let tmp_path_str = util::path_to_str(&tmp_path)?; + + let pwd_path = tmp_path.join(format!("pwd-{}", Uuid::new_v4())); + let pwd_path_str = util::path_to_str(&pwd_path)?; + + format!( + "\ +# PWD hooks in POSIX use a temporary file, located at `$__zoxide_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. + +__zoxide_tmp_path={tmp_path} +__zoxide_pwd_path={pwd_path} + +__zoxide_cleanup() {{ + rm -f \"$__zoxide_pwd_path\" +}} + +__zoxide_setpwd() {{ + mkdir -p \"$__zoxide_tmp_path\" + echo \"$PWD\" > \"$__zoxide_pwd_path\" +}} + +__zoxide_setpwd + +__zoxide_hook() {{ + _ZO_OLDPWD=\"$(cat \"$__zoxide_pwd_path\")\" + if [ -z \"$_ZO_OLDPWD\" ] || [ \"$_ZO_OLDPWD\" != \"$PWD\" ]; then + __zoxide_setpwd && zoxide add \"$(pwd -L)\" > /dev/null + fi +}}", + tmp_path = posix_quote(tmp_path_str), + pwd_path = posix_quote(pwd_path_str), + ) + } + }; + + let hook_init = match options.hook { + Hook::none => NOT_CONFIGURED, + _ => { + "\ +case \"$PS1\" in + *\\$\\(__zoxide_hook\\)*) ;; + *) PS1=\"${PS1}\\$(__zoxide_hook)\" ;; +esac" + } + }; + + let aliases = if options.no_aliases { + NOT_CONFIGURED.into() + } else { + format!( + "\ +alias {cmd}='__zoxide_z' +alias {cmd}i='__zoxide_zi' + +alias {cmd}a='__zoxide_za' + +alias {cmd}q='__zoxide_zq' +alias {cmd}qi='__zoxide_zqi' + +alias {cmd}r='__zoxide_zr' +alias {cmd}ri='__zoxide_zri'", + cmd = options.cmd + ) + }; + + writeln!( + writer, + "\ +# ============================================================================= +# +# Utility functions for zoxide. +# + +# pwd based on the value of _ZO_RESOLVE_SYMLINKS. +{__zoxide_pwd} + +# cd + custom logic based on the value of _ZO_ECHO. +{__zoxide_cd} + +# ============================================================================= +# +# Hook configuration for zoxide. +# + +# Hook to add new entries to the database. +{__zoxide_hook} + +# Initialize hook. +{hook_init} + +# ============================================================================= +# +# When using zoxide with --no-aliases, alias these internal functions as +# desired. +# + +# Jump to a directory using only keywords. +__zoxide_z() {{ + if [ \"$#\" -eq 0 ]; then + __zoxide_cd ~ + elif [ \"$#\" -eq 1 ] && [ \"$1\" = '-' ]; then + if [ -n \"$OLDPWD\" ]; then + __zoxide_cd \"$OLDPWD\" + else + echo \"zoxide: \\$OLDPWD is not set\" + return 1 + fi + else + __zoxide_result=\"$(zoxide query -- \"$@\")\" && __zoxide_cd \"$__zoxide_result\" + fi +}} + +# Jump to a directory using interactive search. +__zoxide_zi() {{ + __zoxide_result=\"$(zoxide query -i -- \"$@\")\" && __zoxide_cd \"$__zoxide_result\" +}} + +# Add a new entry to the database. +alias __zoxide_za='zoxide add' + +# Query an entry from the database using only keywords. +alias __zoxide_zq='zoxide query' + +# Query an entry from the database using interactive selection. +alias __zoxide_zqi='zoxide query -i' + +# Remove an entry from the database using the exact path. +alias __zoxide_zr='zoxide remove' + +# Remove an entry from the database using interactive selection. +__zoxide_zri() {{ + __zoxide_result=\"$(zoxide query -i -- \"$@\")\" && zoxide remove \"$__zoxide_result\" +}} + +# ============================================================================= +# +# Convenient aliases for zoxide. Disable these using --no-aliases. +# + +{aliases} + +# ============================================================================= +# +# To initialize zoxide with your POSIX shell, add the following line to your +# shell configuration file: +# +# eval \"$(zoxide init posix --prompt hook)\" +", + __zoxide_pwd = __zoxide_pwd, + __zoxide_cd = __zoxide_cd, + __zoxide_hook = __zoxide_hook, + hook_init = hook_init, + aliases = aliases, + )?; + + Ok(()) +} + +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 +} diff --git a/src/subcommand/init/powershell.rs b/src/subcommand/init/powershell.rs new file mode 100644 index 0000000..48d1f50 --- /dev/null +++ b/src/subcommand/init/powershell.rs @@ -0,0 +1,180 @@ +use anyhow::Result; + +use std::io::Write; + +use super::{Hook, Init}; +use crate::config; + +pub fn run(writer: &mut W, options: &Init) -> Result<()> { + const NOT_CONFIGURED: &str = "\ +# -- not configured --"; + + let __zoxide_pwd = "\ +function __zoxide_pwd { + $(Get-Location).Path +}"; + + let __zoxide_cd = if config::zo_echo() { + "\ +function __zoxide_cd($dir) { + Set-Location $dir -ea Stop + __zoxide_pwd +}" + } else { + "\ +function __zoxide_cd($dir) { + Set-Location $dir -ea Stop +}" + }; + + let __zoxide_hook = "\ +function __zoxide_hook { + zoxide add $(__zoxide_pwd) +}"; + + let hook_init = match options.hook { + Hook::none => NOT_CONFIGURED, + Hook::prompt => { + "\ +$PreZoxidePrompt = $function:prompt +function prompt { + $null = __zoxide_hook + & $PreZoxidePrompt +}" + } + Hook::pwd => { + "\ +if ($PSVersionTable.PSVersion.Major -ge 6) { + $ExecutionContext.InvokeCommand.LocationChangedAction = { + $null = __zoxide_hook + } +} else { + Write-Error \"zoxide: PWD hooks are not supported below PowerShell 6, use 'zoxide init powershell --hook prompt' instead.\" +}" + } + }; + + let aliases = if options.no_aliases { + NOT_CONFIGURED.into() + } else { + format!( + "\ +Set-Alias {cmd} __zoxide_z +Set-Alias {cmd}i __zoxide_zi + +Set-Alias {cmd}a __zoxide_za + +Set-Alias {cmd}q __zoxide_zq +Set-Alias {cmd}qi __zoxide_zqi + +Set-Alias {cmd}r __zoxide_zr +Set-Alias {cmd}ri __zoxide_zri", + cmd = options.cmd + ) + }; + + writeln!( + writer, + "\ +# ============================================================================= +# +# Utility functions for zoxide. +# + +# pwd based on the value of _ZO_RESOLVE_SYMLINKS. +{__zoxide_pwd} + +# cd + custom logic based on the value of _ZO_ECHO. +{__zoxide_cd} + +# ============================================================================= +# +# Hook configuration for zoxide. +# + +# Hook to add new entries to the database. +{__zoxide_hook} + +# Initialize hook. +{hook_init} + +# ============================================================================= +# +# When using zoxide with --no-aliases, alias these internal functions as +# desired. +# + +# Jump to a directory using only keywords. +function __zoxide_z {{ + if ($args.Length -eq 0) {{ + __zoxide_cd ~ + }} + elseif ($args.Length -eq 1 -and $args[0] -eq '-') {{ + __zoxide_cd - + }} + else {{ + $__zoxide_result = zoxide query -- @args + if ($LASTEXITCODE -eq 0) {{ + __zoxide_cd $__zoxide_result + }} + }} +}} + +# Jump to a directory using interactive search. +function zi {{ + $__zoxide_result = zoxide query -i -- @args + if ($LASTEXITCODE -eq 0) {{ + __zoxide_cd $__zoxide_result + }} +}} + +# Add a new entry to the database. +function __zoxide_za {{ zoxide add @args }} + +# Query an entry from the database using only keywords. +function __zoxide_zq {{ zoxide query @args }} + +# Query an entry from the database using interactive selection. +function __zoxide_zqi {{ zoxide query -i @args }} + +# Remove an entry from the database using the exact path. +function __zoxide_zr {{ zoxide remove @args }} + +# Remove an entry from the database using interactive selection. +function __zoxide_zri {{ + $_zoxide_result = zoxide query -i -- @args + if ($LASTEXITCODE -eq 0) {{ + zoxide remove $_zoxide_result + }} +}} + +# ============================================================================= +# +# Convenient aliases for zoxide. Disable these using --no-aliases. +# + +{aliases} + +# ============================================================================= +# +# To initialize zoxide with PowerShell, add the following line to your +# PowerShell configuration file (the location is stored in $profile): +# +# Invoke-Expression (& {{ +# $hook = if ($PSVersionTable.PSVersion.Major -ge 6) {{ +# 'pwd' +# }} else {{ +# 'prompt' +# }} +# (zoxide init powershell --hook $hook) -join \"`n\" +# }}) +", + __zoxide_pwd = __zoxide_pwd, + __zoxide_cd = __zoxide_cd, + __zoxide_hook = __zoxide_hook, + hook_init = hook_init, + aliases = aliases, + )?; + + Ok(()) +} diff --git a/src/subcommand/init/shell/bash.rs b/src/subcommand/init/shell/bash.rs deleted file mode 100644 index fcd927a..0000000 --- a/src/subcommand/init/shell/bash.rs +++ /dev/null @@ -1,45 +0,0 @@ -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 "$(pwd -L)" -} - -case "$PROMPT_COMMAND" in - *_zoxide_hook*) ;; - *) PROMPT_COMMAND="${PROMPT_COMMAND:+${PROMPT_COMMAND};}_zoxide_hook" ;; -esac -"#; - -const fn hook_pwd() -> Result> { - 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 "$(pwd -L)" - fi -} - -case "$PROMPT_COMMAND" in - *_zoxide_hook*) ;; - *) PROMPT_COMMAND="${PROMPT_COMMAND:+${PROMPT_COMMAND};}_zoxide_hook" ;; -esac -"#; - - Ok(Cow::Borrowed(HOOK_PWD)) -} diff --git a/src/subcommand/init/shell/fish.rs b/src/subcommand/init/shell/fish.rs deleted file mode 100644 index 15098f9..0000000 --- a/src/subcommand/init/shell/fish.rs +++ /dev/null @@ -1,84 +0,0 @@ -use super::{HookConfig, ShellConfig}; - -use anyhow::Result; - -use std::borrow::Cow; - -pub const CONFIG: ShellConfig = ShellConfig { - z, - alias, - hook: HookConfig { - prompt: HOOK_PROMPT, - pwd: hook_pwd, - }, -}; - -fn 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 {0} - set argc (count $argv) - - if test $argc -eq 0 - _z_cd $HOME - else if begin; test $argc -eq 1; and test $argv[1] = '-'; end - _z_cd - - else - set -l _zoxide_result (zoxide query -- $argv) - and _z_cd $_zoxide_result - end -end - -function {0}i - set -l _zoxide_result (zoxide query -i -- $argv) - and _z_cd $_zoxide_result -end -"#, - cmd - ) -} - -fn alias(cmd: &str) -> String { - format!( - r#" -abbr -a {0}a 'zoxide add' - -abbr -a {0}q 'zoxide query' -abbr -a {0}qi 'zoxide query -i' - -abbr -a {0}r 'zoxide remove' -function {0}ri - set -l _zoxide_result (zoxide query -i -- $argv) - and zoxide remove $_zoxide_result -end -"#, - cmd - ) -} - -const HOOK_PROMPT: &str = r#" -function _zoxide_hook --on-event fish_prompt - zoxide add (pwd -L) -end -"#; - -const fn hook_pwd() -> Result> { - const HOOK_PWD: &str = r#" -function _zoxide_hook --on-variable PWD - zoxide add (pwd -L) -end -"#; - - Ok(Cow::Borrowed(HOOK_PWD)) -} diff --git a/src/subcommand/init/shell/mod.rs b/src/subcommand/init/shell/mod.rs deleted file mode 100644 index 45413e3..0000000 --- a/src/subcommand/init/shell/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -pub mod bash; -pub mod fish; -pub mod posix; -pub mod powershell; -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>, -} diff --git a/src/subcommand/init/shell/posix.rs b/src/subcommand/init/shell/posix.rs deleted file mode 100644 index 6058af7..0000000 --- a/src/subcommand/init/shell/posix.rs +++ /dev/null @@ -1,149 +0,0 @@ -use super::{HookConfig, ShellConfig}; -use crate::util; - -use 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(cmd: &str) -> String { - format!( - r#" -_z_cd() {{ - cd "$@" || return "$?" - - if [ "$_ZO_ECHO" = "1" ]; then - echo "$PWD" - fi -}} - -{0}() {{ - if [ "$#" -eq 0 ]; then - _z_cd ~ - elif [ "$#" -eq 1 ] && [ "$1" = '-' ]; then - if [ -n "$OLDPWD" ]; then - _z_cd "$OLDPWD" - else - echo 'zoxide: $OLDPWD is not set' - return 1 - fi - else - _zoxide_result="$(zoxide query -- "$@")" && _z_cd "$_zoxide_result" - fi -}} - -{0}i() {{ - _zoxide_result="$(zoxide query -i -- "$@")" && _z_cd "$_zoxide_result" -}} -"#, - cmd - ) -} - -fn alias(cmd: &str) -> String { - format!( - r#" -alias {0}a='zoxide add' - -alias {0}q='zoxide query' -alias {0}qi='zoxide query -i' - -alias {0}r='zoxide remove' -{0}ri() {{ - _zoxide_result="$(zoxide query -i -- "$@")" && zoxide remove "$_zoxide_result" -}} -"#, - cmd - ) -} - -const HOOK_PROMPT: &str = r#" -_zoxide_hook() { - zoxide add "$(pwd -L)" -} - -case "$PS1" in - *\$\(_zoxide_hook\)*) ;; - *) PS1="${PS1}\$(_zoxide_hook)" ;; -esac -"#; - -fn hook_pwd() -> Result> { - let mut tmp_path = std::env::temp_dir(); - tmp_path.push("zoxide"); - let tmp_path_str = util::path_to_str(&tmp_path)?; - - let pwd_path = tmp_path.join(format!("pwd-{}", Uuid::new_v4())); - let pwd_path_str = util::path_to_str(&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 "$(pwd -L)" > /dev/null - fi -}} - -case "$PS1" in - *\$\(_zoxide_hook\)*) ;; - *) PS1="${{PS1}}\$(_zoxide_hook)" ;; -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 -} diff --git a/src/subcommand/init/shell/powershell.rs b/src/subcommand/init/shell/powershell.rs deleted file mode 100644 index 4b741ed..0000000 --- a/src/subcommand/init/shell/powershell.rs +++ /dev/null @@ -1,92 +0,0 @@ -use super::{HookConfig, ShellConfig}; - -use anyhow::Result; - -use std::borrow::Cow; - -pub const CONFIG: ShellConfig = ShellConfig { - z, - alias, - hook: HookConfig { - prompt: HOOK_PROMPT, - pwd: hook_pwd, - }, -}; - -fn z(cmd: &str) -> String { - format!( - r#" -function _z_cd($dir) {{ - Set-Location $dir -ea Stop - if ($env:_ZO_ECHO -eq "1") {{ - Write-Host "$PWD" - }} -}} - -function {0} {{ - if ($args.Length -eq 0) {{ - _z_cd ~ - }} - elseif ($args.Length -eq 1 -and $args[0] -eq '-') {{ - _z_cd - - }} - else {{ - $_zoxide_result = zoxide query -- @args - if ($LASTEXITCODE -eq 0) {{ - _z_cd $_zoxide_result - }} - }} -}} - -function {0}i {{ - $_zoxide_result = zoxide query -i -- @args - if ($LASTEXITCODE -eq 0) {{ - _z_cd $_zoxide_result - }} -}} -"#, - cmd - ) -} - -fn alias(cmd: &str) -> String { - format!( - r#" -function {0}a {{ zoxide add @args }} - -function {0}q {{ zoxide query @args }} -function {0}qi {{ zoxide query -i @args }} - -function {0}r {{ zoxide remove @args }} -function {0}ri {{ - $_zoxide_result = zoxide query -i -- @args - if ($LASTEXITCODE -eq 0) {{ - zoxide remove $_zoxide_result - }} -}} -"#, - cmd - ) -} - -const HOOK_PROMPT: &str = r#" -$PreZoxidePrompt = $function:prompt -function prompt { - $null = zoxide add $(Get-Location) - & $PreZoxidePrompt -} -"#; - -const fn hook_pwd() -> Result> { - const HOOK_PWD: &str = r#" -if ($PSVersionTable.PSVersion.Major -ge 6) { - $ExecutionContext.InvokeCommand.LocationChangedAction = { - $null = zoxide add $(Get-Location) - } -} else { - Write-Error "pwd hook requires pwsh - use 'zoxide init powershell --hook prompt'" -} -"#; - - Ok(Cow::Borrowed(HOOK_PWD)) -} diff --git a/src/subcommand/init/shell/zsh.rs b/src/subcommand/init/shell/zsh.rs deleted file mode 100644 index bc171a5..0000000 --- a/src/subcommand/init/shell/zsh.rs +++ /dev/null @@ -1,36 +0,0 @@ -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 "$(pwd -L)" -} - -[[ -n "${precmd_functions[(r)_zoxide_hook]}" ]] || { - precmd_functions+=(_zoxide_hook) -} -"#; - -const fn hook_pwd() -> Result> { - const HOOK_PWD: &str = r#" -_zoxide_hook() { - zoxide add "$(pwd -L)" -} - -chpwd_functions=(${chpwd_functions[@]} "_zoxide_hook") -"#; - - Ok(Cow::Borrowed(HOOK_PWD)) -} diff --git a/src/subcommand/init/zsh.rs b/src/subcommand/init/zsh.rs new file mode 100644 index 0000000..152f0f2 --- /dev/null +++ b/src/subcommand/init/zsh.rs @@ -0,0 +1,174 @@ +use anyhow::Result; + +use std::io::Write; + +use super::{Hook, Init}; +use crate::config; + +pub fn run(writer: &mut W, options: &Init) -> Result<()> { + const NOT_CONFIGURED: &str = "\ +# -- not configured --"; + + let __zoxide_pwd = if config::zo_resolve_symlinks() { + "\ +__zoxide_pwd() { + pwd -P +}" + } else { + "\ +__zoxide_pwd() { + pwd -L +}" + }; + + let __zoxide_cd = if config::zo_echo() { + "\ +__zoxide_cd() { + cd \"$@\" || return \"$?\" + __zoxide_pwd +}" + } else { + "\ +__zoxide_cd() { + cd \"$@\" || return \"$?\" +}" + }; + + let __zoxide_hook = match options.hook { + Hook::none => NOT_CONFIGURED, + _ => { + "\ +__zoxide_hook() { + zoxide add \"$(__zoxide_pwd)\" +}" + } + }; + + let hook_init = match options.hook { + Hook::none => NOT_CONFIGURED, + Hook::prompt => { + "\ +[[ -n \"${precmd_functions[(r)__zoxide_hook]}\" ]] || { + precmd_functions+=(__zoxide_hook) +}" + } + Hook::pwd => { + "\ +chpwd_functions=(${chpwd_functions[@]} \"__zoxide_hook\")" + } + }; + + let aliases = if options.no_aliases { + NOT_CONFIGURED.into() + } else { + format!( + "\ +alias {cmd}='__zoxide_z' +alias {cmd}i='__zoxide_zi' + +alias {cmd}a='__zoxide_za' + +alias {cmd}q='__zoxide_zq' +alias {cmd}qi='__zoxide_zqi' + +alias {cmd}r='__zoxide_zr' +alias {cmd}ri='__zoxide_zri'", + cmd = options.cmd + ) + }; + + write!( + writer, + "\ +# ============================================================================= +# +# Utility functions for zoxide. +# + +# pwd based on the value of _ZO_RESOLVE_SYMLINKS. +{__zoxide_pwd} + +# cd + custom logic based on the value of _ZO_ECHO. +{__zoxide_cd} + +# ============================================================================= +# +# Hook configuration for zoxide. +# + +# Hook to add new entries to the database. +{__zoxide_hook} + +# Initialize hook. +{hook_init} + +# ============================================================================= +# +# When using zoxide with --no-aliases, alias these internal functions as +# desired. +# + +# Jump to a directory using only keywords. +__zoxide_z() {{ + if [ \"$#\" -eq 0 ]; then + __zoxide_cd ~ + elif [ \"$#\" -eq 1 ] && [ \"$1\" = '-' ]; then + if [ -n \"$OLDPWD\" ]; then + __zoxide_cd \"$OLDPWD\" + else + echo \"zoxide: \\$OLDPWD is not set\" + return 1 + fi + else + local __zoxide_result + __zoxide_result=\"$(zoxide query -- \"$@\")\" && __zoxide_cd \"$__zoxide_result\" + fi +}} + +# Jump to a directory using interactive search. +__zoxide_zi() {{ + local __zoxide_result + __zoxide_result=\"$(zoxide query -i -- \"$@\")\" && __zoxide_cd \"$__zoxide_result\" +}} + +# Add a new entry to the database. +alias __zoxide_za='zoxide add' + +# Query an entry from the database using only keywords. +alias __zoxide_zq='zoxide query' + +# Query an entry from the database using interactive selection. +alias __zoxide_zqi='zoxide query -i' + +# Remove an entry from the database using the exact path. +alias __zoxide_zr='zoxide remove' + +# Remove an entry from the database using interactive selection. +__zoxide_zri() {{ + local __zoxide_result + __zoxide_result=\"$(zoxide query -i -- \"$@\")\" && zoxide remove \"$__zoxide_result\" +}} + +# ============================================================================= +# +# Convenient aliases for zoxide. Disable these using --no-aliases. +# + +{aliases} + +# ============================================================================= +# +# To initialize zoxide with zsh, add the following line to your zsh +# configuration file (usually ~/.zshrc): +# +# eval \"$(zoxide init zsh)\" +", + __zoxide_pwd = __zoxide_pwd, + __zoxide_cd = __zoxide_cd, + __zoxide_hook = __zoxide_hook, + hook_init = hook_init, + aliases = aliases, + )?; + + Ok(()) +}