Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add scd plugin for smart change of directory.

Synced with the scd-tracker branch
pavoljuhas/oh-my-zsh@2f78243.
  • Loading branch information...
commit c1c107cfb9669beeda258efed9a17cc4bddf7aa7 1 parent 6952105
@pavoljuhas pavoljuhas authored
Showing with 165 additions and 163 deletions.
  1. +1 −2  plugins/scd/README.md
  2. +164 −161 plugins/scd/scd
View
3  plugins/scd/README.md
@@ -111,8 +111,7 @@ SCD_MEANLIFE</dt><dd>
SCD_THRESHOLD</dt><dd>
threshold for cumulative directory likelihood. Directories with
- lower likelihood are excluded unless they are the only match to
- scd patterns.
+ a lower likelihood compared to the best match are excluded (0.005).
</dd><dt>
SCD_SCRIPT</dt><dd>
View
325 plugins/scd/scd
@@ -1,10 +1,11 @@
#!/bin/zsh -f
emulate -L zsh
+local EXIT=return
if [[ $(whence -w $0) == *:' 'command ]]; then
emulate -R zsh
- alias return=exit
local RUNNING_AS_COMMAND=1
+ EXIT=exit
fi
local DOC='scd -- smart change to a recently used directory
@@ -37,8 +38,9 @@ local SCD_ALIAS=~/.scdalias.zsh
local ICASE a d m p i tdir maxrank threshold
local opt_help opt_add opt_unindex opt_recursive opt_verbose
local opt_alias opt_unalias opt_list
-local -A drank dalias dkey
+local -A drank dalias
local dmatching
+local last_directory
setopt extendedhistory extendedglob noautonamedirs brace_ccl
@@ -56,11 +58,11 @@ zparseopts -D -- a=opt_add -add=opt_add -unindex=opt_unindex \
r=opt_recursive -recursive=opt_recursive \
-alias:=opt_alias -unalias=opt_unalias -list=opt_list \
v=opt_verbose -verbose=opt_verbose h=opt_help -help=opt_help \
- || return $?
+ || $EXIT $?
if [[ -n $opt_help ]]; then
print $DOC
- return
+ $EXIT
fi
# load directory aliases if they exist
@@ -79,8 +81,8 @@ _scd_Y19oug_abspath() {
# define directory alias
if [[ -n $opt_alias ]]; then
if [[ -n $1 && ! -d $1 ]]; then
- print -u2 "'$1' is not a directory"
- return 1
+ print -u2 "'$1' is not a directory."
+ $EXIT 1
fi
a=${opt_alias[-1]#=}
_scd_Y19oug_abspath d ${1:-$PWD}
@@ -93,19 +95,19 @@ if [[ -n $opt_alias ]]; then
hash -d -- $a=$d
hash -dL >| $SCD_ALIAS
)
- return $?
+ $EXIT $?
fi
# undefine directory alias
if [[ -n $opt_unalias ]]; then
if [[ -n $1 && ! -d $1 ]]; then
- print -u2 "'$1' is not a directory"
- return 1
+ print -u2 "'$1' is not a directory."
+ $EXIT 1
fi
_scd_Y19oug_abspath a ${1:-$PWD}
a=$(print -rD ${a})
if [[ $a != [~][^/]## ]]; then
- return
+ $EXIT
fi
a=${a#[~]}
# unalias in the current shell, update alias file if successful
@@ -118,35 +120,39 @@ if [[ -n $opt_unalias ]]; then
hash -dL >| $SCD_ALIAS
)
fi
- return $?
+ $EXIT $?
fi
-# Rewrite the history file if it is at least 20% oversized
+# Rewrite directory index if it is at least 20% oversized
if [[ -s $SCD_HISTFILE ]] && \
(( $(wc -l <$SCD_HISTFILE) > 1.2 * $SCD_HISTSIZE )); then
m=( ${(f)"$(<$SCD_HISTFILE)"} )
print -lr -- ${m[-$SCD_HISTSIZE,-1]} >| ${SCD_HISTFILE}
fi
+# Determine the last recorded directory
+if [[ -s ${SCD_HISTFILE} ]]; then
+ last_directory=${"$(tail -1 ${SCD_HISTFILE})"#*;}
+fi
+
# Internal functions are prefixed with "_scd_Y19oug_".
-# The "record" function adds a non-repeating directory to the history
-# and turns on history writing.
+# The "record" function adds its arguments to the directory index.
_scd_Y19oug_record() {
- while [[ -n $1 && $1 == ${history[$HISTCMD]} ]]; do
+ while [[ -n $last_directory && $1 == $last_directory ]]; do
shift
done
- if [[ $# != 0 ]]; then
- ( umask 077; : >>| $SCD_HISTFILE )
- p=": ${EPOCHSECONDS}:0;"
- print -lr -- ${p}${^*} >> $SCD_HISTFILE
+ if [[ $# -gt 0 ]]; then
+ ( umask 077
+ p=": ${EPOCHSECONDS}:0;"
+ print -lr -- ${p}${^*} >>| $SCD_HISTFILE )
fi
}
if [[ -n $opt_add ]]; then
- for a; do
- if [[ ! -d $a ]]; then
- print -u 2 "Directory $a does not exist"
- return 2
+ for d; do
+ if [[ ! -d $d ]]; then
+ print -u2 "Directory '$d' does not exist."
+ $EXIT 2
fi
done
_scd_Y19oug_abspath m ${*:-$PWD}
@@ -158,13 +164,13 @@ if [[ -n $opt_add ]]; then
print "[done]"
done
fi
- return
+ $EXIT
fi
# take care of removing entries from the directory index
if [[ -n $opt_unindex ]]; then
if [[ ! -s $SCD_HISTFILE ]]; then
- return
+ $EXIT
fi
# expand existing directories in the argument list
for i in {1..$#}; do
@@ -190,161 +196,158 @@ if [[ -n $opt_unindex ]]; then
}
}
{ print $0 }
- ' $SCD_HISTFILE ${*:-$PWD} )" || return $?
+ ' $SCD_HISTFILE ${*:-$PWD} )" || $EXIT $?
: >| ${SCD_HISTFILE}
[[ ${#m} == 0 ]] || print -r -- $m >> ${SCD_HISTFILE}
- return
+ $EXIT
fi
# The "action" function is called when there is just one target directory.
_scd_Y19oug_action() {
- if [[ -n $opt_list ]]; then
- for d; do
- a=${(k)dalias[(r)${d}]}
- print -r -- "# $a"
- print -r -- $d
- done
- elif [[ $# == 1 ]]; then
- if [[ -z $SCD_SCRIPT && -n $RUNNING_AS_COMMAND ]]; then
- print -u2 "Warning: running as command with SCD_SCRIPT undefined."
- fi
- [[ -n $SCD_SCRIPT ]] && (umask 077;
- print -r "cd ${(q)1}" >| $SCD_SCRIPT)
- [[ -N $SCD_HISTFILE ]] && touch -a $SCD_HISTFILE
- cd $1
- # record the new directory unless already done in some chpwd hook
- [[ -N $SCD_HISTFILE ]] || _scd_Y19oug_record $PWD
+ cd $1 || return $?
+ if [[ -z $SCD_SCRIPT && -n $RUNNING_AS_COMMAND ]]; then
+ print -u2 "Warning: running as command with SCD_SCRIPT undefined."
+ fi
+ if [[ -n $SCD_SCRIPT ]]; then
+ print -r "cd ${(q)1}" >| $SCD_SCRIPT
fi
}
-# handle different argument scenarios ----------------------------------------
+# Match and rank patterns to the index file
+# set global arrays dmatching and drank
+_scd_Y19oug_match() {
+ ## single argument that is an existing directory or directory alias
+ if [[ $# == 1 ]] && \
+ [[ -d ${d::=$1} || -d ${d::=${nameddirs[$1]}} ]] && [[ -x $d ]];
+ then
+ _scd_Y19oug_abspath dmatching $d
+ drank[${dmatching[1]}]=1
+ return
+ fi
-## single argument that is an existing directory
-if [[ $# == 1 && -d $1 && -x $1 ]]; then
- _scd_Y19oug_action $1
- return $?
-## single argument that is an alias
-elif [[ $# == 1 && -d ${d::=${nameddirs[$1]}} ]]; then
- _scd_Y19oug_action $d
- return $?
-fi
+ # ignore case unless there is an argument with an uppercase letter
+ [[ "$*" == *[[:upper:]]* ]] || ICASE='(#i)'
-# ignore case unless there is an argument with an uppercase letter
-[[ "$*" == *[[:upper:]]* ]] || ICASE='(#i)'
-
-# calculate rank of all directories in the SCD_HISTFILE and keep it as drank
-# include a dummy entry for splitting of an empty string is buggy
-[[ -s $SCD_HISTFILE ]] && drank=( ${(f)"$(
- print -l /dev/null -10
- <$SCD_HISTFILE \
- awk -v epochseconds=$EPOCHSECONDS -v meanlife=$SCD_MEANLIFE '
- BEGIN { FS = "[:;]"; }
- length($0) < 4096 && $2 > 0 {
- tau = 1.0 * ($2 - epochseconds) / meanlife;
- if (tau < -4.61) tau = -4.61;
- prec = exp(tau);
- sub(/^[^;]*;/, "");
- if (NF) ptot[$0] += prec;
- }
- END { for (di in ptot) { print di; print ptot[di]; } }'
- )"}
-)
-unset "drank[/dev/null]"
+ # calculate rank of all directories in the SCD_HISTFILE and keep it as drank
+ # include a dummy entry for splitting of an empty string is buggy
+ [[ -s $SCD_HISTFILE ]] && drank=( ${(f)"$(
+ print -l /dev/null -10
+ <$SCD_HISTFILE \
+ awk -v epochseconds=$EPOCHSECONDS -v meanlife=$SCD_MEANLIFE '
+ BEGIN { FS = "[:;]"; }
+ length($0) < 4096 && $2 > 0 {
+ tau = 1.0 * ($2 - epochseconds) / meanlife;
+ if (tau < -4.61) tau = -4.61;
+ prec = exp(tau);
+ sub(/^[^;]*;/, "");
+ if (NF) ptot[$0] += prec;
+ }
+ END { for (di in ptot) { print di; print ptot[di]; } }'
+ )"}
+ )
+ unset "drank[/dev/null]"
-# filter drank to the entries that match all arguments
-for a; do
- p=${ICASE}"*${a}*"
- drank=( ${(kv)drank[(I)${~p}]} )
-done
+ # filter drank to the entries that match all arguments
+ for a; do
+ p=${ICASE}"*${a}*"
+ drank=( ${(kv)drank[(I)${~p}]} )
+ done
-# build a list of matching directories reverse-sorted by their probabilities
-dmatching=( ${(f)"$(
- for d p in ${(kv)drank}; do
- print -r -- "$p $d";
- done | sort -grk1 | cut -d ' ' -f 2-
- )"}
-)
+ # build a list of matching directories reverse-sorted by their probabilities
+ dmatching=( ${(f)"$(
+ for d p in ${(kv)drank}; do
+ print -r -- "$p $d";
+ done | sort -grk1 | cut -d ' ' -f 2-
+ )"}
+ )
-# if some directory paths match all patterns in order, discard all others
-p=${ICASE}"*${(j:*:)argv}*"
-m=( ${(M)dmatching:#${~p}} )
-[[ -d ${m[1]} ]] && dmatching=( $m )
-# if some directory names match last pattern, discard all others
-p=${ICASE}"*${(j:*:)argv}[^/]#"
-m=( ${(M)dmatching:#${~p}} )
-[[ -d ${m[1]} ]] && dmatching=( $m )
-# if some directory names match all patterns, discard all others
-m=( $dmatching )
-for a; do
- p=${ICASE}"*/[^/]#${a}[^/]#"
- m=( ${(M)m:#${~p}} )
-done
-[[ -d ${m[1]} ]] && dmatching=( $m )
-# if some directory names match all patterns in order, discard all others
-p=${ICASE}"/*${(j:[^/]#:)argv}[^/]#"
-m=( ${(M)dmatching:#${~p}} )
-[[ -d ${m[1]} ]] && dmatching=( $m )
-
-# do not match $HOME or $PWD when run without arguments
-if [[ $# == 0 ]]; then
- dmatching=( ${dmatching:#(${HOME}|${PWD})} )
+ # if some directory paths match all patterns in order, discard all others
+ p=${ICASE}"*${(j:*:)argv}*"
+ m=( ${(M)dmatching:#${~p}} )
+ [[ -d ${m[1]} ]] && dmatching=( $m )
+ # if some directory names match last pattern, discard all others
+ p=${ICASE}"*${(j:*:)argv}[^/]#"
+ m=( ${(M)dmatching:#${~p}} )
+ [[ -d ${m[1]} ]] && dmatching=( $m )
+ # if some directory names match all patterns, discard all others
+ m=( $dmatching )
+ for a; do
+ p=${ICASE}"*/[^/]#${a}[^/]#"
+ m=( ${(M)m:#${~p}} )
+ done
+ [[ -d ${m[1]} ]] && dmatching=( $m )
+ # if some directory names match all patterns in order, discard all others
+ p=${ICASE}"/*${(j:[^/]#:)argv}[^/]#"
+ m=( ${(M)dmatching:#${~p}} )
+ [[ -d ${m[1]} ]] && dmatching=( $m )
+
+ # do not match $HOME or $PWD when run without arguments
+ if [[ $# == 0 ]]; then
+ dmatching=( ${dmatching:#(${HOME}|${PWD})} )
+ fi
+
+ # keep at most SCD_MENUSIZE of matching and valid directories
+ m=( )
+ for d in $dmatching; do
+ [[ ${#m} == $SCD_MENUSIZE ]] && break
+ [[ -d $d && -x $d ]] && m+=$d
+ done
+ dmatching=( $m )
+
+ # find the maximum rank
+ maxrank=0.0
+ for d in $dmatching; do
+ [[ ${drank[$d]} -lt maxrank ]] || maxrank=${drank[$d]}
+ done
+
+ # discard all directories below the rank threshold
+ threshold=$(( maxrank * SCD_THRESHOLD ))
+ dmatching=( ${^dmatching}(Ne:'(( ${drank[$REPLY]} >= threshold ))':) )
+}
+
+_scd_Y19oug_match $*
+
+## process whatever directories that remained
+if [[ ${#dmatching} == 0 ]]; then
+ print -u2 "No matching directory."
+ $EXIT 1
fi
-# keep at most SCD_MENUSIZE of matching and valid directories
-m=( )
+## build formatted directory aliases for selection menu or list display
for d in $dmatching; do
- [[ ${#m} == $SCD_MENUSIZE ]] && break
- [[ -d $d && -x $d ]] && m+=$d
+ if [[ -n ${opt_verbose} ]]; then
+ dalias[$d]=$(printf "%.3g %s" ${drank[$d]} $d)
+ else
+ dalias[$d]=$(print -Dr -- $d)
+ fi
done
-dmatching=( $m )
-# find the maximum rank
-maxrank=0.0
-for d in $dmatching; do
- [[ ${drank[$d]} -lt maxrank ]] || maxrank=${drank[$d]}
+## process the --list option
+if [[ -n $opt_list ]]; then
+ for d in $dmatching; do
+ print -r -- "# ${dalias[$d]}"
+ print -r -- $d
+ done
+ $EXIT
+fi
+
+## process single directory match
+if [[ ${#dmatching} == 1 ]]; then
+ _scd_Y19oug_action $dmatching
+ $EXIT $?
+fi
+
+## here we have multiple matches - display selection menu
+a=( {a-z} {A-Z} )
+p=( )
+for i in {1..${#dmatching}}; do
+ [[ -n ${a[i]} ]] || break
+ p+="${a[i]}) ${dalias[${dmatching[i]}]}"
done
-# discard all directories below the rank threshold
-threshold=$(( maxrank * SCD_THRESHOLD ))
-dmatching=( ${^dmatching}(Ne:'(( ${drank[$REPLY]} >= threshold ))':) )
+print -c -r -- $p
-## process whatever directories that remained
-case ${#dmatching} in
-(0)
- print -u2 "no matching directory"
- return 1
- ;;
-(1)
- _scd_Y19oug_action $dmatching
- return $?
- ;;
-(*)
- # build a list of strings to be displayed in the selection menu
- m=( ${(f)"$(print -lD ${dmatching})"} )
- if [[ -n $opt_verbose ]]; then
- for i in {1..${#dmatching}}; do
- d=${dmatching[i]}
- m[i]=$(printf "%.3g %s" ${drank[$d]} $d)
- done
- fi
- # build a map of string names to actual directory paths
- for i in {1..${#m}}; dalias[${m[i]}]=${dmatching[i]}
- # opt_list - output matching directories and exit
- if [[ -n $opt_list ]]; then
- _scd_Y19oug_action ${dmatching}
- return
- fi
- # finally use the selection menu to get the answer
- a=( {a-z} {A-Z} )
- p=( )
- for i in {1..${#m}}; do
- [[ -n ${a[i]} ]] || break
- dkey[${a[i]}]=${dalias[$m[i]]}
- p+="${a[i]}) ${m[i]}"
- done
- print -c -r -- $p
- if read -s -k 1 d && [[ -n ${dkey[$d]} ]]; then
- _scd_Y19oug_action ${dkey[$d]}
- fi
- return $?
-esac
+if read -s -k 1 d && [[ ${i::=${a[(I)$d]}} -gt 0 ]]; then
+ _scd_Y19oug_action ${dmatching[i]}
+ $EXIT $?
+fi
Please sign in to comment.
Something went wrong with that request. Please try again.