Add z completions for zsh (#309)

This commit is contained in:
Ajeet D'Souza 2021-12-05 14:28:31 +05:30
parent 72fd48ed97
commit 58430d8c54
14 changed files with 128 additions and 74 deletions

View File

@ -10,7 +10,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest, windows-latest] # FIXME: macos-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -28,5 +28,5 @@ jobs:
- run: cargo xtask ci - run: cargo xtask ci
if: ${{ matrix.os == 'windows-latest' }} if: ${{ matrix.os == 'windows-latest' }}
- run: nix-shell --cores 0 --pure --run 'cargo xtask ci' - run: nix-shell --cores 0 --pure --run 'rm -rf ~/.cargo/bin; cargo xtask ci'
if: ${{ matrix.os != 'windows-latest' }} if: ${{ matrix.os != 'windows-latest' }}

View File

@ -9,16 +9,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased ## Unreleased
### Added
- Zsh: completions for `z` command.
### Changed ### Changed
- fzf: better default options. - Fzf: better default options.
- fish: interactive completions are only triggered when the last argument is empty. - Fish: interactive completions are only triggered when the last argument is empty.
### Fixed ### Fixed
- PowerShell: use global scope for aliases. - PowerShell: use global scope for aliases.
- Zsh: fix errors with `set -eu`. - Zsh: fix errors with `set -eu`.
- fzf: handle early selection. - Fzf: handle early selection.
## [0.7.9] - 2021-11-02 ## [0.7.9] - 2021-11-02

View File

@ -2,7 +2,7 @@
authors = ["Ajeet D'Souza <98ajeet@gmail.com>"] authors = ["Ajeet D'Souza <98ajeet@gmail.com>"]
categories = ["command-line-utilities", "filesystem"] categories = ["command-line-utilities", "filesystem"]
description = "A smarter cd command for your terminal" description = "A smarter cd command for your terminal"
edition = "2018" edition = "2021"
keywords = ["cli"] keywords = ["cli"]
license = "MIT" license = "MIT"
name = "zoxide" name = "zoxide"

View File

