diff options
Diffstat (limited to 'plugins/scd')
| -rw-r--r-- | plugins/scd/README.md | 15 | ||||
| -rwxr-xr-x | plugins/scd/scd | 119 | 
2 files changed, 86 insertions, 48 deletions
diff --git a/plugins/scd/README.md b/plugins/scd/README.md index 197cea50a..86ab67203 100644 --- a/plugins/scd/README.md +++ b/plugins/scd/README.md @@ -11,12 +11,9 @@ the index.  A selection menu is displayed in case of several matches, with a  preference given to recently visited paths.  `scd` can create permanent  directory aliases, which appear as named directories in zsh session. -## INSTALLATION +## INSTALLATION NOTES -For oh-my-zsh, add `scd` to the `plugins` array in the ~/.zshrc file as in the -[template file](../../templates/zshrc.zsh-template#L45). - -Besides 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](http://www.vim.org/) plugin and  [IPython](http://ipython.org/) extension.  For installation details, see  https://github.com/pavoljuhas/smart-change-directory. @@ -34,7 +31,7 @@ scd [options] [pattern1 pattern2 ...]    add specified directories to the directory index.</dd><dt>  --unindex</dt><dd> -  remove specified directories from the index.</dd><dt> +  remove current or specified directories from the index.</dd><dt>  -r, --recursive</dt><dd>    apply options <em>--add</em> or <em>--unindex</em> recursively.</dd><dt> @@ -47,6 +44,10 @@ scd [options] [pattern1 pattern2 ...]    remove ALIAS definition for the current or specified directory from    <em>~/.scdalias.zsh</em>.</dd><dt> +-A, --all</dt><dd> +  include all matching directories.  Disregard matching by directory +  alias and filtering of less likely paths.</dd><dt> +  --list</dt><dd>    show matching directories and exit.</dd><dt> @@ -70,7 +71,7 @@ scd doc  scd a b c  # Change to a directory path that ends with "ts" -scd "ts(#e)" +scd "ts$"  # Show selection menu and ranking of 20 most likely directories  scd -v diff --git a/plugins/scd/scd b/plugins/scd/scd index 1567d2736..39b28237d 100755 --- a/plugins/scd/scd +++ b/plugins/scd/scd @@ -11,20 +11,22 @@ fi  local DOC='scd -- smart change to a recently used directory  usage: scd [options] [pattern1 pattern2 ...]  Go to a directory path that contains all fixed string patterns.  Prefer -recently visited directories and directories with patterns in their tail -component.  Display a selection menu in case of multiple matches. +recent or frequently visited directories as found in the directory index. +Display a selection menu in case of multiple matches.  Options: -  -a, --add         add specified directories to the directory index -  --unindex         remove specified directories from the index -  -r, --recursive   apply options --add or --unindex recursively +  -a, --add         add specified directories to the directory index. +  --unindex         remove current or specified directories from the index. +  -r, --recursive   apply options --add or --unindex recursively.    --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 -                    directory from ~/.scdalias.zsh -  --list            show matching directories and exit -  -v, --verbose     display directory rank in the selection menu -  -h, --help        display this message and exit +                    directory from ~/.scdalias.zsh. +  -A, --all         include all matching directories.  Disregard matching by +                    directory alias and filtering of less likely paths. +  --list            show matching directories and exit. +  -v, --verbose     display directory rank in the selection menu. +  -h, --help        display this message and exit.  '  local SCD_HISTFILE=${SCD_HISTFILE:-${HOME}/.scdhistory} @@ -35,9 +37,9 @@ local SCD_THRESHOLD=${SCD_THRESHOLD:-0.005}  local SCD_SCRIPT=${RUNNING_AS_COMMAND:+$SCD_SCRIPT}  local SCD_ALIAS=~/.scdalias.zsh -local ICASE a d m p i tdir maxrank threshold +local ICASE a d m p i maxrank threshold  local opt_help opt_add opt_unindex opt_recursive opt_verbose -local opt_alias opt_unalias opt_list +local opt_alias opt_unalias opt_all opt_list  local -A drank dalias  local dmatching  local last_directory @@ -56,7 +58,8 @@ zmodload -i zsh/zutil  zmodload -i zsh/datetime  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 \ +    -alias:=opt_alias -unalias=opt_unalias \ +    A=opt_all -all=opt_all -list=opt_list \      v=opt_verbose -verbose=opt_verbose h=opt_help -help=opt_help \      || $EXIT $? @@ -68,6 +71,11 @@ fi  # load directory aliases if they exist  [[ -r $SCD_ALIAS ]] && source $SCD_ALIAS +# Private internal functions are prefixed with _scd_Y19oug_. +# Clean them up when the scd function returns. +setopt localtraps +trap 'unfunction -m "_scd_Y19oug_*"' EXIT +  # works faster than the (:a) modifier and is compatible with zsh 4.2.6  _scd_Y19oug_abspath() {      set -A $1 ${(ps:\0:)"$( @@ -123,11 +131,52 @@ if [[ -n $opt_unalias ]]; then      $EXIT $?  fi +# The "compress" function collapses repeated directories to +# one entry with a time stamp that gives equivalent-probability. +_scd_Y19oug_compress() { +    awk -v epochseconds=$EPOCHSECONDS -v meanlife=$SCD_MEANLIFE ' +        BEGIN { FS = "[:;]"; } +        length($0) < 4096 && $2 > 0 { +            tau = 1.0 * ($2 - epochseconds) / meanlife; +            if (tau < -6.9078)  tau = -6.9078; +            prob = exp(tau); +            sub(/^[^;]*;/, ""); +            if (NF)  { +                dlist[last[$0]] = ""; +                dlist[NR] = $0; +                last[$0] = NR; +                ptot[$0] += prob; +            } +        } +        END { +            for (i = 1; i <= NR; ++i) { +                d = dlist[i]; +                if (d) { +                    ts = log(ptot[d]) * meanlife + epochseconds; +                    printf(": %.0f:0;%s\n", ts, d); +                } +            } +        } +    ' $* +} +  # 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} +    # compress repeated entries +    m=( ${(f)"$(_scd_Y19oug_compress $SCD_HISTFILE)"} ) +    # purge non-existent directories +    m=( ${(f)"$( +        for a in $m; do +            if [[ -d ${a#*;} ]]; then print -r -- $a; fi +        done +        )"} +    ) +    # cut old entries if still oversized +    if [[ $#m -gt $SCD_HISTSIZE ]]; then +        m=( ${m[-$SCD_HISTSIZE,-1]} ) +    fi +    print -lr -- $m >| ${SCD_HISTFILE}  fi  # Determine the last recorded directory @@ -135,7 +184,6 @@ if [[ -s ${SCD_HISTFILE} ]]; then      last_directory=${"$(tail -1 ${SCD_HISTFILE})"#*;}  fi -# Internal functions are prefixed with "_scd_Y19oug_".  # The "record" function adds its arguments to the directory index.  _scd_Y19oug_record() {      while [[ -n $last_directory && $1 == $last_directory ]]; do @@ -217,7 +265,7 @@ _scd_Y19oug_action() {  # set global arrays dmatching and drank  _scd_Y19oug_match() {      ## single argument that is an existing directory or directory alias -    if [[ $# == 1 ]] && \ +    if [[ -z $opt_all && $# == 1 ]] && \          [[ -d ${d::=$1} || -d ${d::=${nameddirs[$1]}} ]] && [[ -x $d ]];      then          _scd_Y19oug_abspath dmatching $d @@ -227,6 +275,8 @@ _scd_Y19oug_match() {      # ignore case unless there is an argument with an uppercase letter      [[ "$*" == *[[:upper:]]* ]] || ICASE='(#i)' +    # support "$" as an anchor for the directory name ending +    argv=( ${argv/(#m)?[$](#e)/${MATCH[1]}(#e)} )      # 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 @@ -237,10 +287,10 @@ _scd_Y19oug_match() {              BEGIN { FS = "[:;]"; }              length($0) < 4096 && $2 > 0 {                  tau = 1.0 * ($2 - epochseconds) / meanlife; -                if (tau < -4.61)  tau = -4.61; -                prec = exp(tau); +                if (tau < -6.9078)  tau = -6.9078; +                prob = exp(tau);                  sub(/^[^;]*;/, ""); -                if (NF)  ptot[$0] += prec; +                if (NF)  ptot[$0] += prob;              }              END { for (di in ptot)  { print di; print ptot[di]; } }'          )"} @@ -249,9 +299,12 @@ _scd_Y19oug_match() {      # filter drank to the entries that match all arguments      for a; do -        p=${ICASE}"*${a}*" +        p=${ICASE}"*(${a})*"          drank=( ${(kv)drank[(I)${~p}]} )      done +    # require at least one argument matches the directory name +    p=${ICASE}"*(${(j:|:)argv})[^/]#" +    drank=( ${(kv)drank[(I)${~p}]} )      # build a list of matching directories reverse-sorted by their probabilities      dmatching=( ${(f)"$( @@ -261,26 +314,6 @@ _scd_Y19oug_match() {          )"}      ) -    # 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})} ) @@ -302,6 +335,9 @@ _scd_Y19oug_match() {      # discard all directories below the rank threshold      threshold=$(( maxrank * SCD_THRESHOLD )) +    if [[ -n ${opt_all} ]]; then +        threshold=0 +    fi      dmatching=( ${^dmatching}(Ne:'(( ${drank[$REPLY]} >= threshold ))':) )  } @@ -339,6 +375,7 @@ fi  ## here we have multiple matches - display selection menu  a=( {a-z} {A-Z} ) +a=( ${a[1,${#dmatching}]} )  p=( )  for i in {1..${#dmatching}}; do      [[ -n ${a[i]} ]] || break  | 
