* scd - smart change directory
@ 2010-08-15 5:53 Pavol Juhas
0 siblings, 0 replies; 2+ messages in thread
From: Pavol Juhas @ 2010-08-15 5:53 UTC (permalink / raw)
To: zsh workers
[-- Attachment #1: Type: text/plain, Size: 2013 bytes --]
Hello,
I have another iteration of the "scd" function for a quick
change to any directory. The function keeps a history of
the visited directories, which serves as an index of the existing
paths. The match is more likely for recently visited directories,
a selection menu is used in case of several matches. The scd
supports quick creation and removal of permanent directory aliases
(stored in ~/.scdalias.zsh).
I would welcome comments regarding this script.
Cheers,
Pavol
------------------------------------------------------------------------
Installation for zsh: copy the scd script to some directory in $fpath
and add the following lines to the .zshrc file:
# Activate scd
autoload scd
# Add chpwd hook to record all visited directories.
# The hook is executed only when scd function exists.
scd_chpwd_hook() { [[ ${+functions[scd]} == 0 ]] || scd --add . }
autoload add-zsh-hook
add-zsh-hook chpwd scd_chpwd_hook
# Optional - load directory aliases created by scd.
# [[ -f ~/.scdalias.zsh ]] && source ~/.scdalias.zsh
Bourne-style shells: copy the scd script to ${HOME}/bin/
and make it executable. Add the following lines to .bashrc
or an equivalent file:
export SCD_SCRIPT=${HOME}/bin/.scd.go
scd() {
command scd "$@" && . $SCD_SCRIPT
}
C Shell (csh): Same as above, but use the following lines in .cshrc:
setenv SCD_SCRIPT ${HOME}/bin/.scd.go
alias scd "scd \!* && source $SCD_SCRIPT"
------------------------------------------------------------------------
Examples:
# Index recursively some paths for a very first run.
# The index gets otherwise built with every cd command.
scd -ar /tmp/
# Change to a directory path matching "doc"
scd doc
# Change to a path matching all of "a", "b" and "c"
scd a b c
# Change to a directory path ending in "in"
scd "in(#e)"
# Show selection menu and ranking of 25 most likely directories
scd -v
# Brief usage info:
scd --help
[-- Attachment #2: scd --]
[-- Type: text/plain, Size: 6948 bytes --]
#!/bin/zsh -if
# $Id: scd 170 2010-08-15 05:19:17Z juhas $
emulate -L zsh
if [[ $(whence -w $0) == *:' 'command ]]; then
emulate -R zsh
alias return=exit
local RUNNING_AS_COMMAND=1
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.
Options:
-a, --add add specified directories to the directory index
-r, --recursive add directoriese recursively for option --add
--alias=ALIAS create alias for the current or specified directory and
store it in ~/.scdalias.zsh
--unalias remove ALIAS definition for the current or specified
directory from ~/.scdalias.zsh
-v, --verbose display directory rank in the selection menu
-h, --help display this message and exit
'
local SCD_HISTFILE=~/.scdhistory
local SCD_HISTSIZE=${SCD_HISTSIZE:-5000}
local SCD_MENUSIZE=${SCD_MENUSIZE:-25}
local SCD_MEANLIFE=${SCD_MEANLIFE:-86400}
local SCD_THRESHOLD=${SCD_THRESHOLD:-0.005}
local SCD_SCRIPT=${SCD_SCRIPT:-}
local SCD_ALIAS=~/.scdalias.zsh
local ICASE a d m p i tdir maxrank threshold
local opt_help opt_add opt_recursive opt_verbose opt_alias opt_unalias
local -A drank dalias
local dmatching
setopt incappendhistory extendedhistory extendedglob noautonamedirs
[[ ${+options[histsavebycopy]} == 1 ]] && setopt nohistsavebycopy
# make sure that any old commands are removed from SCD_SCRIPT
[[ -n "$SCD_SCRIPT" && -s $SCD_SCRIPT ]] && : >| $SCD_SCRIPT
# process command line options
zmodload -i zsh/zutil
zmodload -i zsh/datetime
zparseopts -D -- h=opt_help -help=opt_help \
a=opt_add -add=opt_add \
r=opt_recursive -recursive=opt_recursive \
v=opt_verbose -verbose=opt_verbose \
-alias:=opt_alias -unalias=opt_unalias \
|| return $?
if [[ -n $opt_help ]]; then
print $DOC
return
fi
# load directory aliases if they exist
[[ -r $SCD_ALIAS ]] && source $SCD_ALIAS
# define directory alias
if [[ -n $opt_alias ]]; then
if [[ -n $1 && ! -d $1 ]]; then
print -u2 "'$1' is not a directory"
return 1
fi
a=${opt_alias[-1]#=}
d=$(unfunction -m "*"; cd ${1:-.}; pwd)
# alias in the current shell, update alias file if successful
hash -d -- $a=$d &&
(
umask 077
hash -dr
[[ -r $SCD_ALIAS ]] && source $SCD_ALIAS
hash -d -- $a=$d
hash -dL >| $SCD_ALIAS
)
return $?
fi
# undefine directory alias
if [[ -n $opt_unalias ]]; then
if [[ -n $1 && ! -d $1 ]]; then
print -u2 "'$1' is not a directory"
return 1
fi
a=$(unfunction -m "*"; cd ${1:-.}; print -rP "%~")
if [[ $a != [~][^/]## ]]; then
return 0
fi
a=${a#[~]}
# unalias in the current shell, update alias file if successful
if unhash -d -- $a 2>/dev/null && [[ -r $SCD_ALIAS ]]; then
(
umask 077
hash -dr
source $SCD_ALIAS
unhash -d -- $a 2>/dev/null &&
hash -dL >| $SCD_ALIAS
)
fi
return $?
fi
# define custom history file
fc -a -p $SCD_HISTFILE $SCD_HISTSIZE
if [[ -n $opt_add ]]; then
for a in ${*:-.}; do
if [[ ! -d $a ]]; then
print -u 2 "Directory $a does not exist"
return 2
fi
d=$(unfunction -m "*"; cd $a; pwd)
print -rs -- $d
if [[ -n $opt_recursive ]]; then
print -n "scanning ${d} ... "
for i in ${d}/**/*(-/N); do
print -rs -- $i
done
print "[done]"
fi
done
return
fi
# self destructive action command
scd_action() {
if [[ $# == 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
# update SCD_HISTFILE unless already done in chpwd hook
[[ -N $SCD_HISTFILE ]] || print -rs $PWD
fi
}
trap 'unfunction scd_action' EXIT
# take care of existing directories
if [[ $# == 1 && -d $1 ]]; then
scd_action $1
return $?
# take care of exact aliases
elif [[ $# == 1 ]] && d=${$(hash -d -m "(#s)$1")#${1}=} && [[ -d $d ]]; then
scd_action $d
return $?
fi
[[ "$*" == *[[:upper:]]* ]] || ICASE='(#i)'
# calculate rank for all directories in the history
drank=( ${(f)"$(
tail -${SCD_HISTSIZE} $SCD_HISTFILE |
awk -v epochseconds=$EPOCHSECONDS -v meanlife=$SCD_MEANLIFE '
BEGIN { FS = "[:;]"; }
length($0) < 4096 {
pi = 0.01 + exp(1.0 * ($2 - epochseconds) / meanlife);
sub(/^[^;]*;/, "");
p[$0] += pi;
}
END { for (di in p) { print di; print p[di]; } }'
)"}
)
for a; do
p=${ICASE}"*${a}*"
drank=( ${(kv)drank[(I)${~p}]} )
done
# build matching directories sorted by rank
dmatching=( ${(f)"$( for d p in ${(kv)drank}; do print -r -- "$p $d"; done |
sort -grk1 | cut -d ' ' -f 2- )"} )
# reduce to exact matches
# patterns follow each other
p=${ICASE}"*${(j:*:)argv}*"
m=( ${(M)dmatching:#${~p}} )
[[ -d ${m[1]} ]] && dmatching=( $m )
# last pattern is in the path tail
p=${ICASE}"*${(j:*:)argv}[^/]#"
m=( ${(M)dmatching:#${~p}} )
[[ -d ${m[1]} ]] && dmatching=( $m )
# all patterns are present in the path tail
m=( $dmatching )
for a; do
p=${ICASE}"*/[^/]#${a}[^/]#"
m=( ${(M)m:#${~p}} )
done
[[ -d ${m[1]} ]] && dmatching=( $m )
# all patterns are in the path tail following each other
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
# cut dmatching to $SCD_MENUSIZE existing directories
m=( )
for d in $dmatching; do
[[ ${#m} == $SCD_MENUSIZE ]] && break
[[ -d $d ]] && m+=$d
done
dmatching=( $m )
# find out maximum rank
maxrank=0.0
for d in $dmatching; do
[[ ${drank[$d]} -lt maxrank ]] || maxrank=${drank[$d]}
done
# cut out directories below rank threshold
threshold=$(( maxrank * SCD_THRESHOLD ))
dmatching=( ${^dmatching}(Ne:'(( ${drank[$REPLY]} >= threshold ))':) )
case ${#dmatching} in
(0)
print -u2 "no matching directory"
return 1
;;
(1)
scd_action $dmatching
return $?
;;
(*)
m=( ${(f)"$(unfunction -m "*";
for d in ${dmatching}; do
cd $d
[[ -n $opt_verbose ]] && printf "%.3g " ${drank[$d]}
print -P "%~"
done)"} )
for i in {1..${#m}}; dalias[${m[i]}]=$dmatching[i]
select d in ${m}; do
scd_action ${dalias[$d]}
return $?
done
esac
^ permalink raw reply [flat|nested] 2+ messages in thread
* scd - smart change directory
@ 2010-07-15 16:51 Pavol Juhas
0 siblings, 0 replies; 2+ messages in thread
From: Pavol Juhas @ 2010-07-15 16:51 UTC (permalink / raw)
To: zsh workers
[-- Attachment #1: Type: text/plain, Size: 931 bytes --]
Well, I just read the thread on the "cdr" function - seems
that it is indeed hard to come up with anything new. :)
Anyway, here is the scd code. A directory ranking is
calculated from the frequency and time of its visits,
which are recorded in the ~/.scdhistory file.
The scd function accepts one or more patterns arguments,
where all patterns must be present in the full path.
Directories that match with their tail name are preferred.
If scd argument is an existing directory or a directory
alias, scd would jump to that path.
scd can be used as autoloadable zsh function
autoload scd
scd p1 p2 ...
or as a zsh executable, which writes a "cd" command to a
file given by the $SCD_SCRIPT environment variable. The later
mode should support the use of scd from other shells or from the
vim editor.
Cheers,
Pavol
PS: The .scdhistory file is loaded with awk to avoid the
issues with truncated alternate history.
[-- Attachment #2: scd --]
[-- Type: text/plain, Size: 5127 bytes --]
#!/bin/zsh
# $Id: scd 127 2010-07-15 16:48:32Z juhas $
emulate -L zsh
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.
Options:
-a, --add add specified directories to the directory index
-r, --recursive add directoriese recursively for option --add
-v, --verbose display directory rank in the selection menu
-h, --help display this message and exit
This function adds a chpwd hook that records all visited directories.
'
local SCD_HISTFILE=~/.scdhistory
local SCD_HISTSIZE=${SCD_HISTSIZE:-5000}
local SCD_MENUSIZE=${SCD_MENUSIZE:-25}
local SCD_MEANLIFE=${SCD_MEANLIFE:-86400}
local SCD_THRESHOLD=${SCD_THRESHOLD:-0.005}
local SCD_SCRIPT=${SCD_SCRIPT:-}
local ICASE a d m p i tnow tdir maxrank threshold
local opt_help opt_recursive opt_verbose opt_add
local -A drank dalias
local dmatching
setopt nohistsavebycopy extendedhistory extendedglob warncreateglobal
# self destructive action command
scd_action() {
if [[ $# == 1 ]]; then
[[ -n $SCD_SCRIPT ]] && (umask 077;
print -r "cd ${(q)1}" >| $SCD_SCRIPT)
cd $1
fi
unfunction scd_action
}
# define chpwd hook
if [[ -o interactive && ${+functions[scd_chpwd_hook]} == 0 ]]; then
scd_chpwd_hook() { scd --add -- $PWD }
autoload add-zsh-hook
add-zsh-hook chpwd scd_chpwd_hook
fi
# process command line options
zmodload -i zsh/zutil
zparseopts -D -- h=opt_help -help=opt_help \
a=opt_add -add=opt_add \
r=opt_recursive -recursive=opt_recursive \
v=opt_verbose -verbose=opt_verbose || return $?
if [[ -n $opt_help ]]; then
print $DOC
return
fi
# define custom history file
fc -a -p $SCD_HISTFILE $SCD_HISTSIZE
if [[ -n $opt_add || -n $opt_recursive ]]; then
for a in ${*:-.}; do
if [[ ! -d $a ]]; then
print -u 2 "Directory $a does not exist"
return 2
fi
d=$(unfunction -m scd_chpwd_hook; cd $a; pwd)
print -rs -- $d
if [[ -n $opt_recursive ]]; then
print -n "scanning ${d} ... "
m=( ${d}/**/*(/N) )
[[ ${#m} == 0 ]] || print -rsl -- $m
print "[done]"
fi
done
return
fi
# wipe out the script file
[[ -n $SCD_SCRIPT ]] && /bin/rm -f -- $SCD_SCRIPT
# take care of existing directories
if [[ $# == 1 && -d $1 ]]; then
scd_action $1
return $?
# take care of exact aliases
elif [[ $# == 1 ]] && d=${$(hash -d -m "(#s)$1")#${1}=} && [[ -d $d ]]; then
scd_action $d
return $?
fi
[[ "$*" == *[[:upper:]]* ]] || ICASE='(#i)'
# calculate rank for all directories in the history
tnow=${(%):-"%D{%s}"}
drank=( ${(f)"$(
tail -${SCD_HISTSIZE} $SCD_HISTFILE |
awk -v tnow=$tnow -v meanlife=$SCD_MEANLIFE '
BEGIN { FS = "[:;]"; }
{ pi = 0.01 + exp(1.0 * ($2 - tnow) / meanlife);
sub(/^[^;]*;/, "");
p[$0] += pi;
}
END { for (di in p) { print di; print p[di]; } }'
)"}
)
for a; do
p=${ICASE}"*${a}*"
drank=( ${(kv)drank[(I)${~p}]} )
done
# build matching directories sorted by rank
dmatching=( ${(f)"$( for d p in ${(kv)drank}; do print -r -- "$p $d"; done |
sort -grk1 | cut -d ' ' -f 2- )"} )
# reduce to exact matches
# patterns follow each other
p=${ICASE}"*${(j:*:)argv}*"
m=( ${(M)dmatching:#${~p}} )
[[ -d ${m[1]} ]] && dmatching=( $m )
# last pattern is in the path tail
p=${ICASE}"*${(j:*:)argv}[^/]#"
m=( ${(M)dmatching:#${~p}} )
[[ -d ${m[1]} ]] && dmatching=( $m )
# all patterns are present in the path tail
m=( $dmatching )
for a; do
p=${ICASE}"*/[^/]#${a}[^/]#"
m=( ${(M)m:#${~p}} )
done
[[ -d ${m[1]} ]] && dmatching=( $m )
# all patterns are in the path tail
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
# cut dmatching to $SCD_MENUSIZE existing directories
m=( )
for d in $dmatching; do
[[ ${#m} == $SCD_MENUSIZE ]] && break
[[ -d $d ]] && m+=$d
done
dmatching=( $m )
# find out maximum rank
maxrank=0.0
for d in $dmatching; do
[[ ${drank[$d]} -lt maxrank ]] || maxrank=${drank[$d]}
done
# cut out directories below rank threshold
threshold=$(( maxrank * SCD_THRESHOLD ))
dmatching=( ${(f)"$(
for d in ${dmatching}; do
(( ${drank[$d]} > threshold )) && print -r -- $d
done)"}
)
dmatching=( $dmatching )
case ${#dmatching} in
(0)
print -u2 "no matching directory"
return 1
;;
(1)
scd_action $dmatching
return $?
;;
(*)
m=( ${(f)"$(unfunction -m scd_chpwd_hook;
for d in ${dmatching}; do
cd $d
[[ -n $opt_verbose ]] && printf "%.3g " ${drank[$d]}
print -P "%~"
done)"} )
for i in {1..${#m}}; dalias[${m[i]}]=$dmatching[i]
select d in ${m}; do
scd_action ${dalias[$d]}
return $?
done
esac
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2010-08-15 6:20 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-08-15 5:53 scd - smart change directory Pavol Juhas
-- strict thread matches above, loose matches on Subject: below --
2010-07-15 16:51 Pavol Juhas
Code repositories for project(s) associated with this public inbox
https://git.vuxu.org/mirror/zsh/
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).