From c1c107cfb9669beeda258efed9a17cc4bddf7aa7 Mon Sep 17 00:00:00 2001
From: Pavol Juhas <pavol.juhas@gmail.com>
Date: Mon, 17 Mar 2014 19:49:12 -0400
Subject: [PATCH] Add scd plugin for smart change of directory.

Synced with the scd-tracker branch
pavoljuhas/oh-my-zsh@2f78243cad3509058d142aa4b70a604e75ec741e.
---
 plugins/scd/README.md |   3 +-
 plugins/scd/scd       | 343 +++++++++++++++++++++---------------------
 2 files changed, 174 insertions(+), 172 deletions(-)

diff --git a/plugins/scd/README.md b/plugins/scd/README.md
index ea7c72464..197cea50a 100644
--- a/plugins/scd/README.md
+++ b/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>
diff --git a/plugins/scd/scd b/plugins/scd/scd
index 9e055eadd..1567d2736 100755
--- a/plugins/scd/scd
+++ b/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 ----------------------------------------
-
-## 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)'
-
-# 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
-
-# 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})} )
-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 ))':) )
-
-## 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}
+# 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
-    # 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]}"
+
+    # 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]"
+
+    # filter drank to the entries that match all arguments
+    for a; do
+        p=${ICASE}"*${a}*"
+        drank=( ${(kv)drank[(I)${~p}]} )
     done
-    print -c -r -- $p
-    if read -s -k 1 d && [[ -n ${dkey[$d]} ]]; then
-        _scd_Y19oug_action ${dkey[$d]}
+
+    # 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})} )
     fi
-    return $?
-esac
+
+    # 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
+
+## build formatted directory aliases for selection menu or list display
+for d in $dmatching; do
+    if [[ -n ${opt_verbose} ]]; then
+        dalias[$d]=$(printf "%.3g %s" ${drank[$d]} $d)
+    else
+        dalias[$d]=$(print -Dr -- $d)
+    fi
+done
+
+## 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
+
+print -c -r -- $p
+
+if read -s -k 1 d && [[ ${i::=${a[(I)$d]}} -gt 0 ]]; then
+    _scd_Y19oug_action ${dmatching[i]}
+    $EXIT $?
+fi