diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20b164a..e6cae63 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, windows-latest] # FIXME: macos-latest steps: - uses: actions/checkout@v2 @@ -28,5 +28,5 @@ jobs: - run: cargo xtask ci 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' }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dedb8a..7b99d34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,16 +9,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Zsh: completions for `z` command. + ### Changed -- fzf: better default options. -- fish: interactive completions are only triggered when the last argument is empty. +- Fzf: better default options. +- Fish: interactive completions are only triggered when the last argument is empty. ### Fixed - PowerShell: use global scope for aliases. - Zsh: fix errors with `set -eu`. -- fzf: handle early selection. +- Fzf: handle early selection. ## [0.7.9] - 2021-11-02 diff --git a/Cargo.toml b/Cargo.toml index 7dd9b0c..32a16b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ authors = ["Ajeet D'Souza <98ajeet@gmail.com>"] categories = ["command-line-utilities", "filesystem"] description = "A smarter cd command for your terminal" -edition = "2018" +edition = "2021" keywords = ["cli"] license = "MIT" name = "zoxide" diff --git a/README.md b/README.md index e91aa7b..1d3fcbc 100644 --- a/README.md +++ b/README.md @@ -33,15 +33,17 @@ zoxide works on all major shells. ![Tutorial][tutorial] ```sh -z foo # cd into highest ranked directory matching foo -z foo bar # cd into highest ranked directory matching foo and bar +z foo # cd into highest ranked directory matching foo +z foo bar # cd into highest ranked directory matching foo and bar -z ~/foo # z also works like a regular cd command -z foo/ # cd into relative path -z .. # cd one level up -z - # cd into previous directory +z ~/foo # z also works like a regular cd command +z foo/ # cd into relative path +z .. # cd one level up +z - # cd into previous directory -zi foo # cd with interactive selection (using fzf) +zi foo # cd with interactive selection (using fzf) + +z foo # show interactive completions (zoxide v0.7.10+, bash/fish/zsh only) ``` Read more about the matching algorithm [here][algorithm-matching]. @@ -231,6 +233,8 @@ Add this to your configuration (usually `~/.zshrc`): eval "$(zoxide init zsh)" ``` +For completions to work, this line must be added _after_ calling `compinit`. +
@@ -347,16 +351,16 @@ They must be set before `zoxide init` is called. [algorithm-matching]: https://github.com/ajeetdsouza/zoxide/wiki/Algorithm#matching [alpine linux packages]: https://pkgs.alpinelinux.org/packages?name=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/ [chocolatey]: https://community.chocolatey.org/packages/zoxide [conda-forge]: https://anaconda.org/conda-forge/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 [debian packages]: https://packages.debian.org/stable/admin/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 [emacs]: https://www.gnu.org/software/emacs/ [fedora packages]: https://src.fedoraproject.org/rpms/rust-zoxide diff --git a/shell.nix b/shell.nix index 7f9a96d..ef72f24 100644 --- a/shell.nix +++ b/shell.nix @@ -1,41 +1,41 @@ let rust = import (builtins.fetchTarball - "https://github.com/oxalica/rust-overlay/archive/ad311f5bb5c5ef475985f1e0f264e831470a8510.tar.gz"); - pkgs = import { overlays = [ rust ]; }; - pkgs-latest = import (builtins.fetchTarball - "https://github.com/NixOS/nixpkgs/archive/3ef1d2a9602c18f8742e1fb63d5ae9867092e3d6.tar.gz") - { }; + "https://github.com/oxalica/rust-overlay/archive/783722a22ee5d762ac5c1c7b418b57b3010c827a.tar.gz"); + pkgs = import (builtins.fetchTarball + "https://github.com/NixOS/nixpkgs/archive/58f87c20e1abbbe835f1f3106ecea10fd93c4a90.tar.gz") { + overlays = [ rust ]; + }; in pkgs.mkShell { buildInputs = [ # Rust pkgs.rust-bin.stable.latest.default # Shells - pkgs-latest.elvish - pkgs-latest.fish - pkgs-latest.nushell - pkgs-latest.xonsh pkgs.bash pkgs.dash + pkgs.elvish + pkgs.fish + pkgs.nushell pkgs.powershell + pkgs.xonsh pkgs.zsh # Tools - pkgs-latest.cargo-audit - pkgs-latest.mandoc - pkgs-latest.nixfmt - pkgs-latest.nodePackages.markdownlint-cli - pkgs-latest.python3Packages.black - pkgs-latest.python3Packages.mypy - pkgs-latest.python3Packages.pylint - pkgs-latest.shellcheck - pkgs-latest.shfmt + pkgs.cargo-audit + pkgs.mandoc + pkgs.nixfmt + pkgs.nodePackages.markdownlint-cli + pkgs.python3Packages.black + pkgs.python3Packages.mypy + pkgs.python3Packages.pylint + pkgs.shellcheck + pkgs.shfmt # Dependencies pkgs.cacert - pkgs.libiconv pkgs.fzf pkgs.git + pkgs.libiconv ]; RUST_BACKTRACE = 1; diff --git a/src/app/query.rs b/src/app/query.rs index 53e7f09..9262c08 100644 --- a/src/app/query.rs +++ b/src/app/query.rs @@ -32,16 +32,17 @@ impl Query { if self.interactive { let mut fzf = Fzf::new(false)?; + let stdin = fzf.stdin(); + let selection = loop { let dir = match stream.next() { Some(dir) => dir, None => break fzf.select()?, }; - match writeln!(fzf.stdin(), "{}", dir.display_score(now)) { - Ok(()) => (()), + match writeln!(stdin, "{}", dir.display_score(now)) { 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")?, } }; diff --git a/src/app/remove.rs b/src/app/remove.rs index 60f475f..dba9e85 100644 --- a/src/app/remove.rs +++ b/src/app/remove.rs @@ -19,16 +19,17 @@ impl Run for Remove { let mut stream = db.stream(now).with_keywords(keywords); let mut fzf = Fzf::new(true)?; + let stdin = fzf.stdin(); + let selection = loop { let dir = match stream.next() { Some(dir) => dir, None => break fzf.select()?, }; - match writeln!(fzf.stdin(), "{}", dir.display_score(now)) { - Ok(()) => (()), + match writeln!(stdin, "{}", dir.display_score(now)) { 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")?, } }; diff --git a/src/config.rs b/src/config.rs index 69fd695..4ec7903 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,7 +3,6 @@ use std::ffi::OsString; use std::path::PathBuf; use anyhow::{bail, Context, Result}; -use dirs; use glob::Pattern; use crate::db::Rank; diff --git a/src/fzf.rs b/src/fzf.rs index 205df32..3f0d31c 100644 --- a/src/fzf.rs +++ b/src/fzf.rs @@ -24,7 +24,7 @@ impl Fzf { command.args(&[ "--bind=ctrl-z:ignore", "--exit-0", - "--height=35%", + "--height=40%", "--inline-info", "--no-sort", "--reverse", diff --git a/templates/bash.txt b/templates/bash.txt index 314cd79..285e3cb 100644 --- a/templates/bash.txt +++ b/templates/bash.txt @@ -32,7 +32,7 @@ function __zoxide_cd() { {%- if hook == InitHook::Prompt %} function __zoxide_hook() { \builtin local -r retval="$?" - \builtin command zoxide add -- "$(__zoxide_pwd)" + \command zoxide add -- "$(__zoxide_pwd || \builtin true)" return "${retval}" } {%- else if hook == InitHook::Pwd %} @@ -40,10 +40,11 @@ __zoxide_oldpwd="$(__zoxide_pwd)" function __zoxide_hook() { \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 __zoxide_oldpwd="${pwd_tmp}" - \builtin command zoxide add -- "${__zoxide_oldpwd}" + \command zoxide add -- "${__zoxide_oldpwd}" fi return "${retval}" } @@ -76,14 +77,15 @@ function __zoxide_z() { __zoxide_cd "${result:2}" else \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 } # Jump to a directory using interactive search. function __zoxide_zi() { \builtin local result - result="$(\builtin command zoxide query -i -- "$@")" && __zoxide_cd "${result}" + result="$(\command zoxide query -i -- "$@")" && __zoxide_cd "${result}" } {{ section }} @@ -123,11 +125,12 @@ if [[ :"${SHELLOPTS}": =~ :(vi|emacs): && ${TERM} != 'dumb' ]]; then # If there is only one argument, use `cd` completions. 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. elif [[ -z ${COMP_WORDS[-1]} ]]; then - \local result - result="$(\builtin command zoxide query -i -- "${COMP_WORDS[@]:1}")" && + \builtin local result + result="$(\command zoxide query -i -- "{{ "${COMP_WORDS[@]:1:${#COMP_WORDS[@]}-2}" }}")" && COMPREPLY=("${__zoxide_z_prefix}${result}") \builtin printf '\e[5n' fi diff --git a/templates/posix.txt b/templates/posix.txt index 65b356d..3ac9878 100644 --- a/templates/posix.txt +++ b/templates/posix.txt @@ -8,16 +8,16 @@ # pwd based on the value of _ZO_RESOLVE_SYMLINKS. __zoxide_pwd() { {%- if resolve_symlinks %} - \pwd -P + \command pwd -P {%- else %} - \pwd -L + \command pwd -L {%- endif %} } # cd + custom logic based on the value of _ZO_ECHO. __zoxide_cd() { # shellcheck disable=SC2164 - \cd "$@" {%- if echo %} && __zoxide_pwd {%- endif %} + \command cd "$@" {%- if echo %} && __zoxide_pwd {%- endif %} } {{ section }} @@ -31,7 +31,7 @@ __zoxide_cd() { {%- when InitHook::Prompt -%} # Hook to add new entries to the database. __zoxide_hook() { - zoxide add -- "$(__zoxide_pwd)" + \command zoxide add -- "$(__zoxide_pwd || \builtin true)" } # Initialize hook. @@ -40,7 +40,7 @@ if [ "${PS1:=}" = "${PS1#*\$(__zoxide_hook)}" ]; then fi {%- when InitHook::Pwd -%} -\printf "%s\n%s\n" \ +\command printf "%s\n%s\n" \ "zoxide: PWD hooks are not supported on POSIX shells." \ " Use 'zoxide init posix --hook prompt' instead." @@ -60,19 +60,20 @@ __zoxide_z() { __zoxide_cd "${OLDPWD}" else # shellcheck disable=SC2016 - \printf 'zoxide: $OLDPWD is not set' + \command printf 'zoxide: $OLDPWD is not set' return 1 fi elif [ "$#" -eq 1 ] && [ -d "$1" ]; then __zoxide_cd "$1" 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 } # Jump to a directory using interactive search. __zoxide_zi() { - __zoxide_result="$(zoxide query -i -- "$@")" && __zoxide_cd "${__zoxide_result}" + __zoxide_result="$(\command zoxide query -i -- "$@")" && __zoxide_cd "${__zoxide_result}" } {{ section }} @@ -84,10 +85,10 @@ __zoxide_zi() { # Remove definitions. __zoxide_unset() { - \unset -f "$@" >/dev/null 2>&1 - \unset -v "$@" >/dev/null 2>&1 + \command unset -f "$@" >/dev/null 2>&1 + \command unset -v "$@" >/dev/null 2>&1 # shellcheck disable=SC1001 - \unalias "$@" >/dev/null 2>&1 || \: + \command unalias "$@" >/dev/null 2>&1 || \: } __zoxide_unset '{{cmd}}' diff --git a/templates/zsh.txt b/templates/zsh.txt index 9e1677e..9165ee9 100644 --- a/templates/zsh.txt +++ b/templates/zsh.txt @@ -30,7 +30,7 @@ function __zoxide_cd() { {% else -%} # Hook to add new entries to the database. function __zoxide_hook() { - zoxide add -- "$(__zoxide_pwd)" + \command zoxide add -- "$(__zoxide_pwd || \builtin true)" } # Initialize hook. @@ -52,29 +52,29 @@ fi # Jump to a directory using only keywords. function __zoxide_z() { - if [ "$#" -eq 0 ]; then + if [[ "$#" -eq 0 ]]; then __zoxide_cd ~ - elif [ "$#" -eq 1 ] && [ "$1" = '-' ]; then - if [ -n "${OLDPWD}" ]; then + elif [[ "$#" -eq 1 ]] && [[ "$1" = '-' ]]; then + if [[ -n "${OLDPWD}" ]]; then __zoxide_cd "${OLDPWD}" else # shellcheck disable=SC2016 \builtin printf 'zoxide: $OLDPWD is not set' return 1 fi - elif [ "$#" -eq 1 ] && [ -d "$1" ]; then + elif [[ "$#" -eq 1 ]] && [[ -d "$1" ]]; then __zoxide_cd "$1" else \builtin local result - result="$(zoxide query --exclude "$(__zoxide_pwd)" -- "$@")" \ - && __zoxide_cd "${result}" + result="$(\command zoxide query --exclude "$(__zoxide_pwd || \builtin true)" -- "$@")" && + __zoxide_cd "${result}" fi } # Jump to a directory using interactive search. function __zoxide_zi() { \builtin local result - result="$(zoxide query -i -- "$@")" && __zoxide_cd "${result}" + result="$(\command zoxide query -i -- "$@")" && __zoxide_cd "${result}" } {{ section }} @@ -86,8 +86,8 @@ function __zoxide_zi() { # Remove definitions. function __zoxide_unset() { - \builtin unalias "$@" &>/dev/null || true - \builtin unfunction "$@" &>/dev/null || true + \builtin unalias "$@" &>/dev/null || \builtin true + \builtin unfunction "$@" &>/dev/null || \builtin true \builtin unset "$@" &>/dev/null } @@ -101,6 +101,43 @@ function {{cmd}}i() { __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 %} {{ not_configured }} diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 2a999ce..44cd856 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "xtask" version = "0.1.0" -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/xtask/src/main.rs b/xtask/src/main.rs index fd983d9..63df538 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -87,7 +87,11 @@ fn run_fmt(nix_enabled: bool, check: bool) -> Result<()> { fn run_lint(nix_enabled: bool) -> Result<()> { // Run cargo-clippy. 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 { // Run cargo-audit.