@ -33,15 +33,17 @@ zoxide works on all major shells.
![Tutorial][tutorial] ![Tutorial][tutorial]
```sh ```sh
z foo # cd into highest ranked directory matching foo z foo # cd into highest ranked directory matching foo
z foo bar # cd into highest ranked directory matching foo and bar z foo bar # cd into highest ranked directory matching foo and bar
z ~/foo # z also works like a regular cd command z ~/foo # z also works like a regular cd command
z foo/ # cd into relative path z foo/ # cd into relative path
z .. # cd one level up z .. # cd one level up
z - # cd into previous directory z - # cd into previous directory
zi foo # cd with interactive selection (using fzf) zi foo # cd with interactive selection (using fzf)
z foo<SPACE><TAB> # show interactive completions (zoxide v0.7.10+, bash/fish/zsh only)
``` ```
Read more about the matching algorithm [here][algorithm-matching]. Read more about the matching algorithm [here][algorithm-matching].
@ -231,6 +233,8 @@ Add this to your configuration (usually `~/.zshrc`):
eval "$(zoxide init zsh)" eval "$(zoxide init zsh)"
``` ```
For completions to work, this line must be added _after_ calling `compinit`.
</details> </details>
<details> <details>
@ -347,16 +351,16 @@ They must be set before `zoxide init` is called.
[algorithm-matching]: https://github.com/ajeetdsouza/zoxide/wiki/Algorithm#matching [algorithm-matching]: https://github.com/ajeetdsouza/zoxide/wiki/Algorithm#matching
[alpine linux packages]: https://pkgs.alpinelinux.org/packages?name=zoxide [alpine linux packages]: https://pkgs.alpinelinux.org/packages?name=zoxide
[arch linux community]: https://archlinux.org/packages/community/x86_64/zoxide/ [arch linux community]: https://archlinux.org/packages/community/x86_64/zoxide/
[builtwithnix-badge]: https://img.shields.io/badge/builtwith-nix-7d81f7 [builtwithnix-badge]: https://img.shields.io/badge/builtwith-nix-7d81f7?style=flat-square
[builtwithnix]: https://builtwithnix.org/ [builtwithnix]: https://builtwithnix.org/
[chocolatey]: https://community.chocolatey.org/packages/zoxide [chocolatey]: https://community.chocolatey.org/packages/zoxide
[conda-forge]: https://anaconda.org/conda-forge/zoxide [conda-forge]: https://anaconda.org/conda-forge/zoxide
[copr]: https://copr.fedorainfracloud.org/coprs/atim/zoxide/ [copr]: https://copr.fedorainfracloud.org/coprs/atim/zoxide/
[crates.io-badge]: https://img.shields.io/crates/v/zoxide [crates.io-badge]: https://img.shields.io/crates/v/zoxide?style=flat-square
[crates.io]: https://crates.io/crates/zoxide [crates.io]: https://crates.io/crates/zoxide
[debian packages]: https://packages.debian.org/stable/admin/zoxide [debian packages]: https://packages.debian.org/stable/admin/zoxide
[devuan packages]: https://pkginfo.devuan.org/cgi-bin/package-query.html?c=package&q=zoxide [devuan packages]: https://pkginfo.devuan.org/cgi-bin/package-query.html?c=package&q=zoxide
[downloads-badge]: https://img.shields.io/github/downloads/ajeetdsouza/zoxide/total [downloads-badge]: https://img.shields.io/github/downloads/ajeetdsouza/zoxide/total?style=flat-square
[dports]: https://github.com/DragonFlyBSD/DPorts/tree/master/sysutils/zoxide [dports]: https://github.com/DragonFlyBSD/DPorts/tree/master/sysutils/zoxide
[emacs]: https://www.gnu.org/software/emacs/ [emacs]: https://www.gnu.org/software/emacs/
[fedora packages]: https://src.fedoraproject.org/rpms/rust-zoxide [fedora packages]: https://src.fedoraproject.org/rpms/rust-zoxide

View File

