scd: update to 1.4.0 (#9066)

This commit is contained in:
Pavol Juhas 2020-08-27 00:44:25 -07:00 committed by GitHub
parent cfb86cd08d
commit 8d08f1634a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 357 additions and 120 deletions

View file

@ -14,8 +14,9 @@ directory aliases, which appear as named directories in zsh session.
## INSTALLATION NOTES ## INSTALLATION NOTES
Besides oh-my-zsh, `scd` can be used with *bash*, *dash* or *tcsh* Besides oh-my-zsh, `scd` can be used with *bash*, *dash* or *tcsh*
shells and is also available as [Vim](https://www.vim.org/) plugin and shells and is also available as Vim plugin
[IPython](https://ipython.org/) extension. For installation details, see [scd.vim](https://github.com/pavoljuhas/scd.vim) and
[IPython](https://ipython.org) extension. For installation details, see
https://github.com/pavoljuhas/smart-change-directory. https://github.com/pavoljuhas/smart-change-directory.
## SYNOPSIS ## SYNOPSIS
@ -24,11 +25,31 @@ https://github.com/pavoljuhas/smart-change-directory.
scd [options] [pattern1 pattern2 ...] scd [options] [pattern1 pattern2 ...]
``` ```
## PATTERNS
Patterns may use all zsh [glob operators](
http://zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Operators)
available with *extendedglob* option. Specified patterns must match
the absolute path and at least one of them must match in the tail.
Several special patterns are also recognized as follows:
<dl><dt>
^PAT</dt><dd>
PAT must match at the beginning of the path, for example, "^/home"</dd><dt>
PAT$</dt><dd>
require PAT to match the end of the path, "man$"</dd><dt>
./</dt><dd>
match only subdirectories of the current directory</dd><dt>
:PAT</dt><dd>
require PAT to match over the tail component, ":doc", ":re/doc"</dd>
</dl>
## OPTIONS ## OPTIONS
<dl><dt> <dl><dt>
-a, --add</dt><dd> -a, --add</dt><dd>
add specified directories to the directory index.</dd><dt> add current or specified directories to the directory index.</dd><dt>
--unindex</dt><dd> --unindex</dt><dd>
remove current or specified directories from the index.</dd><dt> remove current or specified directories from the index.</dd><dt>
@ -42,11 +63,16 @@ scd [options] [pattern1 pattern2 ...]
--unalias</dt><dd> --unalias</dt><dd>
remove ALIAS definition for the current or specified directory from remove ALIAS definition for the current or specified directory from
<em>~/.scdalias.zsh</em>.</dd><dt> <em>~/.scdalias.zsh</em>. Use "OLD" to purge aliases to non-existent
directories.</dd><dt>
-A, --all</dt><dd> -A, --all</dt><dd>
include all matching directories. Disregard matching by directory display all directories even those excluded by patterns in
alias and filtering of less likely paths.</dd><dt> <em>~/.scdignore</em>. Disregard the unique matching for a
directory alias and filtering of less likely paths.</dd><dt>
-p, --push</dt><dd>
use "pushd" to change to the target directory.</dd><dt>
--list</dt><dd> --list</dt><dd>
show matching directories and exit.</dd><dt> show matching directories and exit.</dd><dt>
@ -58,6 +84,7 @@ scd [options] [pattern1 pattern2 ...]
display this options summary and exit.</dd> display this options summary and exit.</dd>
</dl> </dl>
## Examples ## Examples
```sh ```sh
@ -83,17 +110,26 @@ scd --alias=xray
scd xray scd xray
``` ```
# FILES ## FILES
<dl><dt> <dl><dt>
~/.scdhistory</dt><dd> ~/.scdhistory</dt><dd>
time-stamped index of visited directories.</dd><dt> time-stamped index of visited directories.</dd><dt>
~/.scdalias.zsh</dt><dd> ~/.scdalias.zsh</dt><dd>
scd-generated definitions of directory aliases.</dd> scd-generated definitions of directory aliases.</dd><dt>
~/.scdignore</dt><dd>
<a href="http://zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Operators">
glob patterns</a> for paths to be ignored in the scd search, for example,
<code>/mnt/backup/*</code>. The patterns are specified one per line
and are matched assuming the <em>extendedglob</em> zsh option. Lines
starting with "#" are skipped as comments. The .scdignore patterns
are not applied in the <em>--all</em> mode.</dd>
</dl> </dl>
# ENVIRONMENT
## ENVIRONMENT
<dl><dt> <dl><dt>
SCD_HISTFILE</dt><dd> SCD_HISTFILE</dt><dd>

60
plugins/scd/_scd Normal file
View file

@ -0,0 +1,60 @@
#compdef scd
#description smart change directory
local curcontext="$curcontext" state line expl ret=1
typeset -A opt_args
local -a indexopts myargs
indexopts=( --add -a --unindex )
myargs=(
# common options
"(--help -h)"{--help,-h}"[print help and exit]"
# options for manipulating directory index
- index
"(--recursive -r)"{--recursive,-r}"[use recursive --add or --unindex]"
"($indexopts)"{--add,-a}"[add specified directories to the index]"
"($indexopts)--unindex[remove specified directories from the index]"
"*:directory:{ (( ${words[(I)-a|--add|--unindex]} )) && _path_files -/ }"
# define new directory alias
- alias
"--alias=[create alias for this or given directory]:directory-alias:()"
'1:directory:{ (( words[(I)--alias*] )) && _path_files -/ }'
# remove definition of directory alias
- unalias
"--unalias[remove definition of directory alias]"
"*::directory alias:->scd-alias-target"
# act on the directory change
- scd
"(--all -A)"{--all,-A}"[include less likely and ignored paths]"
"--list[print matching directories and exit]"
"(--verbose -v)"{--verbose,-v}"[show directory ranking and full paths]"
"(--push -p)"{--push,-p}"[change directory with 'pushd']"
"1::directory alias:->scd-alias-target"
"*:patterns:()"
)
_arguments -S -C $myargs && ret=0
if [[ "$state" == scd-alias-target && -s ~/.scdalias.zsh ]]; then
local -a scdaliases
scdaliases=( )
eval "$(setopt extendedglob
phome="(#b)(#s)${HOME}(/*)#(#e)"
builtin hash -dr
source ~/.scdalias.zsh &&
for k v in ${(kv)nameddirs}; do
scdaliases+=( $k:${v/${~phome}/"~"${match[1]}} )
done
complete_unalias=${+opt_args[unalias---unalias]}
if (( complete_unalias && ! ${+nameddirs[OLD]} )); then
scdaliases+=( 'OLD:all aliases to non-existent paths' )
fi
typeset -p scdaliases )"
_describe -t scdaliases scdalias scdaliases
fi

347
plugins/scd/scd Normal file → Executable file
View file

@ -1,29 +1,39 @@
#!/bin/zsh -f #!/bin/zsh -f
emulate -L zsh emulate -L zsh
local RUNNING_AS_COMMAND=
local EXIT=return local EXIT=return
if [[ $(whence -w $0) == *:' 'command ]]; then if [[ $(whence -w $0) == *:' 'command ]]; then
emulate -R zsh RUNNING_AS_COMMAND=1
local RUNNING_AS_COMMAND=1
EXIT=exit EXIT=exit
fi fi
local DOC='scd -- smart change to a recently used directory local DOC='scd -- smart change to a recently used directory
usage: scd [options] [pattern1 pattern2 ...] usage: scd [options] [pattern1 pattern2 ...]
Go to a directory path that contains all fixed string patterns. Prefer Go to a directory path that matches all patterns. Prefer recent or
recent or frequently visited directories as found in the directory index. frequently visited directories as found in the directory index.
Display a selection menu in case of multiple matches. Display a selection menu in case of multiple matches.
Special patterns:
^PAT match at the path root, "^/home"
PAT$ match paths ending with PAT, "man$"
./ match paths under the current directory
:PAT require PAT to span the tail, ":doc", ":re/doc"
Options: Options:
-a, --add add specified directories to the directory index. -a, --add add current or specified directories to the index.
--unindex remove current or specified directories from the index. --unindex remove current or specified directories from the index.
-r, --recursive apply options --add or --unindex recursively. -r, --recursive apply options --add or --unindex recursively.
--alias=ALIAS create alias for the current or specified directory and --alias=ALIAS create alias for the current or specified directory and
store it in ~/.scdalias.zsh. store it in ~/.scdalias.zsh.
--unalias remove ALIAS definition for the current or specified --unalias remove ALIAS definition for the current or specified
directory from ~/.scdalias.zsh. directory from ~/.scdalias.zsh.
-A, --all include all matching directories. Disregard matching by Use "OLD" to purge aliases to non-existent directories.
directory alias and filtering of less likely paths. -A, --all display all directories even those excluded by patterns
in ~/.scdignore. Disregard unique match for a directory
alias and filtering of less likely paths.
-p, --push use "pushd" to change to the target directory.
--list show matching directories and exit. --list show matching directories and exit.
-v, --verbose display directory rank in the selection menu. -v, --verbose display directory rank in the selection menu.
-h, --help display this message and exit. -h, --help display this message and exit.
@ -36,18 +46,28 @@ local SCD_MEANLIFE=${SCD_MEANLIFE:-86400}
local SCD_THRESHOLD=${SCD_THRESHOLD:-0.005} local SCD_THRESHOLD=${SCD_THRESHOLD:-0.005}
local SCD_SCRIPT=${RUNNING_AS_COMMAND:+$SCD_SCRIPT} local SCD_SCRIPT=${RUNNING_AS_COMMAND:+$SCD_SCRIPT}
local SCD_ALIAS=~/.scdalias.zsh local SCD_ALIAS=~/.scdalias.zsh
local SCD_IGNORE=~/.scdignore
local ICASE a d m p i maxrank threshold # Minimum logarithm of probability. Avoids out of range warning in exp().
local -r MINLOGPROB=-15
# When false, use case-insensitive globbing to fix PWD capitalization.
local PWDCASECORRECT=true
if [[ ${OSTYPE} == darwin* ]]; then
PWDCASECORRECT=false
fi
local a d m p i maxrank threshold
local opt_help opt_add opt_unindex opt_recursive opt_verbose local opt_help opt_add opt_unindex opt_recursive opt_verbose
local opt_alias opt_unalias opt_all opt_list local opt_alias opt_unalias opt_all opt_push opt_list
local -A drank dalias local -A drank dalias scdignore
local dmatching local dmatching
local last_directory local last_directory
setopt extendedhistory extendedglob noautonamedirs brace_ccl setopt extendedglob noautonamedirs brace_ccl
# If SCD_SCRIPT is defined make sure the file exists and is empty. # If SCD_SCRIPT is defined make sure that that file exists and is empty.
# This removes any previous old commands. # This removes any old previous commands from the SCD_SCRIPT file.
[[ -n "$SCD_SCRIPT" ]] && [[ -s $SCD_SCRIPT || ! -f $SCD_SCRIPT ]] && ( [[ -n "$SCD_SCRIPT" ]] && [[ -s $SCD_SCRIPT || ! -f $SCD_SCRIPT ]] && (
umask 077 umask 077
: >| $SCD_SCRIPT : >| $SCD_SCRIPT
@ -56,13 +76,17 @@ setopt extendedhistory extendedglob noautonamedirs brace_ccl
# process command line options # process command line options
zmodload -i zsh/zutil zmodload -i zsh/zutil
zmodload -i zsh/datetime zmodload -i zsh/datetime
zparseopts -D -- a=opt_add -add=opt_add -unindex=opt_unindex \ zmodload -i zsh/parameter
zparseopts -D -E -- a=opt_add -add=opt_add -unindex=opt_unindex \
r=opt_recursive -recursive=opt_recursive \ r=opt_recursive -recursive=opt_recursive \
-alias:=opt_alias -unalias=opt_unalias \ -alias:=opt_alias -unalias=opt_unalias \
A=opt_all -all=opt_all -list=opt_list \ A=opt_all -all=opt_all p=opt_push -push=opt_push -list=opt_list \
v=opt_verbose -verbose=opt_verbose h=opt_help -help=opt_help \ v=opt_verbose -verbose=opt_verbose h=opt_help -help=opt_help \
|| $EXIT $? || $EXIT $?
# remove the first instance of "--" from positional arguments
argv[(i)--]=( )
if [[ -n $opt_help ]]; then if [[ -n $opt_help ]]; then
print $DOC print $DOC
$EXIT $EXIT
@ -71,6 +95,22 @@ fi
# load directory aliases if they exist # load directory aliases if they exist
[[ -r $SCD_ALIAS ]] && source $SCD_ALIAS [[ -r $SCD_ALIAS ]] && source $SCD_ALIAS
# load scd-ignore patterns if available
if [[ -s $SCD_IGNORE ]]; then
setopt noglob
<$SCD_IGNORE \
while read p; do
[[ $p != [\#]* ]] || continue
[[ -n $p ]] || continue
# expand leading tilde if it has valid expansion
if [[ $p == [~]* ]] && ( : ${~p} ) 2>/dev/null; then
p=${~p}
fi
scdignore[$p]=1
done
setopt glob
fi
# Private internal functions are prefixed with _scd_Y19oug_. # Private internal functions are prefixed with _scd_Y19oug_.
# Clean them up when the scd function returns. # Clean them up when the scd function returns.
setopt localtraps setopt localtraps
@ -79,9 +119,17 @@ trap 'unfunction -m "_scd_Y19oug_*"' EXIT
# works faster than the (:a) modifier and is compatible with zsh 4.2.6 # works faster than the (:a) modifier and is compatible with zsh 4.2.6
_scd_Y19oug_abspath() { _scd_Y19oug_abspath() {
set -A $1 ${(ps:\0:)"$( set -A $1 ${(ps:\0:)"$(
unfunction -m "*"; shift setopt pushdsilent
unfunction -m "*"
unalias -m "*"
unset CDPATH
shift
for d; do for d; do
cd $d && print -Nr -- $PWD && cd $OLDPWD pushd $d || continue
$PWDCASECORRECT &&
print -Nr -- $PWD ||
print -Nr -- (#i)$PWD
popd 2>/dev/null
done done
)"} )"}
} }
@ -106,47 +154,76 @@ if [[ -n $opt_alias ]]; then
$EXIT $? $EXIT $?
fi fi
# undefine directory alias # undefine one or more directory aliases
if [[ -n $opt_unalias ]]; then if [[ -n $opt_unalias ]]; then
if [[ -n $1 && ! -d $1 ]]; then local -U uu
print -u2 "'$1' is not a directory." local ec=0
$EXIT 1 uu=( ${*:-${PWD}} )
if (( ${uu[(I)OLD]} && ${+nameddirs[OLD]} == 0 )); then
uu=( ${uu:#OLD} ${(ps:\0:)"$(
hash -dr
if [[ -r $SCD_ALIAS ]]; then
source $SCD_ALIAS
fi
for a d in ${(kv)nameddirs}; do
[[ -d $d ]] || print -Nr -- $a
done
)"}
)
fi fi
_scd_Y19oug_abspath a ${1:-$PWD} m=( )
a=$(print -rD ${a}) for p in $uu; do
if [[ $a != [~][^/]## ]]; then d=$p
$EXIT if [[ ${+nameddirs[$d]} == 0 && -d $d ]]; then
fi _scd_Y19oug_abspath d $d
a=${a#[~]} fi
# unalias in the current shell, update alias file if successful a=${(k)nameddirs[$d]:-${(k)nameddirs[(r)$d]}}
if unhash -d -- $a 2>/dev/null && [[ -r $SCD_ALIAS ]]; then if [[ -z $a ]]; then
ec=1
print -u2 "'$p' is neither a directory alias nor an aliased path."
continue
fi
# unalias in the current shell and remember to update the alias file
if unhash -d -- $a 2>/dev/null; then
m+=( $a )
fi
done
if [[ $#m != 0 && -r $SCD_ALIAS ]]; then
( (
umask 077 umask 077
hash -dr hash -dr
source $SCD_ALIAS source $SCD_ALIAS
unhash -d -- $a 2>/dev/null && for a in $m; do
unhash -d -- $a 2>/dev/null
done
hash -dL >| $SCD_ALIAS hash -dL >| $SCD_ALIAS
) ) || ec=$?
fi fi
$EXIT $? $EXIT $ec
fi fi
# The "compress" function collapses repeated directories to # The "compress" function collapses repeated directories into
# one entry with a time stamp that gives equivalent-probability. # a single entry with a time-stamp yielding an equivalent probability.
_scd_Y19oug_compress() { _scd_Y19oug_compress() {
awk -v epochseconds=$EPOCHSECONDS -v meanlife=$SCD_MEANLIFE ' awk -v epochseconds=$EPOCHSECONDS \
BEGIN { FS = "[:;]"; } -v meanlife=$SCD_MEANLIFE \
length($0) < 4096 && $2 > 0 { -v minlogprob=$MINLOGPROB \
'
BEGIN {
FS = "[:;]";
pmin = exp(minlogprob);
}
/^: deleted:0;/ { next; }
length($0) < 4096 && $2 > 1000 {
df = $0;
sub("^[^;]*;", "", df);
if (!df) next;
tau = 1.0 * ($2 - epochseconds) / meanlife; tau = 1.0 * ($2 - epochseconds) / meanlife;
if (tau < -6.9078) tau = -6.9078; prob = (tau < minlogprob) ? pmin : exp(tau);
prob = exp(tau); dlist[last[df]] = "";
sub(/^[^;]*;/, ""); dlist[NR] = df;
if (NF) { last[df] = NR;
dlist[last[$0]] = ""; ptot[df] += prob;
dlist[NR] = $0;
last[$0] = NR;
ptot[$0] += prob;
}
} }
END { END {
for (i = 1; i <= NR; ++i) { for (i = 1; i <= NR; ++i) {
@ -157,26 +234,38 @@ _scd_Y19oug_compress() {
} }
} }
} }
' $* ' $*
} }
# Rewrite directory index if it is at least 20% oversized # Rewrite directory index if it is at least 20% oversized.
if [[ -s $SCD_HISTFILE ]] && \ local curhistsize
(( $(wc -l <$SCD_HISTFILE) > 1.2 * $SCD_HISTSIZE )); then if [[ -z $opt_unindex && -s $SCD_HISTFILE ]] && \
# compress repeated entries curhistsize=$(wc -l <$SCD_HISTFILE) && \
m=( ${(f)"$(_scd_Y19oug_compress $SCD_HISTFILE)"} ) (( $curhistsize > 1.2 * $SCD_HISTSIZE )); then
# purge non-existent directories # Compress repeated entries in a background process.
m=( ${(f)"$( (
for a in $m; do m=( ${(f)"$(_scd_Y19oug_compress $SCD_HISTFILE)"} )
if [[ -d ${a#*;} ]]; then print -r -- $a; fi # purge non-existent and ignored directories
done m=( ${(f)"$(
)"} for a in $m; do
) d=${a#*;}
# cut old entries if still oversized [[ -z ${scdignore[(k)$d]} ]] || continue
if [[ $#m -gt $SCD_HISTSIZE ]]; then [[ -d $d ]] || continue
m=( ${m[-$SCD_HISTSIZE,-1]} ) $PWDCASECORRECT || d=( (#i)${d} )
fi t=${a%%;*}
print -lr -- $m >| ${SCD_HISTFILE} print -r -- "${t};${d}"
done
)"}
)
# cut old entries if still oversized
if [[ $#m -gt $SCD_HISTSIZE ]]; then
m=( ${m[-$SCD_HISTSIZE,-1]} )
fi
# Checking existence of many directories could have taken a while.
# Append any index entries added in meantime.
m+=( ${(f)"$(sed "1,${curhistsize}d" $SCD_HISTFILE)"} )
print -lr -- $m >| ${SCD_HISTFILE}
) &|
fi fi
# Determine the last recorded directory # Determine the last recorded directory
@ -197,13 +286,8 @@ _scd_Y19oug_record() {
} }
if [[ -n $opt_add ]]; then if [[ -n $opt_add ]]; then
for d; do m=( ${^${argv:-$PWD}}(N-/) )
if [[ ! -d $d ]]; then _scd_Y19oug_abspath m ${m}
print -u2 "Directory '$d' does not exist."
$EXIT 2
fi
done
_scd_Y19oug_abspath m ${*:-$PWD}
_scd_Y19oug_record $m _scd_Y19oug_record $m
if [[ -n $opt_recursive ]]; then if [[ -n $opt_recursive ]]; then
for d in $m; do for d in $m; do
@ -220,6 +304,7 @@ if [[ -n $opt_unindex ]]; then
if [[ ! -s $SCD_HISTFILE ]]; then if [[ ! -s $SCD_HISTFILE ]]; then
$EXIT $EXIT
fi fi
argv=( ${argv:-$PWD} )
# expand existing directories in the argument list # expand existing directories in the argument list
for i in {1..$#}; do for i in {1..$#}; do
if [[ -d ${argv[i]} ]]; then if [[ -d ${argv[i]} ]]; then
@ -227,24 +312,28 @@ if [[ -n $opt_unindex ]]; then
argv[i]=${d} argv[i]=${d}
fi fi
done done
# strip trailing slashes, but preserve the root path
argv=( ${argv/(#m)?\/##(#e)/${MATCH[1]}} )
m="$(awk -v recursive=${opt_recursive} ' m="$(awk -v recursive=${opt_recursive} '
BEGIN { BEGIN {
for (i = 2; i < ARGC; ++i) { for (i = 2; i < ARGC; ++i) {
argset[ARGV[i]] = 1; argset[ARGV[i]] = 1;
delete ARGV[i]; delete ARGV[i];
} }
unindex_root = ("/" in argset);
} }
1 { 1 {
d = $0; sub(/^[^;]*;/, "", d); d = $0; sub(/^[^;]*;/, "", d);
if (d in argset) next; if (d in argset) next;
} }
recursive { recursive {
if (unindex_root) exit;
for (a in argset) { for (a in argset) {
if (substr(d, 1, length(a) + 1) == a"/") next; if (substr(d, 1, length(a) + 1) == a"/") next;
} }
} }
{ print $0 } { print $0 }
' $SCD_HISTFILE ${*:-$PWD} )" || $EXIT $? ' $SCD_HISTFILE $* )" || $EXIT $?
: >| ${SCD_HISTFILE} : >| ${SCD_HISTFILE}
[[ ${#m} == 0 ]] || print -r -- $m >> ${SCD_HISTFILE} [[ ${#m} == 0 ]] || print -r -- $m >> ${SCD_HISTFILE}
$EXIT $EXIT
@ -252,67 +341,113 @@ fi
# The "action" function is called when there is just one target directory. # The "action" function is called when there is just one target directory.
_scd_Y19oug_action() { _scd_Y19oug_action() {
cd $1 || return $? local cdcmd=cd
[[ -z ${opt_push} ]] || cdcmd=pushd
builtin $cdcmd $1 || return $?
if [[ -z $SCD_SCRIPT && -n $RUNNING_AS_COMMAND ]]; then if [[ -z $SCD_SCRIPT && -n $RUNNING_AS_COMMAND ]]; then
print -u2 "Warning: running as command with SCD_SCRIPT undefined." print -u2 "Warning: running as command with SCD_SCRIPT undefined."
fi fi
if [[ -n $SCD_SCRIPT ]]; then if [[ -n $SCD_SCRIPT ]]; then
print -r "cd ${(q)1}" >| $SCD_SCRIPT local d=$1
if [[ $OSTYPE == cygwin && ${(L)SCD_SCRIPT} == *.bat ]]; then
d=$(cygpath -aw .)
fi
print -r "${cdcmd} ${(qqq)d}" >| $SCD_SCRIPT
fi fi
} }
# Match and rank patterns to the index file # Select and order indexed directories by matching command-line patterns.
# set global arrays dmatching and drank # Set global arrays dmatching and drank.
_scd_Y19oug_match() { _scd_Y19oug_match() {
## single argument that is an existing directory or directory alias ## single argument that is an existing directory or directory alias
if [[ -z $opt_all && $# == 1 ]] && \ if [[ -z $opt_all && $# == 1 ]] && \
[[ -d ${d::=$1} || -d ${d::=${nameddirs[$1]}} ]] && [[ -x $d ]]; [[ -d ${d::=${nameddirs[$1]}} || -d ${d::=$1} ]] && [[ -x $d ]];
then then
_scd_Y19oug_abspath dmatching $d _scd_Y19oug_abspath dmatching $d
drank[${dmatching[1]}]=1 drank[${dmatching[1]}]=1
return return
fi fi
# ignore case unless there is an argument with an uppercase letter # quote brackets when PWD is /Volumes/[C]/
[[ "$*" == *[[:upper:]]* ]] || ICASE='(#i)' local qpwd=${PWD//(#m)[][]/\\${MATCH}}
# support "$" as an anchor for the directory name ending
# support "./" as an alias for $PWD to match only subdirectories.
argv=( ${argv/(#s).\/(#e)/(#s)${qpwd}(|/*)(#e)} )
# support "./pat" as an alias for $PWD/pat.
argv=( ${argv/(#m)(#s).\/?*/(#s)${qpwd}${MATCH#.}} )
# support "^" as an anchor for the root directory, e.g., "^$HOME".
argv=( ${argv/(#m)(#s)\^?*/(#s)${${~MATCH[2,-1]}}} )
# support "$" as an anchor at the end of directory name.
argv=( ${argv/(#m)?[$](#e)/${MATCH[1]}(#e)} ) argv=( ${argv/(#m)?[$](#e)/${MATCH[1]}(#e)} )
# calculate rank of all directories in the SCD_HISTFILE and keep it as drank # support prefix ":" to match over the tail component.
# include a dummy entry for splitting of an empty string is buggy argv=( ${argv/(#m)(#s):?*/${MATCH[2,-1]}[^/]#(#e)} )
# calculate rank of all directories in SCD_HISTFILE and store it in drank.
# include a dummy entry to avoid issues with splitting an empty string.
[[ -s $SCD_HISTFILE ]] && drank=( ${(f)"$( [[ -s $SCD_HISTFILE ]] && drank=( ${(f)"$(
print -l /dev/null -10 print -l /dev/null -10
<$SCD_HISTFILE \ <$SCD_HISTFILE \
awk -v epochseconds=$EPOCHSECONDS -v meanlife=$SCD_MEANLIFE ' awk -v epochseconds=$EPOCHSECONDS \
BEGIN { FS = "[:;]"; } -v meanlife=$SCD_MEANLIFE \
length($0) < 4096 && $2 > 0 { -v minlogprob=$MINLOGPROB \
tau = 1.0 * ($2 - epochseconds) / meanlife; '
if (tau < -6.9078) tau = -6.9078; BEGIN {
prob = exp(tau); FS = "[:;]";
sub(/^[^;]*;/, ""); pmin = exp(minlogprob);
if (NF) ptot[$0] += prob;
} }
END { for (di in ptot) { print di; print ptot[di]; } }' /^: deleted:0;/ {
df = $0;
sub("^[^;]*;", "", df);
delete ptot[df];
next;
}
length($0) < 4096 && $2 > 0 {
df = $0;
sub("^[^;]*;", "", df);
if (!df) next;
dp = df;
while (!(dp in ptot)) {
ptot[dp] = pmin;
sub("//*[^/]*$", "", dp);
if (!dp) break;
}
if ($2 <= 1000) next;
tau = 1.0 * ($2 - epochseconds) / meanlife;
prob = (tau < minlogprob) ? pmin : exp(tau);
ptot[df] += prob;
}
END { for (di in ptot) { print di; print ptot[di]; } }
'
)"} )"}
) )
unset "drank[/dev/null]" unset "drank[/dev/null]"
# filter drank to the entries that match all arguments # filter drank to the entries that match all arguments
for a; do for a; do
p=${ICASE}"*(${a})*" p="(#l)*(${a})*"
drank=( ${(kv)drank[(I)${~p}]} ) drank=( ${(kv)drank[(I)${~p}]} )
done done
# require at least one argument matches the directory name # require that at least one argument matches in directory tail name.
p=${ICASE}"*(${(j:|:)argv})[^/]#" p="(#l)*(${(j:|:)argv})[^/]#"
drank=( ${(kv)drank[(I)${~p}]} ) drank=( ${(kv)drank[(I)${~p}]} )
# discard ignored directories
if [[ -z ${opt_all} ]]; then
for d in ${(k)drank}; do
[[ -z ${scdignore[(k)$d]} ]] || unset "drank[$d]"
done
fi
# build a list of matching directories reverse-sorted by their probabilities # build a list of matching directories reverse-sorted by their probabilities
dmatching=( ${(f)"$( dmatching=( ${(f)"$(
for d p in ${(kv)drank}; do builtin printf "%s %s\n" ${(Oakv)drank} |
print -r -- "$p $d"; /usr/bin/sort -grk1 )"}
done | sort -grk1 | cut -d ' ' -f 2-
)"}
) )
dmatching=( ${dmatching#*[[:blank:]]} )
# do not match $HOME or $PWD when run without arguments # do not match $HOME or $PWD when run without arguments
if [[ $# == 0 ]]; then if [[ $# == 0 ]]; then
@ -320,12 +455,20 @@ _scd_Y19oug_match() {
fi fi
# keep at most SCD_MENUSIZE of matching and valid directories # keep at most SCD_MENUSIZE of matching and valid directories
# mark up any deleted entries in the index
local -A isdeleted
m=( ) m=( )
isdeleted=( )
for d in $dmatching; do for d in $dmatching; do
[[ ${#m} == $SCD_MENUSIZE ]] && break [[ ${#m} == $SCD_MENUSIZE ]] && break
[[ -d $d && -x $d ]] && m+=$d (( ${+isdeleted[$d]} == 0 )) || continue
[[ -d $d ]] || { isdeleted[$d]=1; continue }
[[ -x $d ]] && m+=$d
done done
dmatching=( $m ) dmatching=( $m )
if [[ -n ${isdeleted} ]]; then
print -lr -- ": deleted:0;"${^${(k)isdeleted}} >> $SCD_HISTFILE
fi
# find the maximum rank # find the maximum rank
maxrank=0.0 maxrank=0.0
@ -343,7 +486,7 @@ _scd_Y19oug_match() {
_scd_Y19oug_match $* _scd_Y19oug_match $*
## process whatever directories that remained ## process matching directories.
if [[ ${#dmatching} == 0 ]]; then if [[ ${#dmatching} == 0 ]]; then
print -u2 "No matching directory." print -u2 "No matching directory."
$EXIT 1 $EXIT 1
@ -367,13 +510,13 @@ if [[ -n $opt_list ]]; then
$EXIT $EXIT
fi fi
## process single directory match ## handle a single matching directory here.
if [[ ${#dmatching} == 1 ]]; then if [[ ${#dmatching} == 1 ]]; then
_scd_Y19oug_action $dmatching _scd_Y19oug_action $dmatching
$EXIT $? $EXIT $?
fi fi
## here we have multiple matches - display selection menu ## Here we have multiple matches. Let's use the selection menu.
a=( {a-z} {A-Z} ) a=( {a-z} {A-Z} )
a=( ${a[1,${#dmatching}]} ) a=( ${a[1,${#dmatching}]} )
p=( ) p=( )

View file

@ -1,19 +1,17 @@
## The scd script should autoload as a shell function. ## The scd script should autoload as a shell function.
autoload scd autoload -Uz scd
## If the scd function exists, define a change-directory-hook function ## If the scd function exists, define a change-directory-hook function
## to record visited directories in the scd index. ## to record visited directories in the scd index.
if [[ ${+functions[scd]} == 1 ]]; then if [[ ${+functions[scd]} == 1 ]]; then
scd_chpwd_hook() { scd --add $PWD } chpwd_scd() { scd --add $PWD }
autoload add-zsh-hook autoload -Uz add-zsh-hook
add-zsh-hook chpwd scd_chpwd_hook add-zsh-hook chpwd chpwd_scd
fi fi
## Allow scd usage with unquoted wildcard characters such as "*" or "?".
alias scd='noglob scd'
## Load the directory aliases created by scd if any. ## Load the directory aliases created by scd if any.
if [[ -s ~/.scdalias.zsh ]]; then source ~/.scdalias.zsh; fi if [[ -s ~/.scdalias.zsh ]]; then
source ~/.scdalias.zsh
fi