12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001 |
- ################################################################################
- # 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-2023 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_CD -> the directory-changing command that is used (default: builtin cd)
- # 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)
- ################################################################################
- 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_CD
- # 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 a custom datafile in $ZSHZ_DATA (or legacy $_Z_DATA)
- local custom_datafile="${ZSHZ_DATA:-$_Z_DATA}"
- # If a datafile was provided as a standalone file without a directory path
- # print a warning and exit
- if [[ -n ${custom_datafile} && ${custom_datafile} != */* ]]; then
- print "ERROR: You configured a custom Zsh-z datafile (${custom_datafile}), but have not specified its directory." >&2
- exit
- fi
- # If the user specified a datafile, use that or default to ~/.z
- # If the datafile is a symlink, it gets dereferenced
- local datafile=${${custom_datafile:-$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 ]] || { mkdir -p "${datafile:h}" && 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'
- }
- #########################################################
- # Allow the user to specify directory-changing command
- # using $ZSHZ_CD (default: builtin cd).
- #
- # Globals:
- # ZSHZ_CD
- #
- # Arguments:
- # $* Path
- #########################################################
- zshz_cd() {
- setopt LOCAL_OPTIONS NO_WARN_CREATE_GLOBAL
- if [[ -z $ZSHZ_CD ]]; then
- builtin cd "$*"
- else
- ${=ZSHZ_CD} "$*"
- fi
- }
- #########################################################
- # 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} ]] && zshz_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 ]] && zshz_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
- zshz_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
- # https://zdharma-continuum.github.io/Zsh-100-Commits-Club/Zsh-Plugin-Standard.html
- 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:
|