diff options
| author | Eduardo San Martin Morote <i@posva.net> | 2015-07-23 09:16:13 +0200 | 
|---|---|---|
| committer | Eduardo San Martin Morote <i@posva.net> | 2015-11-04 16:23:48 +0100 | 
| commit | d3363964a24d253baa0116d34a465bfabf797c7a (patch) | |
| tree | a219aac37ac62e8ff2b4e9fd7158d4e451de0e14 | |
| parent | e44aa50301cbdb9c713193263e6c0c5a9a5798c0 (diff) | |
| download | zsh-d3363964a24d253baa0116d34a465bfabf797c7a.tar.gz zsh-d3363964a24d253baa0116d34a465bfabf797c7a.tar.bz2 zsh-d3363964a24d253baa0116d34a465bfabf797c7a.zip | |
Updated z version
Closes #3248
Closes #4570
| -rw-r--r-- | plugins/z/README | 43 | ||||
| -rw-r--r-- | plugins/z/z.1 | 35 | ||||
| -rw-r--r-- | plugins/z/z.sh | 425 | 
3 files changed, 269 insertions, 234 deletions
| diff --git a/plugins/z/README b/plugins/z/README index ec5abc6f5..7de82a4c7 100644 --- a/plugins/z/README +++ b/plugins/z/README @@ -6,7 +6,7 @@ NAME         z - jump around  SYNOPSIS -       z [-chlrt] [regex1 regex2 ... regexn] +       z [-chlrtx] [regex1 regex2 ... regexn]  AVAILABILITY         bash, zsh @@ -15,10 +15,13 @@ 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. +       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. +       -c     restrict matches to subdirectories of the current directory         -h     show a brief help message @@ -28,10 +31,12 @@ OPTIONS         -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 and bar +       z foo bar     cd to most frecent dir matching foo, then bar         z -r foo      cd to highest ranked dir matching foo @@ -55,8 +60,9 @@ NOTES                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 directories to exclude. -              (These  settings  should  go  in .bashrc/.zshrc before the lines +              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    like                /usr/local/man/man1. @@ -64,12 +70,12 @@ NOTES     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 greater than 6000, all ranks are -       multiplied by 0.99. Entries with a rank lower than 1 are forgotten. +       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 portmantaeu of 'recent' and 'frequency'. It is a weighted -       rank  that  depends on how often and how recently something occured. As +       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 @@ -107,20 +113,23 @@ ENVIRONMENT         resolving  of  symlinks.  If  it  is  not  set,  symbolic links will be         resolved when added to the datafile. -       In bash, z prepends a command to the PROMPT_COMMAND  environment  vari- -       able  to  maintain its database. In zsh, z appends a function _z_precmd -       to the precmd_functions array. +       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 -       directories to exclude from tracking. $HOME is always excluded.  Direc- -       tories must be full paths without trailing slashes. +       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 enviroment 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 +       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. diff --git a/plugins/z/z.1 b/plugins/z/z.1 index 022a4b35d..cc99910bf 100644 --- a/plugins/z/z.1 +++ b/plugins/z/z.1 @@ -4,7 +4,7 @@ NAME  z \- jump around  .SH  SYNOPSIS -z [\-chlrt] [regex1 regex2 ... regexn] +z [\-chlrtx] [regex1 regex2 ... regexn]  .SH  AVAILABILITY  bash, zsh @@ -13,12 +13,14 @@ 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. +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. +restrict matches to subdirectories of the current directory  .TP  \fB\-h\fR  show a brief help message @@ -31,13 +33,16 @@ 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 and bar +cd to most frecent dir matching foo, then bar  .TP 14  \fBz -r foo\fR  cd to highest ranked dir matching foo @@ -76,10 +81,13 @@ Set \fB$_Z_NO_RESOLVE_SYMLINKS\fR to prevent symlink resolution.  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 directories to exclude. +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 lines added above.) +(These settings should go in .bashrc/.zshrc before the line added above.)  .RE  .RS  Install the provided man page \fBz.1\fR somewhere like \fB/usr/local/man/man1\fR. @@ -88,12 +96,12 @@ Install the provided man page \fBz.1\fR somewhere like \fB/usr/local/man/man1\fR  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 greater than 6000, all ranks are multiplied by 0.99. Entries -with a rank lower than 1 are forgotten. +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 portmantaeu of 'recent' and 'frequency'. It is a weighted rank -that depends on how often and how recently something occured. As far as I +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 @@ -131,7 +139,7 @@ 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 prepends a command to the \fBPROMPT_COMMAND\fR environment +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 @@ -139,8 +147,11 @@ 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 -directories to exclude from tracking. \fB$HOME\fR is always excluded. +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 enviroment keeps \fB$HOME\fR set.  .SH  FILES  Data is stored in \fB$HOME/.z\fR. This can be overridden by setting the diff --git a/plugins/z/z.sh b/plugins/z/z.sh index 7e444ef46..d0eeb97ef 100644 --- a/plugins/z/z.sh +++ b/plugins/z/z.sh @@ -3,29 +3,25 @@  # 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_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. +#     * 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_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 -c foo  # restrict matches to subdirs of $PWD - -case $- in - *i*) ;; -   *) echo 'ERROR: z.sh is meant to be sourced, not directly executed.' -esac +#     * 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 -c foo  # restrict matches to subdirs of $PWD  [ -d "${_Z_DATA:-$HOME/.z}" ] && {      echo "ERROR: z.sh's datafile (${_Z_DATA:-$HOME/.z}) is a directory." @@ -33,196 +29,215 @@ esac  _z() { - local datafile="${_Z_DATA:-$HOME/.z}" - - # bail out if we don't own ~/.z (we're another user but our ENV is still set) - [ -f "$datafile" -a ! -O "$datafile" ] && return - - # add entries - if [ "$1" = "--add" ]; then -  shift - -  # $HOME isn't worth matching -  [ "$*" = "$HOME" ] && return - -  # don't track excluded dirs -  local exclude -  for exclude in "${_Z_EXCLUDE_DIRS[@]}"; do -   [ "$*" = "$exclude" ] && return -  done - -  # maintain the file -  local tempfile -  tempfile="$(mktemp "$datafile.XXXXXX")" || return -  while read line; do -   [ -d "${line%%\|*}" ] && echo $line -  done < "$datafile" | awk -v path="$*" -v now="$(date +%s)" -F"|" ' -   BEGIN { -    rank[path] = 1 -    time[path] = now -   } -   $2 >= 1 { -    if( $1 == path ) { -     rank[$1] = $2 + 1 -     time[$1] = now -    } else { -     rank[$1] = $2 -     time[$1] = $3 -    } -    count += $2 -   } -   END { -    if( count > 6000 ) { -     for( i in rank ) print i "|" 0.99*rank[i] "|" time[i] # aging -    } else for( i in rank ) print i "|" rank[i] "|" time[i] -   } -  ' 2>/dev/null >| "$tempfile" -  if [ $? -ne 0 -a -f "$datafile" ]; then -   env rm -f "$tempfile" -  else -   env mv -f "$tempfile" "$datafile" -  fi - - # tab completion - elif [ "$1" = "--complete" ]; then -  while read line; do -   [ -d "${line%%\|*}" ] && echo $line -  done < "$datafile" | awk -v q="$2" -F"|" ' -   BEGIN { -    if( q == tolower(q) ) nocase = 1 -    split(substr(q,3),fnd," ") -   } -   { -    if( nocase ) { -     for( i in fnd ) tolower($1) !~ tolower(fnd[i]) && $1 = "" -    } else { -     for( i in fnd ) $1 !~ fnd[i] && $1 = "" -    } -    if( $1 ) print $1 -   } -  ' 2>/dev/null - - else -  # list/go -  while [ "$1" ]; do case "$1" in -   --) while [ "$1" ]; do shift; local fnd="$fnd $1";done;; -   -*) local opt=${1:1}; while [ "$opt" ]; do case ${opt:0:1} in -        c) local fnd="^$PWD $fnd";; -        h) echo "${_Z_CMD:-z} [-chlrt] args" >&2; return;; -        l) local list=1;; -        r) local typ="rank";; -        t) local typ="recent";; -       esac; opt=${opt:1}; done;; -    *) local fnd="$fnd $1";; -  esac; local last=$1; shift; done -  [ "$fnd" -a "$fnd" != "^$PWD " ] || local 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" ] && cd "$last" && return;; -  esac - -  # no file yet -  [ -f "$datafile" ] || return - -  local cd -  cd="$(while read line; do -   [ -d "${line%%\|*}" ] && echo $line -  done < "$datafile" | awk -v t="$(date +%s)" -v list="$list" -v typ="$typ" -v q="$fnd" -F"|" ' -   function frecent(rank, time) { -    dx = t-time -    if( dx < 3600 ) return rank*4 -    if( dx < 86400 ) return rank*2 -    if( dx < 604800 ) return rank/2 -    return rank/4 -   } -   function output(files, toopen, override) { -    if( list ) { -     cmd = "sort -n >&2" -     for( i in files ) if( files[i] ) printf "%-10s %s\n", files[i], i | cmd -     if( override ) printf "%-10s %s\n", "common:", override > "/dev/stderr" -    } else { -     if( override ) toopen = override -     print toopen -    } -   } -   function common(matches) { -    # shortest match -    for( i in matches ) { -     if( matches[i] && (!short || length(i) < length(short)) ) short = i -    } -    if( short == "/" ) return -    # shortest match must be common to each match. escape special characters in -    # a copy when testing, so we can return the original. -    clean_short = short -    gsub(/[\(\)\[\]\|]/, "\\\\&", clean_short) -    for( i in matches ) if( matches[i] && i !~ clean_short ) return -    return short -   } -   BEGIN { split(q, a, " "); oldf = noldf = -9999999999 } -   { -    if( typ == "rank" ) { -     f = $2 -    } else if( typ == "recent" ) { -     f = $3-t -    } else f = frecent($2, $3) -    wcase[$1] = nocase[$1] = f -    for( i in a ) { -     if( $1 !~ a[i] ) delete wcase[$1] -     if( tolower($1) !~ tolower(a[i]) ) delete nocase[$1] -    } -    if( wcase[$1] && wcase[$1] > oldf ) { -     cx = $1 -     oldf = wcase[$1] -    } else if( nocase[$1] && nocase[$1] > noldf ) { -     ncx = $1 -     noldf = nocase[$1] -    } -   } -   END { -    if( cx ) { -     output(wcase, cx, common(wcase)) -    } else if( ncx ) output(nocase, ncx, common(nocase)) -   } -  ')" -  [ $? -gt 0 ] && return -  [ "$cd" ] && cd "$cd" - fi +    local datafile="${_Z_DATA:-$HOME/.z}" + +    # bail if we don't own ~/.z and $_Z_OWNER not set +    [ -z "$_Z_OWNER" -a -f "$datafile" -a ! -O "$datafile" ] && return + +    # add entries +    if [ "$1" = "--add" ]; then +        shift + +        # $HOME isn't worth matching +        [ "$*" = "$HOME" ] && return + +        # don't track excluded directory trees +        local exclude +        for exclude in "${_Z_EXCLUDE_DIRS[@]}"; do +            case "$*" in "$exclude*") return;; esac +        done + +        # maintain the data file +        local tempfile="$datafile.$RANDOM" +        while read line; do +            # only count directories +            [ -d "${line%%\|*}" ] && echo $line +        done < "$datafile" | awk -v path="$*" -v now="$(date +%s)" -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 > 9000 ) { +                    # 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 +        while read line; do +            [ -d "${line%%\|*}" ] && echo $line +        done < "$datafile" | awk -v q="$2" -F"|" ' +            BEGIN { +                if( q == tolower(q) ) imatch = 1 +                q = substr(q, 3) +                gsub(" ", ".*", q) +            } +            { +                if( imatch ) { +                    if( tolower($1) ~ tolower(q) ) print $1 +                } else if( $1 ~ q ) print $1 +            } +        ' 2>/dev/null + +    else +        # list/go +        while [ "$1" ]; do case "$1" in +            --) while [ "$1" ]; do shift; local fnd="$fnd${fnd:+ }$1";done;; +            -*) local opt=${1:1}; while [ "$opt" ]; do case ${opt:0:1} in +                    c) local fnd="^$PWD $fnd";; +                    h) echo "${_Z_CMD:-z} [-chlrtx] args" >&2; return;; +                    x) sed -i -e "\:^${PWD}|.*:d" "$datafile";; +                    l) local list=1;; +                    r) local typ="rank";; +                    t) local typ="recent";; +                esac; opt=${opt:1}; done;; +             *) local fnd="$fnd${fnd:+ }$1";; +        esac; local last=$1; [ "$#" -gt 0 ] && shift; done +        [ "$fnd" -a "$fnd" != "^$PWD " ] || local 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" ] && cd "$last" && return;; +        esac + +        # no file yet +        [ -f "$datafile" ] || return + +        local cd +        cd="$(while read line; do +            [ -d "${line%%\|*}" ] && echo $line +        done < "$datafile" | 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 +                if( dx < 3600 ) return rank * 4 +                if( dx < 86400 ) return rank * 2 +                if( dx < 604800 ) return rank / 2 +                return rank / 4 +            } +            function output(files, out, common) { +                # list or return the desired directory +                if( list ) { +                    cmd = "sort -n >&2" +                    for( x in files ) { +                        if( files[x] ) printf "%-10s %s\n", files[x], x | cmd +                    } +                    if( common ) { +                        printf "%-10s %s\n", "common:", common > "/dev/stderr" +                    } +                } else { +                    if( common ) out = common +                    print out +                } +            } +            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 +                # use a copy to escape special characters, as we want to return +                # the original. yeah, this escaping is awful. +                clean_short = short +                gsub(/\[\(\)\[\]\|\]/, "\\\\&", clean_short) +                for( x in matches ) if( matches[x] && x !~ clean_short ) 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)) +                } else if( ibest_match ) { +                    output(imatches, ibest_match, common(imatches)) +                } +            } +        ')" +        [ $? -gt 0 ] && return +        [ "$cd" ] && cd "$cd" +    fi  }  alias ${_Z_CMD:-z}='_z 2>&1'  [ "$_Z_NO_RESOLVE_SYMLINKS" ] || _Z_RESOLVE_SYMLINKS="-P" -if compctl &> /dev/null; then - [ "$_Z_NO_PROMPT_COMMAND" ] || { -  # zsh populate directory list, avoid clobbering any other precmds -  if [ "$_Z_NO_RESOLVE_SYMLINKS" ]; then -    _z_precmd() { -      _z --add "${PWD:a}" +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}" +            } +        else +            _z_precmd() { +                _z --add "${PWD:A}" +            } +        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")"})      } -  else -    _z_precmd() { -      _z --add "${PWD:A}" +    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 -  precmd_functions+=(_z_precmd) - } - # zsh tab completion - _z_zsh_tab_completion() { -  local compl -  read -l compl -  reply=(${(f)"$(_z --complete "$compl")"}) - } - compctl -U -K _z_zsh_tab_completion _z -elif complete &> /dev/null; then - # bash tab completion - complete -o filenames -C '_z --complete "$COMP_LINE"' ${_Z_CMD:-z} - [ "$_Z_NO_PROMPT_COMMAND" ] || { -  # bash populate directory list. avoid clobbering other PROMPT_COMMANDs. -  echo $PROMPT_COMMAND | grep -q "_z --add" || { -   PROMPT_COMMAND='_z --add "$(pwd '$_Z_RESOLVE_SYMLINKS' 2>/dev/null)" 2>/dev/null;'"$PROMPT_COMMAND" -  } - }  fi | 
