summaryrefslogtreecommitdiff
path: root/plugins/z
diff options
context:
space:
mode:
authorTuowen Zhao <ztuowen@gmail.com>2026-01-04 22:47:54 -0800
committerTuowen Zhao <ztuowen@gmail.com>2026-01-04 22:47:54 -0800
commit2aa4cb7a52b28722816ecfd55f3b06293332c55c (patch)
treef02a9f3d59d109c70caf932a24e43368994e0e8c /plugins/z
parent7e951c254e779ff0620537cf43ca69dd878387b4 (diff)
parentd23d3ea69fdb839088e6e5589557cce77b34aaf8 (diff)
downloadzsh-master.tar.gz
zsh-master.tar.bz2
zsh-master.zip
Merge remote-tracking branch 'github/master'HEADmaster
Diffstat (limited to 'plugins/z')
-rw-r--r--plugins/z/LICENSE2
-rw-r--r--plugins/z/MANUAL.md63
-rw-r--r--plugins/z/img/demo.gifbin0 -> 1271520 bytes
-rw-r--r--plugins/z/z.plugin.zsh62
4 files changed, 78 insertions, 49 deletions
diff --git a/plugins/z/LICENSE b/plugins/z/LICENSE
index 6af13b9e0..b36aeb408 100644
--- a/plugins/z/LICENSE
+++ b/plugins/z/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2018-2023 Alexandros Kozak
+Copyright (c) 2018-2025 Alexandros Kozak
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/plugins/z/MANUAL.md b/plugins/z/MANUAL.md
index d367c3026..eddf787dd 100644
--- a/plugins/z/MANUAL.md
+++ b/plugins/z/MANUAL.md
@@ -4,15 +4,17 @@
![Zsh version 4.3.11 and higher](img/zsh_4.3.11_plus.svg)
[![GitHub stars](https://img.shields.io/github/stars/agkozak/zsh-z.svg)](https://github.com/agkozak/zsh-z/stargazers)
-Zsh-z is a command line tool that allows you to jump quickly to directories that you have visited frequently in the past, or recently -- but most often a combination of the two (a concept known as ["frecency"](https://en.wikipedia.org/wiki/Frecency)). It works by keeping track of when you go to directories and how much time you spend in them. It is then in the position to guess where you want to go when you type a partial string, e.g., `z src` might take you to `~/src/zsh`. `z zsh` might also get you there, and `z c/z` might prove to be even more specific -- it all depends on your habits and how much time you have been using Zsh-z to build up a database. After using Zsh-z for a little while, you will get to where you want to be by typing considerably less than you would need if you were using `cd`.
+![Zsh-z demo](img/demo.gif)
-Zsh-z is a native Zsh port of [rupa/z](https://github.com/rupa/z), a tool written for `bash` and Zsh that uses embedded `awk` scripts to do the heavy lifting. It was quite possibly my most used command line tool for a couple of years. I decided to translate it, `awk` parts and all, into pure Zsh script, to see if by eliminating calls to external tools (`awk`, `sort`, `date`, `sed`, `mv`, `rm`, and `chown`) and reducing forking through subshells I could make it faster. The performance increase is impressive, particularly on systems where forking is slow, such as Cygwin, MSYS2, and WSL. I have found that, in those environments, switching directories using Zsh-z can be over 100% faster than it is using `rupa/z`.
+Zsh-z is a command-line tool that allows you to jump quickly to directories that you have visited frequently or recently -- but most often a combination of the two (a concept known as ["frecency"](https://en.wikipedia.org/wiki/Frecency)). It works by keeping track of when you go to directories and how much time you spend in them. Based on this data, it predicts where you want to go when you type a partial string. For example, `z src` might take you to `~/src/zsh`. `z zsh` might also get you there, and `z c/z` might prove to be even more specific -- it all depends on your habits and how long you have been using Zsh-z to build up a database. After using Zsh-z for a little while, you will get to where you want to be by typing considerably less than you would need to if you were using `cd`.
-There is a noteworthy stability increase as well. Race conditions have always been a problem with `rupa/z`, and users of that utility will occasionally lose their `.z` databases. By having Zsh-z only use Zsh (`rupa/z` uses a hybrid shell code that works on `bash` as well), I have been able to implement a `zsh/system`-based file-locking mechanism similar to [the one @mafredri once proposed for `rupa/z`](https://github.com/rupa/z/pull/199). It is now nearly impossible to crash the database, even through extreme testing.
+Zsh-z is a native Zsh port of [`rupa/z`](https://github.com/rupa/z), a tool written for `bash` and Zsh that uses embedded `awk` scripts to do the heavy lifting. `rupa/z` was my most used command-line tool for a couple of years. I decided to translate it, `awk` parts and all, into pure Zsh script, to see if by eliminating calls to external tools (`awk`, `sort`, `date`, `sed`, `mv`, `rm`, and `chown`) and reducing forking through subshells I could make it faster. The performance increase is impressive, particularly on systems where forking is slow, such as Cygwin, MSYS2, and WSL. I have found that in those environments, switching directories using Zsh-z can be over 100% faster than it is using `rupa/z`.
-There are other, smaller improvements which I try to document in [Improvements and Fixes](#improvements-and-fixes). These include the new default behavior of sorting your tab completions by frecency rather than just letting Zsh sort the raw results alphabetically (a behavior which can be restored if you like it -- [see below](#settings)).
+There is also a significant stability improvement. Race conditions have always been a problem with `rupa/z`, and users of that utility occasionally lose their `~/.z` databases. By having Zsh-z only use Zsh (`rupa/z` uses a hybrid shell code standard that works on `bash` as well), I have been able to implement a `zsh/system`-based file-locking mechanism similar to [the one @mafredri once proposed for `rupa/z`](https://github.com/rupa/z/pull/199). It is now nearly impossible to crash the database.
-Zsh-z is a drop-in replacement for `rupa/z` and will, by default, use the same database (`~/.z`), so you can go on using `rupa/z` when you launch `bash`.
+There are other, smaller improvements which I document below in [Improvements and Fixes](#improvements-and-fixes). For instance, tab completions are now sorted by frecency by default rather than alphabetically (the latter behavior can be restored if you like it -- [see below](#settings)).
+
+Zsh-z is a drop-in replacement for `rupa/z` and will, by default, use the same database (`~/.z`, or whatever database file you specify), so you can go on using `rupa/z` when you launch `bash`.
## Table of Contents
- [News](#news)
@@ -32,10 +34,16 @@ Zsh-z is a drop-in replacement for `rupa/z` and will, by default, use the same d
<details>
<summary>Here are the latest features and updates.</summary>
+- August 24, 2023
+ + Zsh-z will now run when `setopt NO_UNSET` has been enabled (props @ntninja).
+- August 23, 2023
+ + Better logic for loading `zsh/files` (props @z0rc).
+- August 2, 2023
+ + Zsh-z still uses the `zsh/files` module when possible but will fall back on the standard `chown`, `mv`, and `rm` commands in its absence.
- April 27, 2023
+ Zsh-z now allows the user to specify the directory-changing command using the `ZSHZ_CD` environment variable (default: `builtin cd`; props @basnijholt).
- January 27, 2023
- + If the datafile directory specified by `ZSHZ_DATA` or `_Z_DATA` does not already exist, create it (props @mattmc3).
+ + If the database file directory specified by `ZSHZ_DATA` or `_Z_DATA` does not already exist, create it (props @mattmc3).
- June 29, 2022
+ Zsh-z is less likely to leave temporary files sitting around (props @mafredri).
- June 27, 2022
@@ -61,10 +69,10 @@ Zsh-z is a drop-in replacement for `rupa/z` and will, by default, use the same d
+ Fixed the explanation string printed during completion so that it may be formatted with `zstyle`.
+ Zsh-z now declares `ZSHZ_EXCLUDE_DIRS` as an array with unique elements so that you do not have to.
- July 29, 2021
- + Temporarily disabling use of `print -v`, which seems to be mangling CJK multibyte strings.
+ + Temporarily disabling the use of `print -v`, which was mangling CJK multibyte strings.
- July 27, 2021
+ Internal escaping of path names now works with older versions of ZSH.
- + Zsh-z now detects and discards any incomplete or incorrectly formattted database entries.
+ + Zsh-z now detects and discards any incomplete or incorrectly formatted database entries.
- July 10, 2021
+ Setting `ZSHZ_TRAILING_SLASH=1` makes it so that a search pattern ending in `/` can match the end of a path; e.g. `z foo/` can match `/path/to/foo`.
- June 25, 2021
@@ -85,7 +93,7 @@ Zsh-z is a drop-in replacement for `rupa/z` and will, by default, use the same d
- January 11, 2021
+ Major refactoring of the code.
+ `z -lr` and `z -lt` work as expected.
- + `EXTENDED_GLOB` has been disabled within the plugin to accomodate old-fashioned Windows directories with names such as `Progra~1`.
+ + `EXTENDED_GLOB` has been disabled within the plugin to accommodate old-fashioned Windows directories with names such as `Progra~1`.
+ Removed `zshelldoc` documentation.
- January 6, 2021
+ I have corrected the frecency routine so that it matches `rupa/z`'s math, but for the present, Zsh-z will continue to display ranks as 1/10000th of what they are in `rupa/z` -- [they had to multiply theirs by 10000](https://github.com/rupa/z/commit/f1f113d9bae9effaef6b1e15853b5eeb445e0712) to work around `bash`'s inadequacies at dealing with decimal fractions.
@@ -94,7 +102,7 @@ Zsh-z is a drop-in replacement for `rupa/z` and will, by default, use the same d
- December 22, 2020
+ `ZSHZ_CASE`: when set to `ignore`, pattern matching is case-insensitive; when set to `smart`, patterns are matched case-insensitively when they are all lowercase and case-sensitively when they have uppercase characters in them (a behavior very much like Vim's `smartcase` setting).
+ `ZSHZ_KEEP_DIRS` is an array of directory names that should not be removed from the database, even if they are not currently available (useful when a drive is not always mounted).
- + Symlinked datafiles were having their symlinks overwritten; this bug has been fixed.
+ + Symlinked database files were having their symlinks overwritten; this bug has been fixed.
</details>
@@ -102,15 +110,15 @@ Zsh-z is a drop-in replacement for `rupa/z` and will, by default, use the same d
### General observations
-This script can be installed simply by downloading it and sourcing it from your `.zshrc`:
+This plugin can be installed simply by putting the various files in a directory together and by sourcing `zsh-z.plugin.zsh` in your `.zshrc`:
source /path/to/zsh-z.plugin.zsh
-For tab completion to work, you will want to have loaded `compinit`. The frameworks handle this themselves. If you are not using a framework, put
+For tab completion to work, `_zshz` *must* be in the same directory as `zsh-z.plugin.zsh`, and you will want to have loaded `compinit`. The frameworks handle this themselves. If you are not using a framework, put
- autoload -U compinit && compinit
+ autoload -U compinit; compinit
-in your .zshrc somewhere below where you source `zsh-z.plugin.zsh`.
+in your `.zshrc` somewhere below where you source `zsh-z.plugin.zsh`.
If you add
@@ -220,7 +228,7 @@ Add the line
to your `.zshrc`.
-`zsh-z` supports `zinit`'s `unload` feature; just run `zinit unload agkozak/zshz` to restore the shell to its state before `zsh-z` was loaded.
+Zsh-z supports `zinit`'s `unload` feature; just run `zinit unload agkozak/zsh-z` to restore the shell to its state before Zsh-z was loaded.
### For [Znap](https://github.com/marlonrichert/zsh-snap) users
@@ -241,7 +249,7 @@ somewhere above the line that says `zplug load`. Then run
zplug install
zplug load
-to install `zsh-z`.
+to install Zsh-z.
## Command Line Options
@@ -255,9 +263,9 @@ to install `zsh-z`.
- `-x` Remove a directory (by default, the current directory) from the database
- `-xR` Remove a directory (by default, the current directory) and its subdirectories from the database
-# Settings
+## Settings
-Zsh-z has environment variables (they all begin with `ZSHZ_`) that change its behavior if you set them; you can also keep your old ones if you have been using `rupa/z` (they begin with `_Z_`).
+Zsh-z has environment variables (they all begin with `ZSHZ_`) that change its behavior if you set them. You can also keep your old ones if you have been using `rupa/z` (whose environment variables begin with `_Z_`).
* `ZSHZ_CMD` changes the command name (default: `z`)
* `ZSHZ_CD` specifies the default directory-changing command (default: `builtin cd`)
@@ -316,19 +324,18 @@ A good example might involve a directory tree that has Git repositories within i
(As a Zsh user, I tend to use `**` instead of `find`, but it is good to see how deep your directory trees go before doing that.)
-
## Other Improvements and Fixes
* `z -x` works, with the help of `chpwd_functions`.
-* Zsh-z works on Solaris.
+* Zsh-z is compatible with Solaris.
* Zsh-z uses the "new" `zshcompsys` completion system instead of the old `compctl` one.
-* There is no error message when the database file has not yet been created.
-* There is support for special characters (e.g., `[`) in directory names.
-* If `z -l` only returns one match, a common root is not printed.
-* Exit status codes increasingly make sense.
-* Completions work with options `-c`, `-r`, and `-t`.
-* If `~/foo` and `~/foob` are matches, `~/foo` is *not* the common root. Only a common parent directory can be a common root.
-* `z -x` and the new, recursive `z -xR` can take an argument so that you can remove directories other than `PWD` from the database.
+* No error message is displayed when the database file has not yet been created.
+* Special characters (e.g., `[`) in directory names are now supported.
+* If `z -l` returns only one match, a common root is not printed.
+* Exit status codes are more logical.
+* Completions now work with options `-c`, `-r`, and `-t`.
+* If `~/foo` and `~/foob` are matches, `~/foo` is no longer considered the common root. Only a common parent directory can be a common root.
+* `z -x` and the new, recursive `z -xR` can now accept an argument so that you can remove directories other than `PWD` from the database.
## Migrating from Other Tools
@@ -350,7 +357,7 @@ the line
That will re-bind `z` or the command of your choice to the underlying Zsh-z function.
-## Known Bugs
+## Known Bug
It is possible to run a completion on a string with spaces in it, e.g., `z us bi<TAB>` might take you to `/usr/local/bin`. This works, but as things stand, after the completion the command line reads
z us /usr/local/bin.
diff --git a/plugins/z/img/demo.gif b/plugins/z/img/demo.gif
new file mode 100644
index 000000000..247f52f4d
--- /dev/null
+++ b/plugins/z/img/demo.gif
Binary files differ
diff --git a/plugins/z/z.plugin.zsh b/plugins/z/z.plugin.zsh
index 60a630624..39b832292 100644
--- a/plugins/z/z.plugin.zsh
+++ b/plugins/z/z.plugin.zsh
@@ -4,7 +4,7 @@
#
# https://github.com/agkozak/zsh-z
#
-# Copyright (c) 2018-2023 Alexandros Kozak
+# Copyright (c) 2018-2025 Alexandros Kozak
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -100,20 +100,29 @@ With no ARGUMENT, list the directory history in ascending rank.
}
# Load zsh/datetime module, if necessary
-(( $+EPOCHSECONDS )) || zmodload zsh/datetime
-
-# Load zsh/files, if necessary
-[[ ${builtins[zf_chown]} == 'defined' &&
- ${builtins[zf_mv]} == 'defined' &&
- ${builtins[zf_rm]} == 'defined' ]] ||
- zmodload -F zsh/files b:zf_chown b:zf_mv b:zf_rm
-
-# Load zsh/system, if necessary
-[[ ${modules[zsh/system]} == 'loaded' ]] || zmodload zsh/system &> /dev/null
+(( ${+EPOCHSECONDS} )) || zmodload zsh/datetime
# Global associative array for internal use
typeset -gA ZSHZ
+# Fallback utilities in case Zsh lacks zsh/files (as is the case with MobaXterm)
+ZSHZ[CHOWN]='chown'
+ZSHZ[MV]='mv'
+ZSHZ[RM]='rm'
+# Try to load zsh/files utilities
+if [[ ${builtins[zf_chown]-} != 'defined' ||
+ ${builtins[zf_mv]-} != 'defined' ||
+ ${builtins[zf_rm]-} != 'defined' ]]; then
+ zmodload -F zsh/files b:zf_chown b:zf_mv b:zf_rm &> /dev/null
+fi
+# Use zsh/files, if it is available
+[[ ${builtins[zf_chown]-} == 'defined' ]] && ZSHZ[CHOWN]='zf_chown'
+[[ ${builtins[zf_mv]-} == 'defined' ]] && ZSHZ[MV]='zf_mv'
+[[ ${builtins[zf_rm]-} == 'defined' ]] && ZSHZ[RM]='zf_rm'
+
+# Load zsh/system, if necessary
+[[ ${modules[zsh/system]-} == 'loaded' ]] || zmodload zsh/system &> /dev/null
+
# Make sure ZSHZ_EXCLUDE_DIRS has been declared so that other scripts can
# simply append to it
(( ${+ZSHZ_EXCLUDE_DIRS} )) || typeset -gUa ZSHZ_EXCLUDE_DIRS
@@ -145,7 +154,7 @@ is-at-least 5.3.0 && ZSHZ[PRINTV]=1
zshz() {
# Don't use `emulate -L zsh' - it breaks PUSHD_IGNORE_DUPS
- setopt LOCAL_OPTIONS NO_KSH_ARRAYS NO_SH_WORD_SPLIT EXTENDED_GLOB
+ setopt LOCAL_OPTIONS NO_KSH_ARRAYS NO_SH_WORD_SPLIT EXTENDED_GLOB UNSET
(( ZSHZ_DEBUG )) && setopt LOCAL_OPTIONS WARN_CREATE_GLOBAL
local REPLY
@@ -277,7 +286,7 @@ zshz() {
if (( ret != 0 )); then
# Avoid clobbering the datafile if the write to tempfile failed
- zf_rm -f "$tempfile"
+ ${ZSHZ[RM]} -f "$tempfile"
return $ret
fi
@@ -285,16 +294,26 @@ zshz() {
owner=${ZSHZ_OWNER:-${_Z_OWNER}}
if (( ZSHZ[USE_FLOCK] )); then
- zf_mv "$tempfile" "$datafile" 2> /dev/null || zf_rm -f "$tempfile"
+ # An unsual case: if inside Docker container where datafile could be bind
+ # mounted
+ if [[ -r '/proc/1/cgroup' && "$(< '/proc/1/cgroup')" == *docker* ]]; then
+ print "$(< "$tempfile")" > "$datafile" 2> /dev/null
+ ${ZSHZ[RM]} -f "$tempfile"
+ # All other cases
+ else
+ ${ZSHZ[MV]} "$tempfile" "$datafile" 2> /dev/null ||
+ ${ZSHZ[RM]} -f "$tempfile"
+ fi
if [[ -n $owner ]]; then
- zf_chown ${owner}:"$(id -ng ${owner})" "$datafile"
+ ${ZSHZ[CHOWN]} ${owner}:"$(id -ng ${owner})" "$datafile"
fi
else
if [[ -n $owner ]]; then
- zf_chown "${owner}":"$(id -ng "${owner}")" "$tempfile"
+ ${ZSHZ[CHOWN]} "${owner}":"$(id -ng "${owner}")" "$tempfile"
fi
- zf_mv -f "$tempfile" "$datafile" 2> /dev/null || zf_rm -f "$tempfile"
+ ${ZSHZ[MV]} -f "$tempfile" "$datafile" 2> /dev/null ||
+ ${ZSHZ[RM]} -f "$tempfile"
fi
# In order to make z -x work, we have to disable zsh-z's adding
@@ -306,7 +325,7 @@ zshz() {
}
############################################################
- # Read the curent datafile contents, update them, "age" them
+ # Read the current datafile contents, update them, "age" them
# when the total rank gets high enough, and print the new
# contents to STDOUT.
#
@@ -884,6 +903,9 @@ alias ${ZSHZ_CMD:-${_Z_CMD:-z}}='zshz 2>&1'
# ZSHZ
############################################################
_zshz_precmd() {
+ # Protect against `setopt NO_UNSET'
+ setopt LOCAL_OPTIONS UNSET
+
# Do not add PWD to datafile when in HOME directory, or
# if `z -x' has just been run
[[ $PWD == "$HOME" ]] || (( ZSHZ[DIRECTORY_REMOVED] )) && return
@@ -931,7 +953,7 @@ add-zsh-hook chpwd _zshz_chpwd
# Completion
############################################################
-# Standarized $0 handling
+# Standardized $0 handling
# https://zdharma-continuum.github.io/Zsh-100-Commits-Club/Zsh-Plugin-Standard.html
0="${${ZERO:-${0:#$ZSH_ARGZERO}}:-${(%):-%N}}"
0="${${(M)0:#/*}:-$PWD/$0}"
@@ -958,7 +980,7 @@ ZSHZ[FUNCTIONS]='_zshz_usage
# Enable WARN_NESTED_VAR for functions listed in
# ZSHZ[FUNCTIONS]
############################################################
-(( ZSHZ_DEBUG )) && () {
+(( ${+ZSHZ_DEBUG} )) && () {
if is-at-least 5.4.0; then
local x
for x in ${=ZSHZ[FUNCTIONS]}; do