@ -1,41 +1,41 @@
let let
rust = import (builtins.fetchTarball rust = import (builtins.fetchTarball
"https://github.com/oxalica/rust-overlay/archive/ad311f5bb5c5ef475985f1e0f264e831470a8510.tar.gz"); "https://github.com/oxalica/rust-overlay/archive/783722a22ee5d762ac5c1c7b418b57b3010c827a.tar.gz");
pkgs = import <nixpkgs> { overlays = [ rust ]; }; pkgs = import (builtins.fetchTarball
pkgs-latest = import (builtins.fetchTarball "https://github.com/NixOS/nixpkgs/archive/58f87c20e1abbbe835f1f3106ecea10fd93c4a90.tar.gz") {
"https://github.com/NixOS/nixpkgs/archive/3ef1d2a9602c18f8742e1fb63d5ae9867092e3d6.tar.gz") overlays = [ rust ];
{ }; };
in pkgs.mkShell { in pkgs.mkShell {
buildInputs = [ buildInputs = [
# Rust # Rust
pkgs.rust-bin.stable.latest.default pkgs.rust-bin.stable.latest.default
# Shells # Shells
pkgs-latest.elvish
pkgs-latest.fish
pkgs-latest.nushell
pkgs-latest.xonsh
pkgs.bash pkgs.bash
pkgs.dash pkgs.dash
pkgs.elvish
pkgs.fish
pkgs.nushell
pkgs.powershell pkgs.powershell
pkgs.xonsh
pkgs.zsh pkgs.zsh
# Tools # Tools
pkgs-latest.cargo-audit pkgs.cargo-audit
pkgs-latest.mandoc pkgs.mandoc
pkgs-latest.nixfmt pkgs.nixfmt
pkgs-latest.nodePackages.markdownlint-cli pkgs.nodePackages.markdownlint-cli
pkgs-latest.python3Packages.black pkgs.python3Packages.black
pkgs-latest.python3Packages.mypy pkgs.python3Packages.mypy
pkgs-latest.python3Packages.pylint pkgs.python3Packages.pylint
pkgs-latest.shellcheck pkgs.shellcheck
pkgs-latest.shfmt pkgs.shfmt
# Dependencies # Dependencies
pkgs.cacert pkgs.cacert
pkgs.libiconv
pkgs.fzf pkgs.fzf
pkgs.git pkgs.git
pkgs.libiconv
]; ];
RUST_BACKTRACE = 1; RUST_BACKTRACE = 1;

View File

@ -32,16 +32,17 @@ impl Query {
if self.interactive { if self.interactive {
let mut fzf = Fzf::new(false)?; let mut fzf = Fzf::new(false)?;
let stdin = fzf.stdin();
let selection = loop { let selection = loop {
let dir = match stream.next() { let dir = match stream.next() {
Some(dir) => dir, Some(dir) => dir,
None => break fzf.select()?, None => break fzf.select()?,
}; };
match writeln!(fzf.stdin(), "{}", dir.display_score(now)) { match writeln!(stdin, "{}", dir.display_score(now)) {
Ok(()) => (()),
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => break fzf.select()?, Err(e) if e.kind() == io::ErrorKind::BrokenPipe => break fzf.select()?,
Err(e) => Err(e).context("could not write to fzf")?, result => result.context("could not write to fzf")?,
} }
}; };

View File

@ -19,16 +19,17 @@ impl Run for Remove {
let mut stream = db.stream(now).with_keywords(keywords); let mut stream = db.stream(now).with_keywords(keywords);
let mut fzf = Fzf::new(true)?; let mut fzf = Fzf::new(true)?;
let stdin = fzf.stdin();
let selection = loop { let selection = loop {
let dir = match stream.next() { let dir = match stream.next() {
Some(dir) => dir, Some(dir) => dir,
None => break fzf.select()?, None => break fzf.select()?,
}; };
match writeln!(fzf.stdin(), "{}", dir.display_score(now)) { match writeln!(stdin, "{}", dir.display_score(now)) {
Ok(()) => (()),
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => break fzf.select()?, Err(e) if e.kind() == io::ErrorKind::BrokenPipe => break fzf.select()?,
Err(e) => Err(e).context("could not write to fzf")?, result => result.context("could not write to fzf")?,
} }
}; };

View File

@ -3,7 +3,6 @@ use std::ffi::OsString;
use std::path::PathBuf; use std::path::PathBuf;
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use dirs;
use glob::Pattern; use glob::Pattern;
use crate::db::Rank; use crate::db::Rank;

View File

@ -24,7 +24,7 @@ impl Fzf {
command.args(&[ command.args(&[
"--bind=ctrl-z:ignore", "--bind=ctrl-z:ignore",
"--exit-0", "--exit-0",
"--height=35%", "--height=40%",
"--inline-info", "--inline-info",
"--no-sort", "--no-sort",
"--reverse", "--reverse",

View File

@ -32,7 +32,7 @@ function __zoxide_cd() {
{%- if hook == InitHook::Prompt %} {%- if hook == InitHook::Prompt %}
function __zoxide_hook() { function __zoxide_hook() {
\builtin local -r retval="$?" \builtin local -r retval="$?"
\builtin command zoxide add -- "$(__zoxide_pwd)" \command zoxide add -- "$(__zoxide_pwd || \builtin true)"
return "${retval}" return "${retval}"
} }
{%- else if hook == InitHook::Pwd %} {%- else if hook == InitHook::Pwd %}
@ -40,10 +40,11 @@ __zoxide_oldpwd="$(__zoxide_pwd)"
function __zoxide_hook() { function __zoxide_hook() {
\builtin local -r retval="$?" \builtin local -r retval="$?"
\builtin local -r pwd_tmp="$(__zoxide_pwd)" \builtin local pwd_tmp
pwd_tmp="$(__zoxide_pwd)"
if [[ ${__zoxide_oldpwd} != "${pwd_tmp}" ]]; then if [[ ${__zoxide_oldpwd} != "${pwd_tmp}" ]]; then
__zoxide_oldpwd="${pwd_tmp}" __zoxide_oldpwd="${pwd_tmp}"
\builtin command zoxide add -- "${__zoxide_oldpwd}" \command zoxide add -- "${__zoxide_oldpwd}"
fi fi
return "${retval}" return "${retval}"
} }
@ -76,14 +77,15 @@ function __zoxide_z() {
__zoxide_cd "${result:2}" __zoxide_cd "${result:2}"
else else
\builtin local result \builtin local result
result="$(\builtin command zoxide query --exclude "$(__zoxide_pwd)" -- "$@")" && __zoxide_cd "${result}" result="$(\command zoxide query --exclude "$(__zoxide_pwd || \builtin true)" -- "$@")" &&
__zoxide_cd "${result}"
fi fi
} }
# Jump to a directory using interactive search. # Jump to a directory using interactive search.
function __zoxide_zi() { function __zoxide_zi() {
\builtin local result \builtin local result
result="$(\builtin command zoxide query -i -- "$@")" && __zoxide_cd "${result}" result="$(\command zoxide query -i -- "$@")" && __zoxide_cd "${result}"
} }
{{ section }} {{ section }}
@ -123,11 +125,12 @@ if [[ :"${SHELLOPTS}": =~ :(vi|emacs): && ${TERM} != 'dumb' ]]; then
# If there is only one argument, use `cd` completions. # If there is only one argument, use `cd` completions.
if [[ {{ "${#COMP_WORDS[@]}" }} -eq 2 ]]; then if [[ {{ "${#COMP_WORDS[@]}" }} -eq 2 ]]; then
\builtin mapfile -t COMPREPLY < <(compgen -A directory -S / -- "${COMP_WORDS[-1]}") \builtin mapfile -t COMPREPLY < \
<(\builtin compgen -A directory -S / -- "${COMP_WORDS[-1]}" || \builtin true)
# If there is a space after the last word, use interactive selection. # If there is a space after the last word, use interactive selection.
elif [[ -z ${COMP_WORDS[-1]} ]]; then elif [[ -z ${COMP_WORDS[-1]} ]]; then
\local result \builtin local result
result="$(\builtin command zoxide query -i -- "${COMP_WORDS[@]:1}")" && result="$(\command zoxide query -i -- "{{ "${COMP_WORDS[@]:1:${#COMP_WORDS[@]}-2}" }}")" &&
COMPREPLY=("${__zoxide_z_prefix}${result}") COMPREPLY=("${__zoxide_z_prefix}${result}")
\builtin printf '\e[5n' \builtin printf '\e[5n'
fi fi

View File

@ -8,16 +8,16 @@
# pwd based on the value of _ZO_RESOLVE_SYMLINKS. # pwd based on the value of _ZO_RESOLVE_SYMLINKS.
__zoxide_pwd() { __zoxide_pwd() {
{%- if resolve_symlinks %} {%- if resolve_symlinks %}
\pwd -P \command pwd -P
{%- else %} {%- else %}
\pwd -L \command pwd -L
{%- endif %} {%- endif %}
} }
# cd + custom logic based on the value of _ZO_ECHO. # cd + custom logic based on the value of _ZO_ECHO.
__zoxide_cd() { __zoxide_cd() {
# shellcheck disable=SC2164 # shellcheck disable=SC2164
\cd "$@" {%- if echo %} && __zoxide_pwd {%- endif %} \command cd "$@" {%- if echo %} && __zoxide_pwd {%- endif %}
} }
{{ section }} {{ section }}
@ -31,7 +31,7 @@ __zoxide_cd() {
{%- when InitHook::Prompt -%} {%- when InitHook::Prompt -%}
# Hook to add new entries to the database. # Hook to add new entries to the database.
__zoxide_hook() { __zoxide_hook() {
zoxide add -- "$(__zoxide_pwd)" \command zoxide add -- "$(__zoxide_pwd || \builtin true)"
} }
# Initialize hook. # Initialize hook.
@ -40,7 +40,7 @@ if [ "${PS1:=}" = "${PS1#*\$(__zoxide_hook)}" ]; then
fi fi
{%- when InitHook::Pwd -%} {%- when InitHook::Pwd -%}
\printf "%s\n%s\n" \ \command printf "%s\n%s\n" \
"zoxide: PWD hooks are not supported on POSIX shells." \ "zoxide: PWD hooks are not supported on POSIX shells." \
" Use 'zoxide init posix --hook prompt' instead." " Use 'zoxide init posix --hook prompt' instead."
@ -60,19 +60,20 @@ __zoxide_z() {
__zoxide_cd "${OLDPWD}" __zoxide_cd "${OLDPWD}"
else else
# shellcheck disable=SC2016 # shellcheck disable=SC2016
\printf 'zoxide: $OLDPWD is not set' \command printf 'zoxide: $OLDPWD is not set'
return 1 return 1
fi fi
elif [ "$#" -eq 1 ] && [ -d "$1" ]; then elif [ "$#" -eq 1 ] && [ -d "$1" ]; then
__zoxide_cd "$1" __zoxide_cd "$1"
else else
__zoxide_result="$(zoxide query --exclude "$(__zoxide_pwd)" -- "$@")" && __zoxide_cd "${__zoxide_result}" __zoxide_result="$(\command zoxide query --exclude "$(__zoxide_pwd || \builtin true)" -- "$@")" &&
__zoxide_cd "${__zoxide_result}"
fi fi
} }
# Jump to a directory using interactive search. # Jump to a directory using interactive search.
__zoxide_zi() { __zoxide_zi() {
__zoxide_result="$(zoxide query -i -- "$@")" && __zoxide_cd "${__zoxide_result}" __zoxide_result="$(\command zoxide query -i -- "$@")" && __zoxide_cd "${__zoxide_result}"
} }
{{ section }} {{ section }}
@ -84,10 +85,10 @@ __zoxide_zi() {
# Remove definitions. # Remove definitions.
__zoxide_unset() { __zoxide_unset() {
\unset -f "$@" >/dev/null 2>&1 \command unset -f "$@" >/dev/null 2>&1
\unset -v "$@" >/dev/null 2>&1 \command unset -v "$@" >/dev/null 2>&1
# shellcheck disable=SC1001 # shellcheck disable=SC1001
\unalias "$@" >/dev/null 2>&1 || \: \command unalias "$@" >/dev/null 2>&1 || \:
} }
__zoxide_unset '{{cmd}}' __zoxide_unset '{{cmd}}'

View File

@ -30,7 +30,7 @@ function __zoxide_cd() {
{% else -%} {% else -%}
# Hook to add new entries to the database. # Hook to add new entries to the database.
function __zoxide_hook() { function __zoxide_hook() {
zoxide add -- "$(__zoxide_pwd)" \command zoxide add -- "$(__zoxide_pwd || \builtin true)"
} }
# Initialize hook. # Initialize hook.
@ -52,29 +52,29 @@ fi
# Jump to a directory using only keywords. # Jump to a directory using only keywords.
function __zoxide_z() { function __zoxide_z() {
if [ "$#" -eq 0 ]; then if [[ "$#" -eq 0 ]]; then
__zoxide_cd ~ __zoxide_cd ~
elif [ "$#" -eq 1 ] && [ "$1" = '-' ]; then elif [[ "$#" -eq 1 ]] && [[ "$1" = '-' ]]; then
if [ -n "${OLDPWD}" ]; then if [[ -n "${OLDPWD}" ]]; then
__zoxide_cd "${OLDPWD}" __zoxide_cd "${OLDPWD}"
else else
# shellcheck disable=SC2016 # shellcheck disable=SC2016
\builtin printf 'zoxide: $OLDPWD is not set' \builtin printf 'zoxide: $OLDPWD is not set'
return 1 return 1
fi fi
elif [ "$#" -eq 1 ] && [ -d "$1" ]; then elif [[ "$#" -eq 1 ]] && [[ -d "$1" ]]; then
__zoxide_cd "$1" __zoxide_cd "$1"
else else
\builtin local result \builtin local result
result="$(zoxide query --exclude "$(__zoxide_pwd)" -- "$@")" \ result="$(\command zoxide query --exclude "$(__zoxide_pwd || \builtin true)" -- "$@")" &&
&& __zoxide_cd "${result}" __zoxide_cd "${result}"
fi fi
} }
# Jump to a directory using interactive search. # Jump to a directory using interactive search.
function __zoxide_zi() { function __zoxide_zi() {
\builtin local result \builtin local result
result="$(zoxide query -i -- "$@")" && __zoxide_cd "${result}" result="$(\command zoxide query -i -- "$@")" && __zoxide_cd "${result}"
} }
{{ section }} {{ section }}
@ -86,8 +86,8 @@ function __zoxide_zi() {
# Remove definitions. # Remove definitions.
function __zoxide_unset() { function __zoxide_unset() {
\builtin unalias "$@" &>/dev/null || true \builtin unalias "$@" &>/dev/null || \builtin true
\builtin unfunction "$@" &>/dev/null || true \builtin unfunction "$@" &>/dev/null || \builtin true
\builtin unset "$@" &>/dev/null \builtin unset "$@" &>/dev/null
} }
@ -101,6 +101,43 @@ function {{cmd}}i() {
__zoxide_zi "$@" __zoxide_zi "$@"
} }
if [[ -o zle ]]; then
function _{{cmd}}() {
\builtin local buffer tokens
# shellcheck disable=SC2034,SC2153,SC2154
buffer="${BUFFER}."
# shellcheck disable=SC2206,SC2296
tokens=(${(Q)${(z)buffer}})
if [[ "{{ "${#tokens[@]}" }}" -eq 2 ]]; then
_files -/
elif [[ "${tokens[-1]}" == '.' ]]; then
\builtin printf '\e[5n'
fi
}
function _{{cmd}}_helper() {
\builtin local tokens result
# shellcheck disable=SC2154,SC2206,SC2296
tokens=(${(Q)${(z)BUFFER}})
# shellcheck disable=SC2086
if result="$(\command zoxide query -i -- ${tokens[2,-1]})"; then
# shellcheck disable=SC2034
RBUFFER=''
# shellcheck disable=SC2034,SC2296
LBUFFER="${tokens[1]} ${(q-)result}"
fi
\builtin zle reset-prompt
}
\builtin zle -N _{{cmd}}_helper
\builtin bindkey "\e[0n" _{{cmd}}_helper
if [[ "${+functions[compdef]}" -ne 0 ]]; then
\compdef -d {{cmd}}
\compdef _{{cmd}} {{cmd}}
fi
fi
{%- when None %} {%- when None %}
{{ not_configured }} {{ not_configured }}

View File

@ -1,7 +1,7 @@
[package] [package]
name = "xtask" name = "xtask"
version = "0.1.0" version = "0.1.0"
edition = "2018" edition = "2021"
publish = false publish = false
[dependencies] [dependencies]

View File

@ -87,7 +87,11 @@ fn run_fmt(nix_enabled: bool, check: bool) -> Result<()> {
fn run_lint(nix_enabled: bool) -> Result<()> { fn run_lint(nix_enabled: bool) -> Result<()> {
// Run cargo-clippy. // Run cargo-clippy.
let color: &[&str] = if is_ci() { &["--color=always"] } else { &[] }; let color: &[&str] = if is_ci() { &["--color=always"] } else { &[] };
Command::new("cargo").args(&["clippy", "--all-features", "--all-targets"]).args(color)._run()?; Command::new("cargo")
.args(&["clippy", "--all-features", "--all-targets"])
.args(color)
.args(&["--", "-Dwarnings"])
._run()?;
if nix_enabled { if nix_enabled {
// Run cargo-audit. // Run cargo-audit.