# temporarily change options 'builtin' 'local' '-a' '_fzf_tab_opts' [[ ! -o 'aliases' ]] || _fzf_tab_opts+=('aliases') [[ ! -o 'sh_glob' ]] || _fzf_tab_opts+=('sh_glob') [[ ! -o 'no_brace_expand' ]] || _fzf_tab_opts+=('no_brace_expand') 'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand' zmodload zsh/zutil zmodload -F zsh/stat b:zstat 0="${${ZERO:-${0:#$ZSH_ARGZERO}}:-${(%):-%N}}" 0="${${(M)0:#/*}:-$PWD/$0}" FZF_TAB_HOME=${0:h} source ${0:h}/lib/zsh-ls-colors/ls-colors.zsh fzf-tab-lscolors # thanks Valodim/zsh-capture-completion _fzf_tab_compadd() { # parse all options local -A apre hpre dscrs _oad expl local -a isfile _opts __ zparseopts -E -a _opts P:=apre p:=hpre d:=dscrs X:=expl O:=_oad A:=_oad D:=_oad f=isfile \ i: S: s: I: x: r: R: W: F: M+: E: q e Q n U C \ J:=__ V:=__ a=__ l=__ k=__ o=__ 1=__ 2=__ # just delegate and leave if any of -O, -A or -D are given or fzf-tab is not enabled zstyle -t ":fzf-tab:${curcontext#:}" ignore if (( $#_oad != 0 || ! IN_FZF_TAB || ! $? )); then builtin compadd "$@" return fi # store matches in $__hits and descriptions in $__dscr local -a __hits __dscr if (( $#dscrs == 1 )); then __dscr=( "${(@P)${(v)dscrs}}" ) fi builtin compadd -A __hits -D __dscr "$@" local ret=$? if (( $#__hits == 0 )); then return $ret fi # store $curcontext for furthur usage _fzf_tab_curcontext=${curcontext#:} # keep order of group description [[ -n $expl ]] && _fzf_tab_groups+=$expl # store these values in _fzf_tab_compcap local -a keys=(apre hpre isfile PREFIX SUFFIX IPREFIX ISUFFIX) local key expanded __tmp_value=$'<\0>' # placeholder for key in $keys; do expanded=${(P)key} if [[ $expanded ]]; then __tmp_value+=$'\0'$key$'\0'$expanded fi done if [[ $expl ]]; then # store group index __tmp_value+=$'\0group\0'$_fzf_tab_groups[(ie)$expl] fi _opts+=("${(@kv)apre}" "${(@kv)hpre}" $isfile) __tmp_value+=$'\0args\0'${(pj:\1:)_opts} # dscr - the string to show to users # word - the string to be inserted local dscr word i for i in {1..$#__hits}; do word=$__hits[i] dscr=$__dscr[i] if [[ -n $dscr ]]; then dscr=${dscr//$'\n'} elif [[ -n $word ]]; then dscr=$word fi _fzf_tab_compcap+=$dscr$'\2'$__tmp_value${word:+$'\0word\0'$word} done # tell zsh that the match is successful if _fzf_tab_get -t fake-compadd "fakeadd"; then nm=-1 # see _alternative:76 else builtin compadd -U -qS '' -R _fzf_tab_remove_space '' fi } # when insert multi results, a whitespace will be added to each result # remove left space of our fake result because I can't remove right space # FIXME: what if the left char is not whitespace: `echo $widgets[\t` _fzf_tab_remove_space() { [[ $LBUFFER[-1] == ' ' ]] && LBUFFER[-1]='' } : ${(A)=FZF_TAB_GROUP_COLORS=\ $'\033[94m' $'\033[32m' $'\033[33m' $'\033[35m' $'\033[31m' $'\033[38;5;27m' $'\033[36m' \ $'\033[38;5;100m' $'\033[38;5;98m' $'\033[91m' $'\033[38;5;80m' $'\033[92m' \ $'\033[38;5;214m' $'\033[38;5;165m' $'\033[38;5;124m' $'\033[38;5;120m' } (( $+FZF_TAB_OPTS )) || FZF_TAB_OPTS=( --ansi # Enable ANSI color support, necessary for showing groups --expect='$continuous_trigger' # For continuous completion '--color=hl:$(( $#headers == 0 ? 108 : 255 ))' --nth=2,3 --delimiter='\x00' # Don't search prefix --layout=reverse --height='${FZF_TMUX_HEIGHT:=75%}' --tiebreak=begin -m --bind=tab:down,btab:up,change:top,ctrl-space:toggle --cycle '--query=$query' # $query will be expanded to query string at runtime. '--header-lines=$#headers' # $#headers will be expanded to lines of headers at runtime ) _fzf_tab_get() { zstyle $1 ":fzf-tab:$_fzf_tab_curcontext" ${@:2} } () { emulate -L zsh -o extended_glob _fzf_tab_add_default() { zstyle -t ':fzf-tab:*' $1 (( $? != 2 )) || zstyle ':fzf-tab:*' $1 ${@:2} } # Some users may still use variable _fzf_tab_add_default continuous-trigger ${FZF_TAB_CONTINUOUS_TRIGGER:-'/'} _fzf_tab_add_default fake-compadd ${FZF_TAB_FAKE_COMPADD:-default} _fzf_tab_add_default insert-space ${FZF_TAB_INSERT_SPACE:-true} _fzf_tab_add_default query-string ${(A)=FZF_TAB_QUERY:-prefix input first} _fzf_tab_add_default single-group ${(A)=FZF_TAB_SINGLE_GROUP:-color header} _fzf_tab_add_default show-group ${FZF_TAB_SHOW_GROUP:-full} _fzf_tab_add_default command ${FZF_TAB_COMMAND:-fzf} $FZF_TAB_OPTS _fzf_tab_add_default extra-opts '' _fzf_tab_add_default no-group-color ${FZF_TAB_NO_GROUP_COLOR:-$'\033[37m'} _fzf_tab_add_default group-colors $FZF_TAB_GROUP_COLORS _fzf_tab_add_default ignore false if zstyle -m ':completion:*:descriptions' format '*'; then _fzf_tab_add_default prefix 'ยท' else _fzf_tab_add_default prefix '' fi unfunction _fzf_tab_add_default } # sets `query` to the valid query string _fzf_tab_find_query_str() { local key qtype tmp query_string typeset -g query= _fzf_tab_get -a query-string query_string for qtype in $query_string; do if [[ $qtype == prefix ]]; then # find the longest common prefix among descriptions local -a keys=(${_fzf_tab_compcap%$'\2'*}) tmp=$keys[1] local MATCH match mbegin mend prefix=(${(s::)tmp}) for key in ${keys:1}; do (( $#tmp )) || break [[ $key == $tmp* ]] && continue # interpose characters from the current common prefix and $key and see how # many pairs of equal characters we get at the start of the resulting string [[ ${(j::)${${(s::)key[1,$#tmp]}:^prefix}} =~ '^(((.)\3)*)' ]] # truncate common prefix and maintain loop invariant: ${(s::)tmp} == $prefix tmp[$#MATCH/2+1,-1]="" prefix[$#MATCH/2+1,-1]=() done elif [[ $qtype == input ]]; then local fv=${_fzf_tab_compcap[1]#*$'\2'} local -A v=("${(@0)fv}") tmp=$v[PREFIX] if (( $RBUFFER[(i)$v[SUFFIX]] != 1 )); then tmp=${tmp/%$v[SUFFIX]} fi tmp=${${tmp#$v[hpre]}#$v[apre]} fi if (( $query_string[(I)longest] )); then (( $#tmp > $#query )) && query=$tmp elif [[ -n $tmp ]]; then query=$tmp && break fi done } # pupulates array `headers` with group descriptions _fzf_tab_get_headers() { typeset -ga headers=() local i tmp group_colors local -i mlen=0 len=0 if (( $#_fzf_tab_groups == 1 )) && { ! _fzf_tab_get -m single-group "header" }; then return fi # calculate the max column width for i in $_fzf_tab_groups; do (( $#i > mlen )) && mlen=$#i done mlen+=1 _fzf_tab_get -a group-colors group_colors for (( i=1; i<=$#_fzf_tab_groups; i++ )); do [[ $_fzf_tab_groups[i] == "__hide__"* ]] && continue if (( len + $#_fzf_tab_groups[i] > COLUMNS - 5 )); then headers+=$tmp tmp='' && len=0 fi if (( len + mlen > COLUMNS - 5 )); then # the last column doesn't need padding headers+=$tmp$group_colors[i]$_fzf_tab_groups[i]$'\033[00m' tmp='' && len=0 else tmp+=$group_colors[i]${(r:$mlen:)_fzf_tab_groups[i]}$'\033[00m' len+=$mlen fi done (( $#tmp )) && headers+=$tmp } _fzf_tab_colorize() { emulate -L zsh -o cbases -o octalzeroes local REPLY local -a reply stat lstat # fzf-tab-lscolors::match-by $1 lstat follow zstat -A lstat -L -- $1 # follow symlink (( lstat[3] & 0170000 )) && zstat -A stat -- $1 2>/dev/null fzf-tab-lscolors::from-mode "$1" "$lstat[3]" $stat[3] # fall back to name [[ -z $REPLY ]] && fzf-tab-lscolors::from-name $1 # If this is a symlink if [[ $lstat[14] ]]; then local sym_color=$REPLY local rsv_color=$REPLY local rsv=$lstat[14] # If this is not a broken symlink if [[ -e $rsv ]]; then # fzf-tab-lscolors::match-by $rsv stat zstat -A stat -- $rsv fzf-tab-lscolors::from-mode $rsv $stat[3] # fall back to name [[ -z $REPLY ]] && fzf-tab-lscolors::from-name $rsv rsv_color=$REPLY fi dpre=$'\033[0m\033['$sym_color'm' dsuf+=$'\033[0m -> \033['$rsv_color'm'$rsv else dpre=$'\033[0m\033['$REPLY'm' fi } # pupulates array `candidates` with completion candidates _fzf_tab_get_candidates() { local dsuf dpre k _v filepath first_word show_group no_group_color prefix bs=$'\b' local -a list_colors group_colors tcandidates reply match mbegin mend local -i same_word=1 colorful=0 local -Ua duplicate_groups=() local -A word_map=() (( $#_fzf_tab_compcap == 0 )) && return _fzf_tab_get -s show-group show_group _fzf_tab_get -a group-colors group_colors _fzf_tab_get -s no-group-color no_group_color _fzf_tab_get -s prefix prefix zstyle -a ":completion:$_fzf_tab_curcontext" list-colors list_colors if (( $+builtins[fzf-tab-colorize] )); then fzf-tab-colorize -c list_colors else local -A namecolors=(${(@s:=:)${(@s.:.)list_colors}:#[[:alpha:]][[:alpha:]]=*}) local -A modecolors=(${(@Ms:=:)${(@s.:.)list_colors}:#[[:alpha:]][[:alpha:]]=*}) fi if (( $#_fzf_tab_groups == 1 )); then _fzf_tab_get -m single-group prefix || prefix='' _fzf_tab_get -m single-group color || group_colors=($no_group_color) fi for k _v in "${(@ps:\2:)_fzf_tab_compcap}"; do local -A v=("${(@0)_v}") [[ $v[word] == ${first_word:=$v[word]} ]] || same_word=0 # add character and color to describe the type of the files dsuf='' dpre='' if (( $+v[isfile] )); then filepath=${v[IPREFIX]}${v[hpre]}${k#*$'\b'} filepath=${(Q)${(e)~filepath}} if (( $#list_colors && $+builtins[fzf-tab-colorize] )); then fzf-tab-colorize $filepath 2>/dev/null dpre=$reply[2]$reply[1] dsuf=$reply[2]$reply[3] if [[ $reply[4] ]]; then dsuf+=" -> $reply[4]" fi [[ $dpre ]] && colorful=1 else if [[ -d $filepath ]]; then dsuf=/ fi # add color and resolve symlink if have list-colors # detail: http://zsh.sourceforge.net/Doc/Release/Zsh-Modules.html#The-zsh_002fcomplist-Module if (( $#list_colors )) && [[ -a $filepath || -L $filepath ]]; then _fzf_tab_colorize $filepath colorful=1 elif [[ -L $filepath ]]; then dsuf=@ fi if [[ $options[list_types] == off ]]; then dsuf='' fi fi fi # add color to description if they have group index if (( $+v[group] )); then local color=$group_colors[$v[group]] # add a hidden group index at start of string to keep group order when sorting # first group index is for builtin sort, sencond is for GNU sort tcandidates+=$v[group]$'\b'$color$prefix$dpre$'\0'$v[group]$'\b'$k$'\0'$dsuf else tcandidates+=$no_group_color$dpre$'\0'$k$'\0'$dsuf fi # check group with duplicate member if [[ $show_group == brief ]]; then if (( $+word_map[$v[word]] && $+v[group] )); then duplicate_groups+=$v[group] # add this group duplicate_groups+=$word_map[$v[word]] # add previous group fi word_map[$v[word]]=$v[group] fi done (( same_word )) && tcandidates[2,-1]=() # sort and remove sort group or other index zstyle -T ":completion:$_fzf_tab_curcontext" sort if (( $? != 1 )); then if (( colorful )); then # if enable list_colors, we should skip the first field if [[ ${commands[sort]:A:t} != (|busybox*) ]]; then # this is faster but doesn't work if `find` is from busybox tcandidates=(${(f)"$(command sort -u -t '\0' -k 2 <<< ${(pj:\n:)tcandidates})"}) else # slower but portable tcandidates=(${(@o)${(@)tcandidates:/(#b)([^$'\0']#)$'\0'(*)/$match[2]$'\0'$match[1]}}) tcandidates=(${(@)tcandidates/(#b)(*)$'\0'([^$'\0']#)/$match[2]$'\0'$match[1]}) fi else tcandidates=("${(@o)tcandidates}") fi fi typeset -gUa candidates=("${(@)tcandidates//[0-9]#$bs}") # hide needless group if [[ $show_group == brief ]]; then local i indexs=({1..$#_fzf_tab_groups}) for i in ${indexs:|duplicate_groups}; do # NOTE: _fzf_tab_groups is unique array _fzf_tab_groups[i]="__hide__$i" done fi } _fzf_tab_complete() { local -a _fzf_tab_compcap local -Ua _fzf_tab_groups local choice choices _fzf_tab_curcontext continuous_trigger ignore bs=$'\2' _fzf_tab__main_complete # must run with user options; don't move `emulate -L zsh` above this line emulate -L zsh -o extended_glob # check if we should fall back to zsh builtin completion system if (( _fzf_tab_ignored )); then return fi _fzf_tab_get -s ignore ignore if [[ $ignore == <-> ]] && (( $ignore > 1 && $ignore >= $#_fzf_tab_compcap )); then _fzf_tab_ignored=1 compstate[list]='' compstate[insert]='' return fi local query candidates=() headers=() command opts _fzf_tab_get_candidates # sets `candidates` case $#candidates in 0) return;; # NOTE: won't trigger continuous completion 1) choices=("${_fzf_tab_compcap[1]%$bs*}");; *) _fzf_tab_find_query_str # sets `query` _fzf_tab_get_headers # sets `headers` _fzf_tab_get -s continuous-trigger continuous_trigger _fzf_tab_get -a command command _fzf_tab_get -a extra-opts opts export CTXT=${${_fzf_tab_compcap[1]#*$'\2'}//$'\0'/$'\2'} if (( $#headers )); then choices=$(${(eX)command} $opts <<<${(pj:\n:)headers} <<<${(pj:\n:)candidates}) else choices=$(${(eX)command} $opts <<<${(pj:\n:)candidates}) fi choices=(${${${(f)choices}%$'\0'*}#*$'\0'}) unset CTXT ;; esac if [[ $choices[1] && $choices[1] == $continuous_trigger ]]; then typeset -gi _fzf_tab_continue=1 choices[1]=() fi for choice in "$choices[@]"; do local -A v=("${(@0)${_fzf_tab_compcap[(r)${(b)choice}$bs*]#*$bs}}") local -a args=("${(@ps:\1:)v[args]}") [[ -z $args[1] ]] && args=() # don't pass an empty string IPREFIX=$v[IPREFIX] PREFIX=$v[PREFIX] SUFFIX=$v[SUFFIX] ISUFFIX=$v[ISUFFIX] builtin compadd "${args[@]:--Q}" -Q -- "$v[word]" done compstate[list]= compstate[insert]= if (( $#choices == 1 )); then if _fzf_tab_get -t fake-compadd "fakeadd"; then compstate[insert]='1' else compstate[insert]='2' fi _fzf_tab_get -t insert-space (( $? )) || [[ $RBUFFER == ' '* ]] || compstate[insert]+=' ' elif (( $#choices > 1 )); then compstate[insert]='all' fi } fzf-tab-complete() { # this name must be ugly to avoid clashes local -i _fzf_tab_continue=1 _fzf_tab_ignored=0 while (( _fzf_tab_continue )); do _fzf_tab_continue=0 local IN_FZF_TAB=1 { zle .fzf-tab-orig-$_fzf_tab_orig_widget } always { IN_FZF_TAB=0 } if (( _fzf_tab_ignored )); then zle .fzf-tab-orig-$_fzf_tab_orig_widget return fi if (( _fzf_tab_continue )); then zle .split-undo zle .reset-prompt zle -R else zle redisplay fi done } zle -N fzf-tab-complete disable-fzf-tab() { emulate -L zsh -o extended_glob (( $+_fzf_tab_orig_widget )) || return 0 bindkey '^I' $_fzf_tab_orig_widget case $_fzf_tab_orig_list_grouped in 0) zstyle ':completion:*' list-grouped false ;; 1) zstyle ':completion:*' list-grouped true ;; 2) zstyle -d ':completion:*' list-grouped ;; esac unset _fzf_tab_orig_widget _fzf_tab_orig_list_groupded # unhook compadd so that _approximate can work properply unfunction compadd 2>/dev/null functions[_main_complete]=$functions[_fzf_tab__main_complete] functions[_approximate]=$functions[_fzf_tab__approximate] # Don't remove .fzf-tab-orig-$_fzf_tab_orig_widget as we won't be able to reliably # create it if enable-fzf-tab is called again. } enable-fzf-tab() { emulate -L zsh -o extended_glob (( ! $+_fzf_tab_orig_widget )) || disable-fzf-tab typeset -g _fzf_tab_orig_widget="${${$(bindkey '^I')##* }:-expand-or-complete}" if (( ! $+widgets[.fzf-tab-orig-$_fzf_tab_orig_widget] )); then # Widgets that get replaced by compinit. local compinit_widgets=( complete-word delete-char-or-list expand-or-complete expand-or-complete-prefix list-choices menu-complete menu-expand-or-complete reverse-menu-complete ) # Note: We prefix the name of the widget with '.' so that it doesn't get wrapped. if [[ $widgets[$_fzf_tab_orig_widget] == builtin && $compinit_widgets[(Ie)$_fzf_tab_orig_widget] != 0 ]]; then # We are initializing before compinit and being asked to fall back to a completion # widget that isn't defined yet. Create our own copy of the widget ahead of time. zle -C .fzf-tab-orig-$_fzf_tab_orig_widget .$_fzf_tab_orig_widget _main_complete else # Copy the widget before it's wrapped by zsh-autosuggestions and zsh-syntax-highlighting. zle -A $_fzf_tab_orig_widget .fzf-tab-orig-$_fzf_tab_orig_widget fi fi zstyle -t ':completion:*' list-grouped false typeset -g _fzf_tab_orig_list_grouped=$? zstyle ':completion:*' list-grouped false bindkey '^I' fzf-tab-complete # make sure we can copy them autoload +X -Uz _main_complete _approximate # hook compadd functions[compadd]=$functions[_fzf_tab_compadd] # hook _main_complete to trigger fzf-tab functions[_fzf_tab__main_complete]=$functions[_main_complete] function _main_complete() { _fzf_tab_complete } # TODO: This is not a full support, see #47 # _approximate will also hook compadd # let it call _fzf_tab_compadd instead of builtin compadd so that fzf-tab can capture result # make sure _approximate has been loaded. functions[_fzf_tab__approximate]=$functions[_approximate] function _approximate() { # if not called by fzf-tab, don't do anything with compadd (( ! IN_FZF_TAB )) || unfunction compadd _fzf_tab__approximate (( ! IN_FZF_TAB )) || functions[compadd]=$functions[_fzf_tab_compadd] } } toggle-fzf-tab() { emulate -L zsh -o extended_glob if (( $+_fzf_tab_orig_widget )); then disable-fzf-tab else enable-fzf-tab fi } build-fzf-tab-module() { pushd $FZF_TAB_HOME/modules CPPFLAGS=-I/usr/local/include CFLAGS="-g -Wall -O3" LDFLAGS=-L/usr/local/lib ./configure --disable-gdbm --without-tcsetpgrp make -j popd } () { if [[ -e $FZF_TAB_HOME/modules/Src/aloxaf/fzftab.so ]]; then module_path+=("$FZF_TAB_HOME/modules/Src") zmodload aloxaf/fzftab if [[ $FZF_TAB_MODULE_VERSION != "0.1.1" ]]; then zmodload -u aloxaf/fzftab local rebuild print -Pn "%F{yellow}fzftab module needs to be rebuild, rebuild now?[Y/n]:%f" read -q rebuild if [[ $rebuild == y ]]; then build-fzf-tab-module zmodload aloxaf/fzftab fi fi fi } enable-fzf-tab zle -N toggle-fzf-tab # restore options (( ${#_fzf_tab_opts} )) && setopt ${_fzf_tab_opts[@]} 'builtin' 'unset' '_fzf_tab_opts'