Browse Source

feat(z): replace `rupa/z` with `agkozak/zsh-z` (#11236)

Carlo 1 year ago
parent
commit
7e3231b846
9 changed files with 1417 additions and 600 deletions
  1. 21 0
      plugins/z/LICENSE
  2. 343 0
      plugins/z/MANUAL.md
  3. 0 4
      plugins/z/Makefile
  4. 0 148
      plugins/z/README
  5. 4 3
      plugins/z/README.md
  6. 82 0
      plugins/z/_z
  7. 0 173
      plugins/z/z.1
  8. 967 5
      plugins/z/z.plugin.zsh
  9. 0 267
      plugins/z/z.sh

+ 21 - 0
plugins/z/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018-2022 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
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

File diff suppressed because it is too large
+ 343 - 0
plugins/z/MANUAL.md


+ 0 - 4
plugins/z/Makefile

@@ -1,4 +0,0 @@
-readme:
-	@groff -man -Tascii z.1 | col -bx
-
-.PHONY: readme

+ 0 - 148
plugins/z/README

@@ -1,148 +0,0 @@
-Z(1)                             User Commands                            Z(1)
-
-
-
-NAME
-       z - jump around
-
-SYNOPSIS
-       z [-chlrtx] [regex1 regex2 ... regexn]
-
-AVAILABILITY
-       bash, zsh
-
-DESCRIPTION
-       Tracks your most used directories, based on 'frecency'.
-
-       After  a  short  learning  phase, z will take you to the most 'frecent'
-       directory that matches ALL of the regexes given on the command line, in
-       order.
-
-       For example, z foo bar would match /foo/bar but not /bar/foo.
-
-OPTIONS
-       -c     restrict matches to subdirectories of the current directory
-
-       -e     echo the best match, don't cd
-
-       -h     show a brief help message
-
-       -l     list only
-
-       -r     match by rank only
-
-       -t     match by recent access only
-
-       -x     remove the current directory from the datafile
-
-EXAMPLES
-       z foo         cd to most frecent dir matching foo
-
-       z foo bar     cd to most frecent dir matching foo, then bar
-
-       z -r foo      cd to highest ranked dir matching foo
-
-       z -t foo      cd to most recently accessed dir matching foo
-
-       z -l foo      list all dirs matching foo (by frecency)
-
-NOTES
-   Installation:
-       Put something like this in your $HOME/.bashrc or $HOME/.zshrc:
-
-              . /path/to/z.sh
-
-       cd around for a while to build up the db.
-
-       PROFIT!!
-
-       Optionally:
-              Set $_Z_CMD to change the command name (default z).
-              Set $_Z_DATA to change the datafile (default $HOME/.z).
-              Set  $_Z_MAX_SCORE  lower  to  age  entries  out faster (default
-              9000).
-              Set $_Z_NO_RESOLVE_SYMLINKS to prevent symlink resolution.
-              Set $_Z_NO_PROMPT_COMMAND to handle PROMPT_COMMAND/precmd  your-
-              self.
-              Set $_Z_EXCLUDE_DIRS to an array of directory trees to  exclude.
-              Set $_Z_OWNER to allow usage when in 'sudo -s' mode.
-              (These  settings  should  go  in  .bashrc/.zshrc before the line
-              added above.)
-              Install the provided man page z.1  somewhere  in  your  MANPATH,
-              like /usr/local/man/man1.
-
-   Aging:
-       The rank of directories maintained by z undergoes aging based on a sim-
-       ple formula. The rank of each entry is incremented  every  time  it  is
-       accessed.  When the sum of ranks is over 9000, all ranks are multiplied
-       by 0.99. Entries with a rank lower than 1 are forgotten.
-
-   Frecency:
-       Frecency is a portmanteau of 'recent' and 'frequency'. It is a weighted
-       rank  that depends on how often and how recently something occurred. As
-       far as I know, Mozilla came up with the term.
-
-       To z, a directory that has low ranking but has been  accessed  recently
-       will  quickly  have  higher rank than a directory accessed frequently a
-       long time ago.
-
-       Frecency is determined at runtime.
-
-   Common:
-       When multiple directories match all queries, and they all have a common
-       prefix, z will cd to the shortest matching directory, without regard to
-       priority.  This has been in effect, if  undocumented,  for  quite  some
-       time, but should probably be configurable or reconsidered.
-
-   Tab Completion:
-       z  supports tab completion. After any number of arguments, press TAB to
-       complete on directories that match each argument. Due to limitations of
-       the  completion  implementations,  only  the last argument will be com-
-       pleted in the shell.
-
-       Internally, z decides you've requested a completion if the  last  argu-
-       ment  passed  is  an  absolute  path to an existing directory. This may
-       cause unexpected behavior if the last argument to z begins with /.
-
-ENVIRONMENT
-       A function _z() is defined.
-
-       The contents of the variable $_Z_CMD is aliased to _z 2>&1. If not set,
-       $_Z_CMD defaults to z.
-
-       The  environment  variable $_Z_DATA can be used to control the datafile
-       location. If it is not defined, the location defaults to $HOME/.z.
-
-       The environment variable $_Z_NO_RESOLVE_SYMLINKS can be set to  prevent
-       resolving  of  symlinks.  If  it  is  not  set,  symbolic links will be
-       resolved when added to the datafile.
-
-       In bash, z appends a command to the PROMPT_COMMAND environment variable
-       to maintain its database. In zsh, z appends a function _z_precmd to the
-       precmd_functions array.
-
-       The environment variable $_Z_NO_PROMPT_COMMAND can be set if  you  want
-       to handle PROMPT_COMMAND or precmd yourself.
-
-       The  environment  variable  $_Z_EXCLUDE_DIRS  can be set to an array of
-       directory trees to exclude from tracking.  $HOME  is  always  excluded.
-       Directories must be full paths without trailing slashes.
-
-       The  environment  variable  $_Z_OWNER  can  be set to your username, to
-       allow usage of z when your sudo environment keeps $HOME set.
-
-FILES
-       Data is stored in $HOME/.z. This  can  be  overridden  by  setting  the
-       $_Z_DATA  environment variable. When initialized, z will raise an error
-       if this path is a directory, and not function correctly.
-
-       A man page (z.1) is provided.
-
-SEE ALSO
-       regex(7), pushd, popd, autojump, cdargs
-
-       Please file bugs at https://github.com/rupa/z/
-
-
-
-z                                January 2013                             Z(1)

+ 4 - 3
plugins/z/README.md

@@ -1,8 +1,9 @@
 # z - jump around
 
-This plugin defines the [z command](https://github.com/rupa/z) that tracks your most visited directories and allows you to access them with very few keystrokes.
+This plugin defines the [z command](https://github.com/agkozak/zsh-z) that tracks your most visited directories and allows you to access them with very few keystrokes.
 
 ### Example
+
 Assume that you have previously visited directory `~/.oh-my-zsh/plugins`. From any folder in your command line, you can quickly access it by using a regex match to this folder:
 
 ```bash
@@ -11,6 +12,7 @@ Assume that you have previously visited directory `~/.oh-my-zsh/plugins`. From a
 ```
 
 ### Setup
+
 To enable z, add `z` to your `plugins` array in your zshrc file:
 
 ```zsh
@@ -19,5 +21,4 @@ plugins=(... z)
 
 ### Further reading
 
-For advanced usage and details of z, see [README](./README) (in man page format, copied from [rupa/z](https://github.com/rupa/z)).
-
+For advanced usage and details of z, see [MANUAL](./MANUAL.md) (copied from [agkozak/zsh-z](https://github.com/agkozak/zsh-z)).

+ 82 - 0
plugins/z/_z

@@ -0,0 +1,82 @@
+#compdef zshz ${ZSHZ_CMD:-${_Z_CMD:-z}}
+#
+# Zsh-z - jump around with Zsh - A native Zsh version of z without awk, sort,
+# date, or sed
+#
+# https://github.com/agkozak/zsh-z
+#
+# Copyright (c) 2018-2022 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
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+# z (https://github.com/rupa/z) is copyright (c) 2009 rupa deadwyler and
+# licensed under the WTFPL license, Version 2.a
+#
+# shellcheck shell=ksh
+
+############################################################
+# Zsh-z COMPLETIONS
+############################################################
+emulate -L zsh
+(( ZSHZ_DEBUG )) &&
+  setopt LOCAL_OPTIONS WARN_CREATE_GLOBAL NO_WARN_NESTED_VAR 2> /dev/null
+
+# TODO: This routine currently reproduces z's feature of allowing spaces to be
+# used as wildcards in completions, so that
+#
+#   z us lo bi
+#
+# can expand to
+#
+#   z /usr/local/bin
+#
+# but it also reproduces z's buggy display on the commandline, viz.
+#
+#   z us lo /usr/local/bin
+#
+# Address.
+
+local completions expl completion
+local -a completion_list
+
+completions=$(zshz --complete ${(@)words:1})
+[[ -z $completions ]] && return 1
+
+for completion in ${(f)completions[@]}; do
+  if (( ZSHZ_TILDE )) && [[ $completion == ${HOME}* ]]; then
+    completion="~${(q)${completion#${HOME}}}"
+  else
+    completion="${(q)completion}"
+  fi
+  completion_list+=( $completion )
+done
+
+_description -V completion_list expl 'directories'
+
+if [[ $ZSHZ_COMPLETION == 'legacy' ]]; then
+  compadd "${expl[@]}" -QU -- "${completion_list[@]}"
+else
+  compadd "${expl[@]}" -QU -V zsh-z -- "${completion_list[@]}"
+fi
+
+compstate[insert]=menu
+
+return 0
+
+# vim: ft=zsh:fdm=indent:ts=2:et:sts=2:sw=2:

+ 0 - 173
plugins/z/z.1

@@ -1,173 +0,0 @@
-.TH "Z" "1" "January 2013" "z" "User Commands"
-.SH
-NAME
-z \- jump around
-.SH
-SYNOPSIS
-z [\-chlrtx] [regex1 regex2 ... regexn]
-.SH
-AVAILABILITY
-bash, zsh
-.SH
-DESCRIPTION
-Tracks your most used directories, based on 'frecency'.
-.P
-After a short learning phase, \fBz\fR will take you to the most 'frecent'
-directory that matches ALL of the regexes given on the command line, in order.
-
-For example, \fBz foo bar\fR would match \fB/foo/bar\fR but not \fB/bar/foo\fR.
-.SH
-OPTIONS
-.TP
-\fB\-c\fR
-restrict matches to subdirectories of the current directory
-.TP
-\fB\-e\fR
-echo the best match, don't cd
-.TP
-\fB\-h\fR
-show a brief help message
-.TP
-\fB\-l\fR
-list only
-.TP
-\fB\-r\fR
-match by rank only
-.TP
-\fB\-t\fR
-match by recent access only
-.TP
-\fB\-x\fR
-remove the current directory from the datafile
-.SH EXAMPLES
-.TP 14
-\fBz foo\fR
-cd to most frecent dir matching foo
-.TP 14
-\fBz foo bar\fR
-cd to most frecent dir matching foo, then bar
-.TP 14
-\fBz -r foo\fR
-cd to highest ranked dir matching foo
-.TP 14
-\fBz -t foo\fR
-cd to most recently accessed dir matching foo
-.TP 14
-\fBz -l foo\fR
-list all dirs matching foo (by frecency)
-.SH
-NOTES
-.SS
-Installation:
-.P
-Put something like this in your \fB$HOME/.bashrc\fR or \fB$HOME/.zshrc\fR:
-.RS
-.P
-\fB. /path/to/z.sh\fR
-.RE
-.P
-\fBcd\fR around for a while to build up the db.
-.P
-PROFIT!!
-.P
-Optionally:
-.RS
-Set \fB$_Z_CMD\fR to change the command name (default \fBz\fR).
-.RE
-.RS
-Set \fB$_Z_DATA\fR to change the datafile (default \fB$HOME/.z\fR).
-.RE
-.RS
-Set \fB$_Z_MAX_SCORE\fR lower to age entries out faster (default \fB9000\fR).
-.RE
-.RS
-Set \fB$_Z_NO_RESOLVE_SYMLINKS\fR to prevent symlink resolution.
-.RE
-.RS
-Set \fB$_Z_NO_PROMPT_COMMAND\fR to handle \fBPROMPT_COMMAND/precmd\fR yourself.
-.RE
-.RS
-Set \fB$_Z_EXCLUDE_DIRS\fR to an array of directory trees to exclude.
-.RE
-.RS
-Set \fB$_Z_OWNER\fR to allow usage when in 'sudo -s' mode.
-.RE
-.RS
-(These settings should go in .bashrc/.zshrc before the line added above.)
-.RE
-.RS
-Install the provided man page \fBz.1\fR somewhere in your \f$MANPATH, like
-\fB/usr/local/man/man1\fR.
-.RE
-.SS
-Aging:
-The rank of directories maintained by \fBz\fR undergoes aging based on a simple
-formula. The rank of each entry is incremented every time it is accessed. When
-the sum of ranks is over 9000, all ranks are multiplied by 0.99. Entries with a
-rank lower than 1 are forgotten.
-.SS
-Frecency:
-Frecency is a portmanteau of 'recent' and 'frequency'. It is a weighted rank
-that depends on how often and how recently something occurred. As far as I
-know, Mozilla came up with the term.
-.P
-To \fBz\fR, a directory that has low ranking but has been accessed recently
-will quickly have higher rank than a directory accessed frequently a long time
-ago.
-.P
-Frecency is determined at runtime.
-.SS
-Common:
-When multiple directories match all queries, and they all have a common prefix,
-\fBz\fR will cd to the shortest matching directory, without regard to priority.
-This has been in effect, if undocumented, for quite some time, but should
-probably be configurable or reconsidered.
-.SS
-Tab Completion:
-\fBz\fR supports tab completion. After any number of arguments, press TAB to
-complete on directories that match each argument. Due to limitations of the
-completion implementations, only the last argument will be completed in the
-shell.
-.P
-Internally, \fBz\fR decides you've requested a completion if the last argument
-passed is an absolute path to an existing directory. This may cause unexpected
-behavior if the last argument to \fBz\fR begins with \fB/\fR.
-.SH
-ENVIRONMENT
-A function \fB_z()\fR is defined.
-.P
-The contents of the variable \fB$_Z_CMD\fR is aliased to \fB_z 2>&1\fR. If not
-set, \fB$_Z_CMD\fR defaults to \fBz\fR.
-.P
-The environment variable \fB$_Z_DATA\fR can be used to control the datafile
-location. If it is not defined, the location defaults to \fB$HOME/.z\fR.
-.P
-The environment variable \fB$_Z_NO_RESOLVE_SYMLINKS\fR can be set to prevent
-resolving of symlinks. If it is not set, symbolic links will be resolved when
-added to the datafile.
-.P
-In bash, \fBz\fR appends a command to the \fBPROMPT_COMMAND\fR environment
-variable to maintain its database. In zsh, \fBz\fR appends a function
-\fB_z_precmd\fR to the \fBprecmd_functions\fR array.
-.P
-The environment variable \fB$_Z_NO_PROMPT_COMMAND\fR can be set if you want to
-handle \fBPROMPT_COMMAND\fR or \fBprecmd\fR yourself.
-.P
-The environment variable \fB$_Z_EXCLUDE_DIRS\fR can be set to an array of
-directory trees to exclude from tracking. \fB$HOME\fR is always excluded.
-Directories must be full paths without trailing slashes.
-.P
-The environment variable \fB$_Z_OWNER\fR can be set to your username, to
-allow usage of \fBz\fR when your sudo environment keeps \fB$HOME\fR set.
-.SH
-FILES
-Data is stored in \fB$HOME/.z\fR. This can be overridden by setting the
-\fB$_Z_DATA\fR environment variable. When initialized, \fBz\fR will raise an
-error if this path is a directory, and not function correctly.
-.P
-A man page (\fBz.1\fR) is provided.
-.SH
-SEE ALSO
-regex(7), pushd, popd, autojump, cdargs
-.P
-Please file bugs at https://github.com/rupa/z/

+ 967 - 5
plugins/z/z.plugin.zsh

@@ -1,6 +1,968 @@
-# Handle $0 according to the standard:
-# https://zdharma-continuum.github.io/Zsh-100-Commits-Club/Zsh-Plugin-Standard.html
-0="${${ZERO:-${0:#$ZSH_ARGZERO}}:-${(%):-%N}}"
-0="${${(M)0:#/*}:-$PWD/$0}"
+################################################################################
+# Zsh-z - jump around with Zsh - A native Zsh version of z without awk, sort,
+# date, or sed
+#
+# https://github.com/agkozak/zsh-z
+#
+# Copyright (c) 2018-2022 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
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+# z (https://github.com/rupa/z) is copyright (c) 2009 rupa deadwyler and
+# licensed under the WTFPL license, Version 2.
+#
+# Zsh-z maintains a jump-list of the directories you actually use.
+#
+# INSTALL:
+#   * put something like this in your .zshrc:
+#       source /path/to/zsh-z.plugin.zsh
+#   * cd around for a while to build up the database
+#
+# USAGE:
+#   * z foo       cd to the most frecent directory matching foo
+#   * z foo bar   cd to the most frecent directory matching both foo and bar
+#                   (e.g. /foo/bat/bar/quux)
+#   * z -r foo    cd to the highest ranked directory matching foo
+#   * z -t foo    cd to most recently accessed directory matching foo
+#   * z -l foo    List matches instead of changing directories
+#   * z -e foo    Echo the best match without changing directories
+#   * z -c foo    Restrict matches to subdirectories of PWD
+#   * z -x        Remove a directory (default: PWD) from the database
+#   * z -xR       Remove a directory (default: PWD) and its subdirectories from
+#                   the database
+#
+# ENVIRONMENT VARIABLES:
+#
+#   ZSHZ_CASE -> if `ignore', pattern matching is case-insensitive; if `smart',
+#     pattern matching is case-insensitive only when the pattern is all
+#     lowercase
+#   ZSHZ_CMD -> name of command (default: z)
+#   ZSHZ_COMPLETION -> completion method (default: 'frecent'; 'legacy' for
+#     alphabetic sorting)
+#   ZSHZ_DATA -> name of datafile (default: ~/.z)
+#   ZSHZ_EXCLUDE_DIRS -> array of directories to exclude from your database
+#     (default: empty)
+#   ZSHZ_KEEP_DIRS -> array of directories that should not be removed from the
+#     database, even if they are not currently available (default: empty)
+#   ZSHZ_MAX_SCORE -> maximum combined score the database entries can have
+#     before beginning to age (default: 9000)
+#   ZSHZ_NO_RESOLVE_SYMLINKS -> '1' prevents symlink resolution
+#   ZSHZ_OWNER -> your username (if you want use Zsh-z while using sudo -s)
+#   ZSHZ_UNCOMMON -> if 1, do not jump to "common directories," but rather drop
+#     subdirectories based on what the search string was (default: 0)
+################################################################################
 
-source "${0:h}/z.sh"
+autoload -U is-at-least
+
+if ! is-at-least 4.3.11; then
+  print "Zsh-z requires Zsh v4.3.11 or higher." >&2 && exit
+fi
+
+############################################################
+# The help message
+#
+# Globals:
+#   ZSHZ_CMD
+############################################################
+_zshz_usage() {
+  print "Usage: ${ZSHZ_CMD:-${_Z_CMD:-z}} [OPTION]... [ARGUMENT]
+Jump to a directory that you have visited frequently or recently, or a bit of both, based on the partial string ARGUMENT.
+
+With no ARGUMENT, list the directory history in ascending rank.
+
+  --add Add a directory to the database
+  -c    Only match subdirectories of the current directory
+  -e    Echo the best match without going to it
+  -h    Display this help and exit
+  -l    List all matches without going to them
+  -r    Match by rank
+  -t    Match by recent access
+  -x    Remove a directory from the database (by default, the current directory)
+  -xR   Remove a directory and its subdirectories from the database (by default, the current directory)" |
+    fold -s -w $COLUMNS >&2
+}
+
+# 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
+
+# Global associative array for internal use
+typeset -gA ZSHZ
+
+# 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
+
+# Determine if zsystem flock is available
+zsystem supports flock &> /dev/null && ZSHZ[USE_FLOCK]=1
+
+# Determine if `print -v' is supported
+is-at-least 5.3.0 && ZSHZ[PRINTV]=1
+
+############################################################
+# The Zsh-z Command
+#
+# Globals:
+#   ZSHZ
+#   ZSHZ_CASE
+#   ZSHZ_COMPLETION
+#   ZSHZ_DATA
+#   ZSHZ_DEBUG
+#   ZSHZ_EXCLUDE_DIRS
+#   ZSHZ_KEEP_DIRS
+#   ZSHZ_MAX_SCORE
+#   ZSHZ_OWNER
+#
+# Arguments:
+#   $* Command options and arguments
+############################################################
+zshz() {
+
+  # Don't use `emulate -L zsh' - it breaks PUSHD_IGNORE_DUPS
+  setopt LOCAL_OPTIONS NO_KSH_ARRAYS NO_SH_WORD_SPLIT EXTENDED_GLOB
+  (( ZSHZ_DEBUG )) && setopt LOCAL_OPTIONS WARN_CREATE_GLOBAL
+
+  local REPLY
+  local -a lines
+
+  # Allow the user to specify the datafile name in $ZSHZ_DATA (default: ~/.z)
+  # If the datafile is a symlink, it gets dereferenced
+  local datafile=${${ZSHZ_DATA:-${_Z_DATA:-${HOME}/.z}}:A}
+
+  # If the datafile is a directory, print a warning and exit
+  if [[ -d $datafile ]]; then
+    print "ERROR: Zsh-z's datafile (${datafile}) is a directory." >&2
+    exit
+  fi
+
+  # Make sure that the datafile exists before attempting to read it or lock it
+  # for writing
+  [[ -f $datafile ]] || touch "$datafile"
+
+  # Bail if we don't own the datafile and $ZSHZ_OWNER is not set
+  [[ -z ${ZSHZ_OWNER:-${_Z_OWNER}} && -f $datafile && ! -O $datafile ]] &&
+    return
+
+  # Load the datafile into an array and parse it
+  lines=( ${(f)"$(< $datafile)"} )
+  # Discard entries that are incomplete or incorrectly formatted
+  lines=( ${(M)lines:#/*\|[[:digit:]]##[.,]#[[:digit:]]#\|[[:digit:]]##} )
+
+  ############################################################
+  # Add a path to or remove one from the datafile
+  #
+  # Globals:
+  #   ZSHZ
+  #   ZSHZ_EXCLUDE_DIRS
+  #   ZSHZ_OWNER
+  #
+  # Arguments:
+  #   $1 Which action to perform (--add/--remove)
+  #   $2 The path to add
+  ############################################################
+  _zshz_add_or_remove_path() {
+    local action=${1}
+    shift
+
+    if [[ $action == '--add' ]]; then
+
+      # TODO: The following tasks are now handled by _agkozak_precmd. Dead code?
+
+      # Don't add $HOME
+      [[ $* == $HOME ]] && return
+
+      # Don't track directory trees excluded in ZSHZ_EXCLUDE_DIRS
+      local exclude
+      for exclude in ${(@)ZSHZ_EXCLUDE_DIRS:-${(@)_Z_EXCLUDE_DIRS}}; do
+        case $* in
+          ${exclude}|${exclude}/*) return ;;
+        esac
+      done
+    fi
+
+    # A temporary file that gets copied over the datafile if all goes well
+    local tempfile="${datafile}.${RANDOM}"
+
+    # See https://github.com/rupa/z/pull/199/commits/ed6eeed9b70d27c1582e3dd050e72ebfe246341c
+    if (( ZSHZ[USE_FLOCK] )); then
+
+      local lockfd
+
+      # Grab exclusive lock (released when function exits)
+      zsystem flock -f lockfd "$datafile" 2> /dev/null || return
+
+    fi
+
+    integer tmpfd
+    case $action in
+      --add)
+        exec {tmpfd}>|"$tempfile"  # Open up tempfile for writing
+        _zshz_update_datafile $tmpfd "$*"
+        local ret=$?
+        ;;
+      --remove)
+        local xdir  # Directory to be removed
+
+        if (( ${ZSHZ_NO_RESOLVE_SYMLINKS:-${_Z_NO_RESOLVE_SYMLINKS}} )); then
+          [[ -d ${${*:-${PWD}}:a} ]] && xdir=${${*:-${PWD}}:a}
+        else
+          [[ -d ${${*:-${PWD}}:A} ]] && xdir=${${*:-${PWD}}:a}
+        fi
+
+        local -a lines_to_keep
+        if (( ${+opts[-R]} )); then
+          # Prompt user before deleting entire database
+          if [[ $xdir == '/' ]] && ! read -q "?Delete entire Zsh-z database? "; then
+            print && return 1
+          fi
+          # All of the lines that don't match the directory to be deleted
+          lines_to_keep=( ${lines:#${xdir}\|*} )
+          # Or its subdirectories
+          lines_to_keep=( ${lines_to_keep:#${xdir%/}/**} )
+        else
+          # All of the lines that don't match the directory to be deleted
+          lines_to_keep=( ${lines:#${xdir}\|*} )
+        fi
+        if [[ $lines != "$lines_to_keep" ]]; then
+          lines=( $lines_to_keep )
+        else
+          return 1  # The $PWD isn't in the datafile
+        fi
+        exec {tmpfd}>|"$tempfile"  # Open up tempfile for writing
+        print -u $tmpfd -l -- $lines
+        local ret=$?
+        ;;
+    esac
+
+    if (( tmpfd != 0 )); then
+      # Close tempfile
+      exec {tmpfd}>&-
+    fi
+
+    if (( ret != 0 )); then
+      # Avoid clobbering the datafile if the write to tempfile failed
+      zf_rm -f "$tempfile"
+      return $ret
+    fi
+
+    local owner
+    owner=${ZSHZ_OWNER:-${_Z_OWNER}}
+
+    if (( ZSHZ[USE_FLOCK] )); then
+      zf_mv "$tempfile" "$datafile" 2> /dev/null || zf_rm -f "$tempfile"
+
+      if [[ -n $owner ]]; then
+        zf_chown ${owner}:"$(id -ng ${owner})" "$datafile"
+      fi
+    else
+      if [[ -n $owner ]]; then
+        zf_chown "${owner}":"$(id -ng "${owner}")" "$tempfile"
+      fi
+      zf_mv -f "$tempfile" "$datafile" 2> /dev/null || zf_rm -f "$tempfile"
+    fi
+
+    # In order to make z -x work, we have to disable zsh-z's adding
+    # to the database until the user changes directory and the
+    # chpwd_functions are run
+    if [[ $action == '--remove' ]]; then
+      ZSHZ[DIRECTORY_REMOVED]=1
+    fi
+  }
+
+  ############################################################
+  # Read the curent datafile contents, update them, "age" them
+  # when the total rank gets high enough, and print the new
+  # contents to STDOUT.
+  #
+  # Globals:
+  #   ZSHZ_KEEP_DIRS
+  #   ZSHZ_MAX_SCORE
+  #
+  # Arguments:
+  #   $1 File descriptor linked to tempfile
+  #   $2 Path to be added to datafile
+  ############################################################
+  _zshz_update_datafile() {
+
+    integer fd=$1
+    local -A rank time
+
+    # Characters special to the shell (such as '[]') are quoted with backslashes
+    # See https://github.com/rupa/z/issues/246
+    local add_path=${(q)2}
+
+    local -a existing_paths
+    local now=$EPOCHSECONDS line dir
+    local path_field rank_field time_field count x
+
+    rank[$add_path]=1
+    time[$add_path]=$now
+
+    # Remove paths from database if they no longer exist
+    for line in $lines; do
+      if [[ ! -d ${line%%\|*} ]]; then
+        for dir in ${(@)ZSHZ_KEEP_DIRS}; do
+          if [[ ${line%%\|*} == ${dir}/* ||
+                ${line%%\|*} == $dir     ||
+                $dir == '/' ]]; then
+            existing_paths+=( $line )
+          fi
+        done
+      else
+        existing_paths+=( $line )
+      fi
+    done
+    lines=( $existing_paths )
+
+    for line in $lines; do
+      path_field=${(q)line%%\|*}
+      rank_field=${${line%\|*}#*\|}
+      time_field=${line##*\|}
+
+      # When a rank drops below 1, drop the path from the database
+      (( rank_field < 1 )) && continue
+
+      if [[ $path_field == $add_path ]]; then
+        rank[$path_field]=$rank_field
+        (( rank[$path_field]++ ))
+        time[$path_field]=$now
+      else
+        rank[$path_field]=$rank_field
+        time[$path_field]=$time_field
+      fi
+      (( count += rank_field ))
+    done
+    if (( count > ${ZSHZ_MAX_SCORE:-${_Z_MAX_SCORE:-9000}} )); then
+      # Aging
+      for x in ${(k)rank}; do
+        print -u $fd -- "$x|$(( 0.99 * rank[$x] ))|${time[$x]}" || return 1
+      done
+    else
+      for x in ${(k)rank}; do
+        print -u $fd -- "$x|${rank[$x]}|${time[$x]}" || return 1
+      done
+    fi
+  }
+
+  ############################################################
+  # The original tab completion method
+  #
+  # String processing is smartcase -- case-insensitive if the
+  # search string is lowercase, case-sensitive if there are
+  # any uppercase letters. Spaces in the search string are
+  # treated as *'s in globbing. Read the contents of the
+  # datafile and print matches to STDOUT.
+  #
+  # Arguments:
+  #   $1 The string to be completed
+  ############################################################
+  _zshz_legacy_complete() {
+
+    local line path_field path_field_normalized
+
+    # Replace spaces in the search string with asterisks for globbing
+    1=${1//[[:space:]]/*}
+
+    for line in $lines; do
+
+      path_field=${line%%\|*}
+
+      path_field_normalized=$path_field
+      if (( ZSHZ_TRAILING_SLASH )); then
+        path_field_normalized=${path_field%/}/
+      fi
+
+      # If the search string is all lowercase, the search will be case-insensitive
+      if [[ $1 == "${1:l}" && ${path_field_normalized:l} == *${~1}* ]]; then
+        print -- $path_field
+      # Otherwise, case-sensitive
+      elif [[ $path_field_normalized == *${~1}* ]]; then
+        print -- $path_field
+      fi
+
+    done
+    # TODO: Search strings with spaces in them are currently treated case-
+    # insensitively.
+  }
+
+  ############################################################
+  # `print' or `printf' to REPLY
+  #
+  # Variable assignment through command substitution, of the
+  # form
+  #
+  #   foo=$( bar )
+  #
+  # requires forking a subshell; on Cygwin/MSYS2/WSL1 that can
+  # be surprisingly slow. Zsh-z avoids doing that by printing
+  # values to the variable REPLY. Since Zsh v5.3.0 that has
+  # been possible with `print -v'; for earlier versions of the
+  # shell, the values are placed on the editing buffer stack
+  # and then `read' into REPLY.
+  #
+  # Globals:
+  #   ZSHZ
+  #
+  # Arguments:
+  #   Options and parameters for `print'
+  ############################################################
+  _zshz_printv() {
+    # NOTE: For a long time, ZSH's `print -v' had a tendency
+    # to mangle multibyte strings:
+    #
+    #   https://www.zsh.org/mla/workers/2020/msg00307.html
+    #
+    # The bug was fixed in late 2020:
+    #
+    #   https://github.com/zsh-users/zsh/commit/b6ba74cd4eaec2b6cb515748cf1b74a19133d4a4#diff-32bbef18e126b837c87b06f11bfc61fafdaa0ed99fcb009ec53f4767e246b129
+    #
+    # In order to support shells with the bug, we must use a form of `printf`,
+    # which does not exhibit the undesired behavior. See
+    #
+    #   https://www.zsh.org/mla/workers/2020/msg00308.html
+
+    if (( ZSHZ[PRINTV] )); then
+      builtin print -v REPLY -f %s $@
+    else
+      builtin print -z $@
+      builtin read -rz REPLY
+    fi
+  }
+
+  ############################################################
+  # If matches share a common root, find it, and put it in
+  # REPLY for _zshz_output to use.
+  #
+  # Arguments:
+  #   $1 Name of associative array of matches and ranks
+  ############################################################
+  _zshz_find_common_root() {
+    local -a common_matches
+    local x short
+
+    common_matches=( ${(@Pk)1} )
+
+    for x in ${(@)common_matches}; do
+      if [[ -z $short ]] || (( $#x < $#short )) || [[ $x != ${short}/* ]]; then
+        short=$x
+      fi
+    done
+
+    [[ $short == '/' ]] && return
+
+    for x in ${(@)common_matches}; do
+      [[ $x != $short* ]] && return
+    done
+
+    _zshz_printv -- $short
+  }
+
+  ############################################################
+  # Calculate a common root, if there is one. Then do one of
+  # the following:
+  #
+  #   1) Print a list of completions in frecent order;
+  #   2) List them (z -l) to STDOUT; or
+  #   3) Put a common root or best match into REPLY
+  #
+  # Globals:
+  #   ZSHZ_UNCOMMON
+  #
+  # Arguments:
+  #   $1 Name of an associative array of matches and ranks
+  #   $2 The best match or best case-insensitive match
+  #   $3 Whether to produce a completion, a list, or a root or
+  #        match
+  ############################################################
+  _zshz_output() {
+
+    local match_array=$1 match=$2 format=$3
+    local common k x
+    local -a descending_list output
+    local -A output_matches
+
+    output_matches=( ${(Pkv)match_array} )
+
+    _zshz_find_common_root $match_array
+    common=$REPLY
+
+    case $format in
+
+      completion)
+        for k in ${(@k)output_matches}; do
+          _zshz_printv -f "%.2f|%s" ${output_matches[$k]} $k
+          descending_list+=( ${(f)REPLY} )
+          REPLY=''
+        done
+        descending_list=( ${${(@On)descending_list}#*\|} )
+        print -l $descending_list
+        ;;
+
+      list)
+        local path_to_display
+        for x in ${(k)output_matches}; do
+          if (( ${output_matches[$x]} )); then
+            path_to_display=$x
+            (( ZSHZ_TILDE )) &&
+              path_to_display=${path_to_display/#${HOME}/\~}
+            _zshz_printv -f "%-10d %s\n" ${output_matches[$x]} $path_to_display
+            output+=( ${(f)REPLY} )
+            REPLY=''
+          fi
+        done
+        if [[ -n $common ]]; then
+          (( ZSHZ_TILDE )) && common=${common/#${HOME}/\~}
+          (( $#output > 1 )) && printf "%-10s %s\n" 'common:' $common
+        fi
+        # -lt
+        if (( $+opts[-t] )); then
+          for x in ${(@On)output}; do
+            print -- $x
+          done
+        # -lr
+        elif (( $+opts[-r] )); then
+          for x in ${(@on)output}; do
+            print -- $x
+          done
+        # -l
+        else
+          for x in ${(@on)output}; do
+            print $x
+          done
+        fi
+        ;;
+
+      *)
+        if (( ! ZSHZ_UNCOMMON )) && [[ -n $common ]]; then
+          _zshz_printv -- $common
+        else
+          _zshz_printv -- ${(P)match}
+        fi
+        ;;
+    esac
+  }
+
+  ############################################################
+  # Match a pattern by rank, time, or a combination of the
+  # two, and output the results as completions, a list, or a
+  # best match.
+  #
+  # Globals:
+  #   ZSHZ
+  #   ZSHZ_CASE
+  #   ZSHZ_KEEP_DIRS
+  #   ZSHZ_OWNER
+  #
+  # Arguments:
+  #   #1 Pattern to match
+  #   $2 Matching method (rank, time, or [default] frecency)
+  #   $3 Output format (completion, list, or [default] store
+  #     in REPLY
+  ############################################################
+  _zshz_find_matches() {
+    setopt LOCAL_OPTIONS NO_EXTENDED_GLOB
+
+    local fnd=$1 method=$2 format=$3
+
+    local -a existing_paths
+    local line dir path_field rank_field time_field rank dx escaped_path_field
+    local -A matches imatches
+    local best_match ibest_match hi_rank=-9999999999 ihi_rank=-9999999999
+
+    # Remove paths from database if they no longer exist
+    for line in $lines; do
+      if [[ ! -d ${line%%\|*} ]]; then
+        for dir in ${(@)ZSHZ_KEEP_DIRS}; do
+          if [[ ${line%%\|*} == ${dir}/* ||
+                ${line%%\|*} == $dir     ||
+                $dir == '/' ]]; then
+            existing_paths+=( $line )
+          fi
+        done
+      else
+        existing_paths+=( $line )
+      fi
+    done
+    lines=( $existing_paths )
+
+    for line in $lines; do
+      path_field=${line%%\|*}
+      rank_field=${${line%\|*}#*\|}
+      time_field=${line##*\|}
+
+      case $method in
+        rank) rank=$rank_field ;;
+        time) (( rank = time_field - EPOCHSECONDS )) ;;
+        *)
+          # Frecency routine
+          (( dx = EPOCHSECONDS - time_field ))
+          rank=$(( 10000 * rank_field * (3.75/((0.0001 * dx + 1) + 0.25)) ))
+          ;;
+      esac
+
+      # Use spaces as wildcards
+      local q=${fnd//[[:space:]]/\*}
+
+      # If $ZSHZ_TRAILING_SLASH is set, use path_field with a trailing slash for matching.
+      local path_field_normalized=$path_field
+      if (( ZSHZ_TRAILING_SLASH )); then
+        path_field_normalized=${path_field%/}/
+      fi
+
+      # If $ZSHZ_CASE is 'ignore', be case-insensitive.
+      #
+      # If it's 'smart', be case-insensitive unless the string to be matched
+      # includes capital letters.
+      #
+      # Otherwise, the default behavior of Zsh-z is to match case-sensitively if
+      # possible, then to fall back on a case-insensitive match if possible.
+      if [[ $ZSHZ_CASE == 'smart' && ${1:l} == $1 &&
+            ${path_field_normalized:l} == ${~q:l} ]]; then
+        imatches[$path_field]=$rank
+      elif [[ $ZSHZ_CASE != 'ignore' && $path_field_normalized == ${~q} ]]; then
+        matches[$path_field]=$rank
+      elif [[ $ZSHZ_CASE != 'smart' && ${path_field_normalized:l} == ${~q:l} ]]; then
+        imatches[$path_field]=$rank
+      fi
+
+      # Escape characters that would cause "invalid subscript" errors
+      # when accessing the associative array.
+      escaped_path_field=${path_field//'\'/'\\'}
+      escaped_path_field=${escaped_path_field//'`'/'\`'}
+      escaped_path_field=${escaped_path_field//'('/'\('}
+      escaped_path_field=${escaped_path_field//')'/'\)'}
+      escaped_path_field=${escaped_path_field//'['/'\['}
+      escaped_path_field=${escaped_path_field//']'/'\]'}
+
+      if (( matches[$escaped_path_field] )) &&
+         (( matches[$escaped_path_field] > hi_rank )); then
+        best_match=$path_field
+        hi_rank=${matches[$escaped_path_field]}
+      elif (( imatches[$escaped_path_field] )) &&
+           (( imatches[$escaped_path_field] > ihi_rank )); then
+        ibest_match=$path_field
+        ihi_rank=${imatches[$escaped_path_field]}
+        ZSHZ[CASE_INSENSITIVE]=1
+      fi
+    done
+
+    # Return 1 when there are no matches
+    [[ -z $best_match && -z $ibest_match ]] && return 1
+
+    if [[ -n $best_match ]]; then
+      _zshz_output matches best_match $format
+    elif [[ -n $ibest_match ]]; then
+      _zshz_output imatches ibest_match $format
+    fi
+  }
+
+  # THE MAIN ROUTINE
+
+  local -A opts
+
+  zparseopts -E -D -A opts -- \
+    -add \
+    -complete \
+    c \
+    e \
+    h \
+    -help \
+    l \
+    r \
+    R \
+    t \
+    x
+
+  if [[ $1 == '--' ]]; then
+    shift
+  elif [[ -n ${(M)@:#-*} && -z $compstate ]]; then
+    print "Improper option(s) given."
+    _zshz_usage
+    return 1
+  fi
+
+  local opt output_format method='frecency' fnd prefix req
+
+  for opt in ${(k)opts}; do
+    case $opt in
+      --add)
+        [[ ! -d $* ]] && return 1
+        local dir
+        # Cygwin and MSYS2 have a hard time with relative paths expressed from /
+        if [[ $OSTYPE == (cygwin|msys) && $PWD == '/' && $* != /* ]]; then
+          set -- "/$*"
+        fi
+        if (( ${ZSHZ_NO_RESOLVE_SYMLINKS:-${_Z_NO_RESOLVE_SYMLINKS}} )); then
+          dir=${*:a}
+        else
+          dir=${*:A}
+        fi
+        _zshz_add_or_remove_path --add "$dir"
+        return
+        ;;
+      --complete)
+        if [[ -s $datafile && ${ZSHZ_COMPLETION:-frecent} == 'legacy' ]]; then
+          _zshz_legacy_complete "$1"
+          return
+        fi
+        output_format='completion'
+        ;;
+      -c) [[ $* == ${PWD}/* || $PWD == '/' ]] || prefix="$PWD " ;;
+      -h|--help)
+        _zshz_usage
+        return
+        ;;
+      -l) output_format='list' ;;
+      -r) method='rank' ;;
+      -t) method='time' ;;
+      -x)
+        # Cygwin and MSYS2 have a hard time with relative paths expressed from /
+        if [[ $OSTYPE == (cygwin|msys) && $PWD == '/' && $* != /* ]]; then
+          set -- "/$*"
+        fi
+        _zshz_add_or_remove_path --remove $*
+        return
+        ;;
+    esac
+  done
+  req="$*"
+  fnd="$prefix$*"
+
+  [[ -n $fnd && $fnd != "$PWD " ]] || {
+    [[ $output_format != 'completion' ]] && output_format='list'
+  }
+
+  #########################################################
+  # If $ZSHZ_ECHO == 1, display paths as you jump to them.
+  # If it is also the case that $ZSHZ_TILDE == 1, display
+  # the home directory as a tilde.
+  #########################################################
+  _zshz_echo() {
+    if (( ZSHZ_ECHO )); then
+      if (( ZSHZ_TILDE )); then
+        print ${PWD/#${HOME}/\~}
+      else
+        print $PWD
+      fi
+    fi
+  }
+
+  if [[ ${@: -1} == /* ]] && (( ! $+opts[-e] && ! $+opts[-l] )); then
+    # cd if possible; echo the new path if $ZSHZ_ECHO == 1
+    [[ -d ${@: -1} ]] && builtin cd ${@: -1} && _zshz_echo && return
+  fi
+
+  # With option -c, make sure query string matches beginning of matches;
+  # otherwise look for matches anywhere in paths
+
+  # zpm-zsh/colors has a global $c, so we'll avoid math expressions here
+  if [[ ! -z ${(tP)opts[-c]} ]]; then
+    _zshz_find_matches "$fnd*" $method $output_format
+  else
+    _zshz_find_matches "*$fnd*" $method $output_format
+  fi
+
+  local ret2=$?
+
+  local cd
+  cd=$REPLY
+
+  # New experimental "uncommon" behavior
+  #
+  # If the best choice at this point is something like /foo/bar/foo/bar, and the  # search pattern is `bar', go to /foo/bar/foo/bar; but if the search pattern
+  # is `foo', go to /foo/bar/foo
+  if (( ZSHZ_UNCOMMON )) && [[ -n $cd ]]; then
+    if [[ -n $cd ]]; then
+
+      # In the search pattern, replace spaces with *
+      local q=${fnd//[[:space:]]/\*}
+      q=${q%/} # Trailing slash has to be removed
+
+      # As long as the best match is not case-insensitive
+      if (( ! ZSHZ[CASE_INSENSITIVE] )); then
+        # Count the number of characters in $cd that $q matches
+        local q_chars=$(( ${#cd} - ${#${cd//${~q}/}} ))
+        # Try dropping directory elements from the right; stop when it affects
+        # how many times the search pattern appears
+        until (( ( ${#cd:h} - ${#${${cd:h}//${~q}/}} ) != q_chars )); do
+          cd=${cd:h}
+        done
+
+      # If the best match is case-insensitive
+      else
+        local q_chars=$(( ${#cd} - ${#${${cd:l}//${~${q:l}}/}} ))
+        until (( ( ${#cd:h} - ${#${${${cd:h}:l}//${~${q:l}}/}} ) != q_chars )); do
+          cd=${cd:h}
+        done
+      fi
+
+      ZSHZ[CASE_INSENSITIVE]=0
+    fi
+  fi
+
+  if (( ret2 == 0 )) && [[ -n $cd ]]; then
+    if (( $+opts[-e] )); then               # echo
+      (( ZSHZ_TILDE )) && cd=${cd/#${HOME}/\~}
+      print -- "$cd"
+    else
+      # cd if possible; echo the new path if $ZSHZ_ECHO == 1
+      [[ -d $cd ]] && builtin cd "$cd" && _zshz_echo
+    fi
+  else
+    # if $req is a valid path, cd to it; echo the new path if $ZSHZ_ECHO == 1
+    if ! (( $+opts[-e] || $+opts[-l] )) && [[ -d $req ]]; then
+      builtin cd "$req" && _zshz_echo
+    else
+      return $ret2
+    fi
+  fi
+}
+
+alias ${ZSHZ_CMD:-${_Z_CMD:-z}}='zshz 2>&1'
+
+############################################################
+# precmd - add path to datafile unless `z -x' has just been
+#   run
+#
+# Globals:
+#   ZSHZ
+############################################################
+_zshz_precmd() {
+  # Do not add PWD to datafile when in HOME directory, or
+  # if `z -x' has just been run
+  [[ $PWD == "$HOME" ]] || (( ZSHZ[DIRECTORY_REMOVED] )) && return
+
+  # Don't track directory trees excluded in ZSHZ_EXCLUDE_DIRS
+  local exclude
+  for exclude in ${(@)ZSHZ_EXCLUDE_DIRS:-${(@)_Z_EXCLUDE_DIRS}}; do
+    case $PWD in
+      ${exclude}|${exclude}/*) return ;;
+    esac
+  done
+
+  # It appears that forking a subshell is so slow in Windows that it is better
+  # just to add the PWD to the datafile in the foreground
+  if [[ $OSTYPE == (cygwin|msys) ]]; then
+      zshz --add "$PWD"
+  else
+      (zshz --add "$PWD" &)
+  fi
+
+  # See https://github.com/rupa/z/pull/247/commits/081406117ea42ccb8d159f7630cfc7658db054b6
+  : $RANDOM
+}
+
+############################################################
+# chpwd
+#
+# When the $PWD is removed from the datafile with `z -x',
+# Zsh-z refrains from adding it again until the user has
+# left the directory.
+#
+# Globals:
+#   ZSHZ
+############################################################
+_zshz_chpwd() {
+  ZSHZ[DIRECTORY_REMOVED]=0
+}
+
+autoload -Uz add-zsh-hook
+
+add-zsh-hook precmd _zshz_precmd
+add-zsh-hook chpwd _zshz_chpwd
+
+############################################################
+# Completion
+############################################################
+
+# Standarized $0 handling
+# (See https://github.com/agkozak/Zsh-100-Commits-Club/blob/master/Zsh-Plugin-Standard.adoc)
+0=${${ZERO:-${0:#$ZSH_ARGZERO}}:-${(%):-%N}}
+0=${${(M)0:#/*}:-$PWD/$0}
+
+(( ${fpath[(ie)${0:A:h}]} <= ${#fpath} )) || fpath=( "${0:A:h}" "${fpath[@]}" )
+
+############################################################
+# zsh-z functions
+############################################################
+ZSHZ[FUNCTIONS]='_zshz_usage
+                 _zshz_add_or_remove_path
+                 _zshz_update_datafile
+                 _zshz_legacy_complete
+                 _zshz_printv
+                 _zshz_find_common_root
+                 _zshz_output
+                 _zshz_find_matches
+                 zshz
+                 _zshz_precmd
+                 _zshz_chpwd
+                 _zshz'
+
+############################################################
+# Enable WARN_NESTED_VAR for functions listed in
+#   ZSHZ[FUNCTIONS]
+############################################################
+(( ZSHZ_DEBUG )) && () {
+  if is-at-least 5.4.0; then
+    local x
+    for x in ${=ZSHZ[FUNCTIONS]}; do
+      functions -W $x
+    done
+  fi
+}
+
+############################################################
+# Unload function
+#
+# See https://github.com/agkozak/Zsh-100-Commits-Club/blob/master/Zsh-Plugin-Standard.adoc#unload-fun
+#
+# Globals:
+#   ZSHZ
+#   ZSHZ_CMD
+############################################################
+zsh-z_plugin_unload() {
+  emulate -L zsh
+
+  add-zsh-hook -D precmd _zshz_precmd
+  add-zsh-hook -d chpwd _zshz_chpwd
+
+  local x
+  for x in ${=ZSHZ[FUNCTIONS]}; do
+    (( ${+functions[$x]} )) && unfunction $x
+  done
+
+  unset ZSHZ
+
+  fpath=( "${(@)fpath:#${0:A:h}}" )
+
+  (( ${+aliases[${ZSHZ_CMD:-${_Z_CMD:-z}}]} )) &&
+    unalias ${ZSHZ_CMD:-${_Z_CMD:-z}}
+
+  unfunction $0
+}
+
+# vim: fdm=indent:ts=2:et:sts=2:sw=2:

+ 0 - 267
plugins/z/z.sh

@@ -1,267 +0,0 @@
-# Copyright (c) 2009 rupa deadwyler. Licensed under the WTFPL license, Version 2
-
-# maintains a jump-list of the directories you actually use
-#
-# INSTALL:
-#     * put something like this in your .bashrc/.zshrc:
-#         . /path/to/z.sh
-#     * cd around for a while to build up the db
-#     * PROFIT!!
-#     * optionally:
-#         set $_Z_CMD in .bashrc/.zshrc to change the command (default z).
-#         set $_Z_DATA in .bashrc/.zshrc to change the datafile (default ~/.z).
-#         set $_Z_MAX_SCORE lower to age entries out faster (default 9000).
-#         set $_Z_NO_RESOLVE_SYMLINKS to prevent symlink resolution.
-#         set $_Z_NO_PROMPT_COMMAND if you're handling PROMPT_COMMAND yourself.
-#         set $_Z_EXCLUDE_DIRS to an array of directories to exclude.
-#         set $_Z_OWNER to your username if you want use z while sudo with $HOME kept
-#
-# USE:
-#     * z foo     # cd to most frecent dir matching foo
-#     * z foo bar # cd to most frecent dir matching foo and bar
-#     * z -r foo  # cd to highest ranked dir matching foo
-#     * z -t foo  # cd to most recently accessed dir matching foo
-#     * z -l foo  # list matches instead of cd
-#     * z -e foo  # echo the best match, don't cd
-#     * z -c foo  # restrict matches to subdirs of $PWD
-#     * z -x      # remove the current directory from the datafile
-#     * z -h      # show a brief help message
-
-[ -d "${_Z_DATA:-$HOME/.z}" ] && {
-    echo "ERROR: z.sh's datafile (${_Z_DATA:-$HOME/.z}) is a directory."
-}
-
-_z() {
-
-    local datafile="${_Z_DATA:-$HOME/.z}"
-
-    # if symlink, dereference
-    [ -h "$datafile" ] && datafile=$(readlink "$datafile")
-
-    # bail if we don't own ~/.z and $_Z_OWNER not set
-    [ -z "$_Z_OWNER" -a -f "$datafile" -a ! -O "$datafile" ] && return
-
-    _z_dirs () {
-        [ -f "$datafile" ] || return
-
-        local line
-        while read line; do
-            # only count directories
-            [ -d "${line%%\|*}" ] && echo "$line"
-        done < "$datafile"
-        return 0
-    }
-
-    # add entries
-    if [ "$1" = "--add" ]; then
-        shift
-
-        # $HOME and / aren't worth matching
-        [ "$*" = "$HOME" -o "$*" = '/' ] && return
-
-        # don't track excluded directory trees
-        if [ ${#_Z_EXCLUDE_DIRS[@]} -gt 0 ]; then
-            local exclude
-            for exclude in "${_Z_EXCLUDE_DIRS[@]}"; do
-                case "$*" in "$exclude"*) return;; esac
-            done
-        fi
-
-        # maintain the data file
-        local tempfile="$datafile.$RANDOM"
-        local score=${_Z_MAX_SCORE:-9000}
-        _z_dirs | awk -v path="$*" -v now="$(date +%s)" -v score=$score -F"|" '
-            BEGIN {
-                rank[path] = 1
-                time[path] = now
-            }
-            $2 >= 1 {
-                # drop ranks below 1
-                if( $1 == path ) {
-                    rank[$1] = $2 + 1
-                    time[$1] = now
-                } else {
-                    rank[$1] = $2
-                    time[$1] = $3
-                }
-                count += $2
-            }
-            END {
-                if( count > score ) {
-                    # aging
-                    for( x in rank ) print x "|" 0.99*rank[x] "|" time[x]
-                } else for( x in rank ) print x "|" rank[x] "|" time[x]
-            }
-        ' 2>/dev/null >| "$tempfile"
-        # do our best to avoid clobbering the datafile in a race condition.
-        if [ $? -ne 0 -a -f "$datafile" ]; then
-            env rm -f "$tempfile"
-        else
-            [ "$_Z_OWNER" ] && chown $_Z_OWNER:"$(id -ng $_Z_OWNER)" "$tempfile"
-            env mv -f "$tempfile" "$datafile" || env rm -f "$tempfile"
-        fi
-
-    # tab completion
-    elif [ "$1" = "--complete" -a -s "$datafile" ]; then
-        _z_dirs | awk -v q="$2" -F"|" '
-            BEGIN {
-                q = substr(q, 3)
-                if( q == tolower(q) ) imatch = 1
-                gsub(/ /, ".*", q)
-            }
-            {
-                if( imatch ) {
-                    if( tolower($1) ~ q ) print $1
-                } else if( $1 ~ q ) print $1
-            }
-        ' 2>/dev/null
-
-    else
-        # list/go
-        local echo fnd last list opt typ
-        while [ "$1" ]; do case "$1" in
-            --) while [ "$1" ]; do shift; fnd="$fnd${fnd:+ }$1";done;;
-            -*) opt=${1:1}; while [ "$opt" ]; do case ${opt:0:1} in
-                    c) fnd="^$PWD $fnd";;
-                    e) echo=1;;
-                    h) echo "${_Z_CMD:-z} [-cehlrtx] args" >&2; return;;
-                    l) list=1;;
-                    r) typ="rank";;
-                    t) typ="recent";;
-                    x) sed -i -e "\:^${PWD}|.*:d" "$datafile";;
-                esac; opt=${opt:1}; done;;
-             *) fnd="$fnd${fnd:+ }$1";;
-        esac; last=$1; [ "$#" -gt 0 ] && shift; done
-        [ "$fnd" -a "$fnd" != "^$PWD " ] || list=1
-
-        # if we hit enter on a completion just go there
-        case "$last" in
-            # completions will always start with /
-            /*) [ -z "$list" -a -d "$last" ] && builtin cd "$last" && return;;
-        esac
-
-        # no file yet
-        [ -f "$datafile" ] || return
-
-        local cd
-        cd="$( < <( _z_dirs ) awk -v t="$(date +%s)" -v list="$list" -v typ="$typ" -v q="$fnd" -F"|" '
-            function frecent(rank, time) {
-              # relate frequency and time
-              dx = t - time
-              return int(10000 * rank * (3.75/((0.0001 * dx + 1) + 0.25)))
-            }
-            function output(matches, best_match, common) {
-                # list or return the desired directory
-                if( list ) {
-                    if( common ) {
-                        printf "%-10s %s\n", "common:", common > "/dev/stderr"
-                    }
-                    cmd = "sort -n >&2"
-                    for( x in matches ) {
-                        if( matches[x] ) {
-                            printf "%-10s %s\n", matches[x], x | cmd
-                        }
-                    }
-                } else {
-                    if( common && !typ ) best_match = common
-                    print best_match
-                }
-            }
-            function common(matches) {
-                # find the common root of a list of matches, if it exists
-                for( x in matches ) {
-                    if( matches[x] && (!short || length(x) < length(short)) ) {
-                        short = x
-                    }
-                }
-                if( short == "/" ) return
-                for( x in matches ) if( matches[x] && index(x, short) != 1 ) {
-                    return
-                }
-                return short
-            }
-            BEGIN {
-                gsub(" ", ".*", q)
-                hi_rank = ihi_rank = -9999999999
-            }
-            {
-                if( typ == "rank" ) {
-                    rank = $2
-                } else if( typ == "recent" ) {
-                    rank = $3 - t
-                } else rank = frecent($2, $3)
-                if( $1 ~ q ) {
-                    matches[$1] = rank
-                } else if( tolower($1) ~ tolower(q) ) imatches[$1] = rank
-                if( matches[$1] && matches[$1] > hi_rank ) {
-                    best_match = $1
-                    hi_rank = matches[$1]
-                } else if( imatches[$1] && imatches[$1] > ihi_rank ) {
-                    ibest_match = $1
-                    ihi_rank = imatches[$1]
-                }
-            }
-            END {
-                # prefer case sensitive
-                if( best_match ) {
-                    output(matches, best_match, common(matches))
-                    exit
-                } else if( ibest_match ) {
-                    output(imatches, ibest_match, common(imatches))
-                    exit
-                }
-                exit(1)
-            }
-        ')"
-
-        if [ "$?" -eq 0 ]; then
-          if [ "$cd" ]; then
-            if [ "$echo" ]; then echo "$cd"; else builtin cd "$cd"; fi
-          fi
-        else
-          return $?
-        fi
-    fi
-}
-
-alias ${_Z_CMD:-z}='_z 2>&1'
-
-[ "$_Z_NO_RESOLVE_SYMLINKS" ] || _Z_RESOLVE_SYMLINKS="-P"
-
-if type compctl >/dev/null 2>&1; then
-    # zsh
-    [ "$_Z_NO_PROMPT_COMMAND" ] || {
-        # populate directory list, avoid clobbering any other precmds.
-        if [ "$_Z_NO_RESOLVE_SYMLINKS" ]; then
-            _z_precmd() {
-                (_z --add "${PWD:a}" &)
-                : $RANDOM
-            }
-        else
-            _z_precmd() {
-                (_z --add "${PWD:A}" &)
-                : $RANDOM
-            }
-        fi
-        [[ -n "${precmd_functions[(r)_z_precmd]}" ]] || {
-            precmd_functions[$(($#precmd_functions+1))]=_z_precmd
-        }
-    }
-    _z_zsh_tab_completion() {
-        # tab completion
-        local compl
-        read -l compl
-        reply=(${(f)"$(_z --complete "$compl")"})
-    }
-    compctl -U -K _z_zsh_tab_completion _z
-elif type complete >/dev/null 2>&1; then
-    # bash
-    # tab completion
-    complete -o filenames -C '_z --complete "$COMP_LINE"' ${_Z_CMD:-z}
-    [ "$_Z_NO_PROMPT_COMMAND" ] || {
-        # populate directory list. avoid clobbering other PROMPT_COMMANDs.
-        grep "_z --add" <<< "$PROMPT_COMMAND" >/dev/null || {
-            PROMPT_COMMAND="$PROMPT_COMMAND"$'\n''(_z --add "$(command pwd '$_Z_RESOLVE_SYMLINKS' 2>/dev/null)" 2>/dev/null &);'
-        }
-    }
-fi