This commit is contained in:
GitHub Merge Button 2011-12-21 16:23:45 -08:00
commit 70f04ce6d1
3 changed files with 358 additions and 1 deletions

3
plugins/f/f.plugin.zsh Normal file
View file

@ -0,0 +1,3 @@
# See https://github.com/clvv/f for more info
source $ZSH/plugins/f/f.sh

353
plugins/f/f.sh Normal file
View file

@ -0,0 +1,353 @@
# This tool gives you quick access to your frequent/recent files
#
# INSTALL:
# Source this file somewhere in your shell rc (.bashrc or .zshrc).
#
# SYNOPSIS:
# _f [options] [query ...]
# options:
# -s show list of files with their ranks
# -l list paths only
# -e <cmd> set command to execute on the result file
# -a match files and directories
# -d match directories only
# -f match files only
# -r match by rank only
# -h show a brief help message
#
# EXAMPLES:
# f foo # list recent files mathcing foo
# f foo bar # list recent files mathcing foo and bar
# f -e vim foo # run vim on the most frecent file matching foo
#
# TIPS:
# alias z="f -d -e cd"
# alias v="f -e vim"
# alias m="f -e mplayer"
# alias o="f -e xdg-open"
_f() {
if [ "$1" = "--add" ]; then # add entries
shift
# bail out if we don't own ~/.f (we're another user but our ENV is still set)
[ -f "$_F_DATA" -a ! -O "$_F_DATA" ] && return
# blacklists
local each
for each in "${_F_BLACKLIST[@]}"; do
[[ "$*" =~ "$each" ]] && return
done
# shifts
for each in "${_F_SHIFT[@]}"; do
while [ "$1" = "$each" ]; do shift; done
done
# ignores
[[ "${_F_IGNORE[@]}" =~ "$1" ]] && return
shift
local FILES
while [ "$1" ]; do
# add the adsolute path of the file to FILES, and a delimiter "|"
FILES+="$($_F_READLINK -e -- "$1" 2>> "$_F_SINK")|"
shift
done
# add current pwd if the option set
[ "$_F_TRACK_PWD" = "1" -a "$(pwd -P)" != "$HOME" ] && FILES+="$(pwd -P)"
[ -z "${FILES//|/}" ] && return # stop if we have nothing to add
# maintain the file
local tempfile
tempfile="$(mktemp $_F_DATA.XXXXXX)" || return
$_F_AWK -v list="$FILES" -v now="$(date +%s)" -v max="$_F_MAX" -F"|" '
BEGIN {
split(list, files, "|")
for(i in files) {
path = files[i]
if ( path == "" ) continue
paths[path] = path # array for checking
rank[path] = 1
time[path] = now
}
}
$2 >= 1 {
if( $1 in paths ) {
rank[$1] = $2 + 1
time[$1] = now
} else {
rank[$1] = $2
time[$1] = $3
}
count += $2
}
END {
if( count > max )
for( i in rank ) print i "|" 0.9*rank[i] "|" time[i] # aging
else
for( i in rank ) print i "|" rank[i] "|" time[i]
}' "$_F_DATA" 2>> "$_F_SINK" >| "$tempfile"
if [ $? -ne 0 -a -f "$_F_DATA" ]; then
env rm -f "$tempfile"
else
env mv -f "$tempfile" "$_F_DATA"
fi
elif [ "$1" = "--query" ]; then
# query the database, this need some local variables to be set
while read line; do
[ -${typ:-e} "${line%%|*}" ] && echo "$line"
done < "$_F_DATA" | \
$_F_AWK -v t="$(date +%s)" -v mode="$mode" -v q="$fnd" -F"|" '
function frecent(rank, time) {
dx = t-time
if( dx < 3600 ) return rank*4
if( dx < 86400 ) return rank*2
if( dx < 604800 ) return rank/2
return rank/4
}
function likelihood(pattern, path) {
m = gsub( "/+", "/", path )
r = 1
for( i in pattern ) {
tmp = path
gsub( ".*" pattern[i], "", tmp)
n = gsub( "/+", "/", tmp )
if( n == m )
return 0
else if( n == 0 )
r *= 20 # F
else
r *= 1 - ( n / m )
}
return r
}
function getRank() {
if( mode == "rank" )
f = $2
else
f = frecent($2, $3)
wcase[$1] = f * likelihood( pattern, $1 )
nocase[$1] = f * likelihood( pattern2, tolower($1) )
}
BEGIN {
split(q, pattern, " ")
for( i in pattern ) pattern2[i] = tolower(pattern[i]) # nocase
}
{
getRank()
cx = cx || wcase[$1]
ncx = ncx || nocase[$1]
}
END {
if( cx ) {
for( i in wcase )
if( wcase[i] ) printf "%-10s %s\n", wcase[i], i
} else if( ncx ) {
for( i in nocase )
if( nocase[i] ) printf "%-10s %s\n", nocase[i], i
}
}' - 2>> "$_F_SINK"
else
# parsing logic and processing
[ -f "$_F_DATA" ] || return # no db yet
local fnd last
while [ "$1" ]; do case "$1" in
--complete) [ "$2" = "--" ] && shift; set -- $(echo $2); local list=1 r=r;;
--) while [ "$2" ]; do shift; fnd+="$1 "; last="$1"; done;;
-*) [ "$ZSH_VERSION" ] && local x='o=${o[2,#o]}' || local x='o=${o:1}'
local o=${1#-}; while [ "$o" ]; do case $o in
s*) local show=1;;
l*) local list=1;;
r*) local mode=rank;;
t*) local mode=recent;;
e*) eval $x; if [ "$o" ]; then # there are characters after "-e"
local exec=$o # anything after "-e"
else # use the next argument
local exec=${2:?"Argument needed after -e"}
shift
fi; break;;
a*) local typ=e;;
d*) local typ=d;;
f*) local typ=f;;
h*) echo "_f [options] [query ...]
options:
-s show list of files with their ranks
-l list paths only
-e <cmd> set command to execute on the result file
-a match files and directories
-d match directories only
-f match files only
-r match by rank only
-h show a brief help message" >&2; return;;
#*) fnd+="$1 "; last="$1"; break;; # unknown option detected
esac; eval $x; done;;
*) fnd+="$1 "; last="$1";;
esac; shift; done
# if we hit enter on a completion just execute
case "$last" in
# completions will always start with /
/*) [ -z "$show$list" -a -${typ:-e} "$last" -a "$exec" ] \
&& $exec "$last" && return;;
esac
local result
result="$(_f --query 2>> "$_F_SINK")" # query the database
[ $? -gt 0 ] && return
if [ "$list" ]; then
echo "$result" | sort -n${r} | sed 's/^[0-9.]*[ ]*//'
elif [ "$show" ]; then
echo "$result" | sort -n${r}
elif [ "$fnd" -a "$exec" ]; then # exec
$exec "$(echo "$result" | sort -n | sed -n '$s/^[0-9.]*[ ]*//p')"
elif [ "$fnd" ] && [ "$ZSH_SUBSHELL$BASH_SUBSHELL" != "0" ]; then # echo
echo "$(echo "$result" | sort -n | sed -n '$s/^[0-9.]*[ ]*//p')"
else # no args, show
echo "$result" | sort -n${r}
fi
fi
}
# set default options
alias ${_F_CMD_A:=a}='_f -a'
alias ${_F_CMD_S:=s}='_f -s'
alias ${_F_CMD_D:=d}='_f -d'
alias ${_F_CMD_F:=f}='_f -f'
[ -z "$_F_DATA" ] && _F_DATA="$HOME/.f"
[ -z "$_F_BLACKLIST" ] && _F_BLACKLIST=(--help)
[ -z "$_F_SHIFT" ] && _F_SHIFT=(sudo busybox)
[ -z "$_F_IGNORE" ] && _F_IGNORE=(_f cd ls echo)
[ -z "$_F_SINK" ] && _F_SINK=/dev/null
[ -z "$_F_TRACK_PWD" ] && _F_TRACK_PWD=1
[ -z "$_F_MAX" ] && _F_MAX=2000
[ -z "$_F_QUERY_SEPARATOR" ] && _F_QUERY_SEPARATOR=,
if [ -z "$_F_AWK" ]; then
# awk preferences
for awk in gawk original-awk nawk mawk awk; do
$awk "" >> "$_F_SINK" 2>&1 && _F_AWK=$awk && break
done
fi
if readlink -e / >> "$_F_SINK" 2>&1; then
_F_READLINK=readlink
elif greadlink -e / >> "$_F_SINK" 2>&1; then
_F_READLINK=greadlink
else # fall back on emulated readlink
_f_readlink() {
# function that mimics readlink from GNU coreutils
[ "$1" = "-e" ] && shift && local e=1 # existence option
[ "$1" = "--" ] && shift
[ "$1" = "/" ] && echo / && return
[ "$1" = "." ] && echo "$(pwd -P)" && return
local path
if [ "${1##*/}" = ".." ]; then
path="$(cd "$1" >> "$_F_SINK" 2>&1 && pwd -P)"
[ -z "$path" ] && return 1 # if cd fails
elif [[ "${1#/}" =~ "/" ]]; then
# if target contains "/" (not counting top level) or target is ".."
local base="$(cd "${1%/*}" >> "$_F_SINK" 2>&1 && pwd -P)"
[ -z "$base" ] && return 1 # if cd fails
path="${base%/}/${1##*/}"
elif [ -z "${1##/*}" ]; then # straight top level
path="$1"
else # anything within where we are
path="$(pwd -P)"'/'"$1"
fi
[ "$path" = "/" ] && echo / && return
path=${path%/} # strip off trailing "/"
[ "$e" = "1" -a ! -e "$path" ] && return
echo "$path"
}
_F_READLINK=_f_readlink
fi
if compctl >> "$_F_SINK" 2>&1; then # zsh
_f_zsh_cmd_complete() {
local compl
read -c compl
compstate[insert]=menu # no expand
reply=(${(f)"$(_f --complete "$compl")"})
}
compctl -U -K _f_zsh_cmd_complete -V f -x 'C[-1,-*e],s[-]n[1,e]' -c -- _f
_f_zsh_word_complete() {
local fnd="$(echo "${words[CURRENT]}" | sed 's/'"$_F_QUERY_SEPARATOR"'/ /g')"
local typ=${1:-e}
_f --query 2>> "$_F_SINK" | sort -nr | sed 's/^[0-9.]*[ ]*//' | while read line; do
compadd -U -V f "$line"
done
compstate[insert]=menu # no expand
}
_f_zsh_word_complete_trigger() {
[[ ${words[CURRENT]} == "$_F_QUERY_SEPARATOR"* ]] && _f_zsh_word_complete
}
_f_zsh_word_complete_f() { _f_zsh_word_complete f ; }
_f_zsh_word_complete_d() { _f_zsh_word_complete d ; }
{ zstyle ':completion:*' completer _complete _ignored \
_f_zsh_word_complete_trigger
zle -C f-complete menu-select _f_zsh_word_complete
zle -C f-complete-f menu-select _f_zsh_word_complete_f
zle -C f-complete-d menu-select _f_zsh_word_complete_d
} >> "$_F_SINK" 2>&1
# add zsh hook
autoload -U add-zsh-hook
function _f_preexec () { eval "_f --add $3" >> "$_F_SINK" 2>&1; }
add-zsh-hook preexec _f_preexec
elif complete >> "$_F_SINK" 2>&1; then # bash
_f_bash_cmd_complete() {
# complete command after "-e"
local cur=${COMP_WORDS[COMP_CWORD]}
[[ ${COMP_WORDS[COMP_CWORD-1]} == -*e ]] && \
COMPREPLY=( $(compgen -A command $cur) ) && return
# get completion results using expanded aliases
local RESULT=$( _f --complete "$(alias -p ${COMP_WORDS} | \
sed -n "\$s/^.*'\(.*\)'/\1/p") ${COMP_LINE#* }" )
local IFS=$'\n'
COMPREPLY=( $RESULT )
}
_f_bash_hook_cmd_complete() {
for cmd in $*; do
complete -F _f_bash_cmd_complete $cmd
done
}
_f_bash_hook_cmd_complete $_F_CMD_A $_F_CMD_S $_F_CMD_D $_F_CMD_F
_f_bash_word_complete() {
local cur=${COMP_WORDS[COMP_CWORD]}
if [[ $cur == "$_F_QUERY_SEPARATOR"* ]]; then
local fnd="$(echo "$cur" | sed 's/'"$_F_QUERY_SEPARATOR"'/ /g')"
local typ=e
local RESULT=$(_f --query 2>> "$_F_SINK" | sed 's/^[0-9.]*[ ]*//')
local IFS=$'\n'
COMPREPLY=( $RESULT )
fi
}
_f_bash_word_complete_wrap() {
_f_bash_word_complete
# try original comp func
[ "$COMPREPLY" ] || eval "$( echo "$_F_BASH_COMPLETE_P" | \
grep -e "${COMP_WORDS[0]}$" | sed -n 's/.*-F \(.*\) .*/\1/p' )"
# fall back on original complete options
[ "$COMPREPLY" ] || COMPREPLY=( $(eval "$(echo "$_F_BASH_COMPLETE_P" | \
grep -e "${COMP_WORDS[0]}$" | sed 's/complete/compgen/') \
${COMP_WORDS[COMP_CWORD]}" 2>> "$_F_SINK") )
}
_f_bash_hook_word_complete_wrap_all() {
export _F_BASH_COMPLETE_P="$(complete -p)"
for cmd in $(complete -p | awk '{print $NF}' | tr '\n' ' '); do
complete -F _f_bash_word_complete_wrap $cmd
done
}
complete -D -F _f_bash_word_complete >> "$_F_SINK" 2>&1
# add bash hook
echo $PROMPT_COMMAND | grep -v -q "_f --add" && \
PROMPT_COMMAND='eval "_f --add $(history 1 | \
sed -e "s/^[ ]*[0-9]*[ ]*//")" >> "$_F_SINK" 2>&1;'"$PROMPT_COMMAND"
fi

View file

@ -2,4 +2,5 @@ ZSH_THEME_GIT_PROMPT_PREFIX="%{$fg_bold[blue]%}["
ZSH_THEME_GIT_PROMPT_SUFFIX="]%{$reset_color%} " ZSH_THEME_GIT_PROMPT_SUFFIX="]%{$reset_color%} "
ZSH_THEME_GIT_PROMPT_DIRTY="%{$fg_bold[red]%} x%{$fg_bold[blue]%}" ZSH_THEME_GIT_PROMPT_DIRTY="%{$fg_bold[red]%} x%{$fg_bold[blue]%}"
PROMPT='%{$(git_prompt_info)%}%{$fg_bold[green]%}{%{$(rvm current)%}}%{$reset_color%} %{$fg[cyan]%}%c%{$reset_color%} ' PROMPT='%{$(git_prompt_info)%}%{$fg_bold[green]%}{%{$(rvm current)%}}%{$reset_color%} %{$fg[cyan]%}%c%{$reset_color%}
$ '