From 662ba4a2d1b01a3266610ce6f7843eb02127c6c1 Mon Sep 17 00:00:00 2001 From: xfnw Date: Fri, 3 Jul 2020 22:43:48 -0500 Subject: [PATCH] fuzzy tabbing --- .zsh/fzf-tab/.gitattributes | 2 + .../.github/ISSUE_TEMPLATE/bug_report.md | 31 + .../.github/ISSUE_TEMPLATE/feature_request.md | 20 + .zsh/fzf-tab/.github/workflows/test.yaml | 24 + .zsh/fzf-tab/.gitignore | 1 + .zsh/fzf-tab/LICENSE | 21 + .zsh/fzf-tab/fzf-tab.plugin.zsh | 1 + .zsh/fzf-tab/fzf-tab.zsh | 588 ++++++++++++++++++ .zsh/fzf-tab/lib/zsh-ls-colors/LICENSE | 21 + .zsh/fzf-tab/lib/zsh-ls-colors/README.md | 114 ++++ .zsh/fzf-tab/lib/zsh-ls-colors/demo | 65 ++ .zsh/fzf-tab/lib/zsh-ls-colors/ls-colors.zsh | 186 ++++++ .zshrc | 2 +- 13 files changed, 1075 insertions(+), 1 deletion(-) create mode 100644 .zsh/fzf-tab/.gitattributes create mode 100644 .zsh/fzf-tab/.github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .zsh/fzf-tab/.github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .zsh/fzf-tab/.github/workflows/test.yaml create mode 100644 .zsh/fzf-tab/.gitignore create mode 100644 .zsh/fzf-tab/LICENSE create mode 120000 .zsh/fzf-tab/fzf-tab.plugin.zsh create mode 100644 .zsh/fzf-tab/fzf-tab.zsh create mode 100644 .zsh/fzf-tab/lib/zsh-ls-colors/LICENSE create mode 100644 .zsh/fzf-tab/lib/zsh-ls-colors/README.md create mode 100755 .zsh/fzf-tab/lib/zsh-ls-colors/demo create mode 100644 .zsh/fzf-tab/lib/zsh-ls-colors/ls-colors.zsh diff --git a/.zsh/fzf-tab/.gitattributes b/.zsh/fzf-tab/.gitattributes new file mode 100644 index 0000000..6ad6d1a --- /dev/null +++ b/.zsh/fzf-tab/.gitattributes @@ -0,0 +1,2 @@ +modules/* linguist-vendored +modules/Src/aloxaf/*.c -linguist-vendored diff --git a/.zsh/fzf-tab/.github/ISSUE_TEMPLATE/bug_report.md b/.zsh/fzf-tab/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..d5d93fa --- /dev/null +++ b/.zsh/fzf-tab/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG]" +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Type '...' +2. Press Tab +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Environment (please complete the following information):** + - OS: [e.g. Arch Linux] + - zsh version [e.g. 5.8.1] + - fzf-tab version [e.g. https://github.com/Aloxaf/fzf-tab/commit/56adfa43b6cab964249c740302c8b57d129201f0] + +**zshrc** +If applicable, add a minimal zshrc to help us analyze. diff --git a/.zsh/fzf-tab/.github/ISSUE_TEMPLATE/feature_request.md b/.zsh/fzf-tab/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..524940e --- /dev/null +++ b/.zsh/fzf-tab/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[FR]" +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.zsh/fzf-tab/.github/workflows/test.yaml b/.zsh/fzf-tab/.github/workflows/test.yaml new file mode 100644 index 0000000..1faf470 --- /dev/null +++ b/.zsh/fzf-tab/.github/workflows/test.yaml @@ -0,0 +1,24 @@ +name: Test + +on: + pull_request: + push: + branches: + - master + +jobs: + run-test: + name: Run test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v1 + with: + fetch-depth: 1 + + - name: Initialize + run: sudo apt-get install zsh + + - name: Run test + run: cd test && zsh runtests.zsh fzftab.ztst + diff --git a/.zsh/fzf-tab/.gitignore b/.zsh/fzf-tab/.gitignore new file mode 100644 index 0000000..416cfaa --- /dev/null +++ b/.zsh/fzf-tab/.gitignore @@ -0,0 +1 @@ +*.zwc diff --git a/.zsh/fzf-tab/LICENSE b/.zsh/fzf-tab/LICENSE new file mode 100644 index 0000000..4d4738c --- /dev/null +++ b/.zsh/fzf-tab/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019-2020 Aloxaf + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.zsh/fzf-tab/fzf-tab.plugin.zsh b/.zsh/fzf-tab/fzf-tab.plugin.zsh new file mode 120000 index 0000000..1369e2f --- /dev/null +++ b/.zsh/fzf-tab/fzf-tab.plugin.zsh @@ -0,0 +1 @@ +fzf-tab.zsh \ No newline at end of file diff --git a/.zsh/fzf-tab/fzf-tab.zsh b/.zsh/fzf-tab/fzf-tab.zsh new file mode 100644 index 0000000..4abb89f --- /dev/null +++ b/.zsh/fzf-tab/fzf-tab.zsh @@ -0,0 +1,588 @@ +# 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' diff --git a/.zsh/fzf-tab/lib/zsh-ls-colors/LICENSE b/.zsh/fzf-tab/lib/zsh-ls-colors/LICENSE new file mode 100644 index 0000000..940b4c2 --- /dev/null +++ b/.zsh/fzf-tab/lib/zsh-ls-colors/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Gamma + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.zsh/fzf-tab/lib/zsh-ls-colors/README.md b/.zsh/fzf-tab/lib/zsh-ls-colors/README.md new file mode 100644 index 0000000..7736ce6 --- /dev/null +++ b/.zsh/fzf-tab/lib/zsh-ls-colors/README.md @@ -0,0 +1,114 @@ +# zsh-ls-colors + +![Demo screenshot](https://raw.githubusercontent.com/xPMo/zsh-ls-colors/image/demo.png) + +A zsh library to use `LS_COLORS` in scripts or other plugins. + +For a simple demo, see the `demo` script in this repo. + +For more advanced usage, +instructions are located at top of the source files for `from-mode` and `from-name`. +If a use case isn't adequately covered, +please open an issue! + +## Using zsh-ls-colors in a plugin + +You can use this as a submodule or a subtree. + +### submodule: + +```sh +# Add (only once) +git submodule add git://github.com/xPMo/zsh-ls-colors.git ls-colors +git commit -m 'Add ls-colors as submodule' + +# Update +cd ls-colors +git fetch +git checkout origin/master +cd .. +git commit ls-colors -m 'Update ls-colors to latest' +``` + +### Subtree: + +```sh +# Initial add +git subtree add --prefix=ls-colors/ --squash -m 'Add ls-colors as a subtree' \ + git://github.com/xPMo/zsh-ls-colors.git master + +# Update +git subtree pull --prefix=ls-colors/ --squash -m 'Update ls-colors to latest' \ + git://github.com/xPMo/zsh-ls-colors.git master + + +# Or, after adding a remote: +git remote add ls-colors git://github.com/xPMo/zsh-ls-colors.git + +# Initial add +git subtree add --prefix=ls-colors/ --squash -m 'Add ls-colors as a subtree' ls-colors master + +# Update +git subtree pull --prefix=ls-colors/ --squash -m 'Update ls-colors to latest' ls-colors master +``` + +### Function namespacing + +Since functions are a public namespace, +this plugin allows you to customize the preifix for your plugin: + +```zsh +# load functions as my-lscolors::{init,match-by,from-name,from-mode} +source ${0:h}/ls-colors/ls-colors.zsh my-lscolors +``` + +### Parameter namespacing + +While indirect parameter expansion exists with `${(P)var}`, +it doesn't play nicely with array parameters. + +There are multiple strategies to prevent unnecessary re-parsing: + +```zsh +# Call once when loading. +# Pollutes global namespace but prevents re-parsing +ls-color::init +``` + +```zsh +# Don't call init at all and only use ::match-by. +# Doesn't pollute global namespace but reparses LS_COLORS on every call +ls-color::match-by $file lstat +``` + +```zsh +# Initialize within a scope with local parameters. +# Best for not polluting global namespace when multiple filenames need to be parsed. +(){ + local -A namecolors modecolors + ls-color::init + + for arg; do + ... + done +} +``` + +```zsh +# Serialize: +typeset -g LS_COLORS_CACHE_FILE=$(mktemp) +(){ + local -A namecolors modecolors + ls-color::init + typeset -p modecolors namecolors >| $LS_COLORS_CACHE_FILE + zcompile $LS_COLORS_CACHE_FILE +} + +my-function(){ + local -A namecolors modecolors + source $LS_COLORS_CACHE_FILE + + ... +} +``` + diff --git a/.zsh/fzf-tab/lib/zsh-ls-colors/demo b/.zsh/fzf-tab/lib/zsh-ls-colors/demo new file mode 100755 index 0000000..a5e468d --- /dev/null +++ b/.zsh/fzf-tab/lib/zsh-ls-colors/demo @@ -0,0 +1,65 @@ +#!/usr/bin/env zsh +# set $0 (ref: zdharma.org/Zsh-100-Commits-Club/Zsh-Plugin-Standard.html#zero-handling) +0="${${ZERO:-${0:#$ZSH_ARGZERO}}:-${(%):-%N}}" +0="${${(M)0:#/*}:-$PWD/$0}" + +# load library functions +source ls-colors.zsh '' + +# to name the functions with a different namespace +# call source with a different argument +#source my-plugin::ls + +# init (sets modecolors and namecolors) +# You have options. Either you can pollute global namespace: +ls-color::init +# Or you can have ::match-by re-parse colors on every call +: # (do nothing) +# Or if you have multiple calls, you can parse colors once for a scope: +(){ + local -A modecolors namecolors + ls-color::init + + for arg; do + ls-color::match-by $arg lstat + : do something else + done +} + + +# colors can also be added for other globs after init as well: +namecolors[*.md]='01' # bold markdown files + +# EXTENDED_GLOB is enabled when matching, so things like this are possible: +namecolors[(#i)(*/|)license(|.*)]='04' # underline LICENSE, or license.txt, or similar + +local file reply +# color each file in the argument list +for file; do + ls-color::match-by $file all + # point to symlink resolution if it exists + print '\e['$reply[1]'m'$file'\e[0m'${reply[2]:+' → \e['$reply[3]'m'$reply[2]'\e[0m'} +done + +# ======================= +# Alternate manual method: +for file; do + ls-color::match-by $file lstat follow + if [[ $reply[2] ]]; then + # This is a symlink + symlink_color=$reply[1] + # If broken, use link color for destination + resolved_color=$reply[1] + resolved=$reply[2] + if [[ -e $file ]]; then + # Not broken, update destination color + ls-color::match-by $file stat + resolved_color=$reply[1] + fi + print '\e['$symlink_color'm'$file'\e[0m → \e['$resolved_color'm'$resolved'\e[0m' + else + # This is not a symlink + print '\e['$reply[1]'m'$file'\e[0m' + fi +done + diff --git a/.zsh/fzf-tab/lib/zsh-ls-colors/ls-colors.zsh b/.zsh/fzf-tab/lib/zsh-ls-colors/ls-colors.zsh new file mode 100644 index 0000000..276a7bb --- /dev/null +++ b/.zsh/fzf-tab/lib/zsh-ls-colors/ls-colors.zsh @@ -0,0 +1,186 @@ +#!/usr/bin/env zsh + +# set the prefix for all functions +local pfx=${1:-'ls-color'} + +# {{{ From mode +# Usage: +# $1: filename +# $2: The value of struct stat st_mode +# If empty, modecolors lookup will be skipped +# $3: (If symlink) The value of struct stat st_mode +# for the target of $1's symlink. If unset, +# interpret as a broken link. +# Sets REPLY to the console code +${pfx}::from-mode () { + + emulate -L zsh + setopt cbases octalzeroes extendedglob + + [[ -z $2 ]] && return 1 + + local -i reg=0 + local -a codes + + local -i st_mode=$(($2)) + # See man 7 inode for more info + # file type + case $(( st_mode & 0170000 )) in + $(( 0140000 )) ) codes=( $modecolors[so] ) ;; + $(( 0120000 )) ) # symlink, special handling + if ! (($+3)); then + REPLY=$modecolors[or] + elif [[ $modecolors[ln] = target ]]; then + "$0" "$1" "${@:3}" + else + REPLY=$modecolors[ln] + fi + return + ;; + $(( 0100000 )) ) codes=( ); reg=1 ;; # regular file + $(( 0060000 )) ) codes=( $modecolors[bd] ) ;; + $(( 0040000 )) ) codes=( $modecolors[di] ) ;; + $(( 0020000 )) ) codes=( $modecolors[cd] ) ;; + $(( 0010000 )) ) codes=( $modecolors[pi] ) ;; + esac + + # setuid/setgid/sticky/other-writable + (( st_mode & 04000 )) && codes+=( $modecolors[su] ) + (( st_mode & 02000 )) && codes+=( $modecolors[sg] ) + (( ! reg )) && case $(( st_mode & 01002 )) in + # sticky + $(( 01000 )) ) codes+=( $modecolors[st] ) ;; + # other-writable + $(( 00002 )) ) codes+=( $modecolors[ow] ) ;; + # other-writable and sticky + $(( 01002 )) ) codes+=( $modecolors[tw] ) ;; + esac + + # executable + if (( ! $#codes )); then + (( st_mode & 0111 )) && codes+=( $modecolors[ex] ) + fi + + # return nonzero if no matching code + [[ ${REPLY::=${(j:;:)codes}} ]] +} # }}} +# {{{ From name +# Usage: +# $1: filename +# +# Sets REPLY to the console code +${pfx}::from-name () { + + emulate -L zsh + setopt extendedglob + + # Return non-zero if no keys match + [[ ${REPLY::=$namecolors[(k)$1]} ]] +} # }}} +# {{{ Init +# WARNING: initializes namecolors and modecolors in global scope +${pfx}::init () { + emulate -L zsh + + # Use $1 if provided, otherwise use LS_COLORS + # Use LSCOLORS on BSD + local LS_COLORS=${1:-${LS_COLORS:-$LSCOLORS}} + + # read in LS_COLORS + typeset -gA namecolors=(${(@s:=:)${(@s.:.)LS_COLORS}:#[[:alpha:]][[:alpha:]]=*}) + typeset -gA modecolors=(${(@Ms:=:)${(@s.:.)LS_COLORS}:#[[:alpha:]][[:alpha:]]=*}) +} +# }}} +# {{{ Match by +# Usage: +# $1: filename +# Optional (must be $2): g[lobal]: Use existing stat | lstat in parent scope +# ${@:2}: Append to reply: +# - l[stat] : Look up using lstat (don't follow symlink), if empty match name +# - s[tat] : Look up using stat (do follow symlink), if empty match name +# - n[ame] : Only match name +# - f[ollow]: Get resolution path of symlink +# - L[stat] : Same as above but don't match name +# - S[tat] : Same as above but don't match name +# - a[ll] : If a broken symlink: lstat follow lstat +# : If a symlink : lstat follow stat +# : Otherwise : lstat +# - A[ll] : If a broken symlink: Lstat follow Lstat +# : If a symlink : Lstat follow Stat +# : Otherwise : Lstat +# +# or returns non-zero +${pfx}::match-by () { + emulate -L zsh + setopt extendedglob cbases octalzeroes + + local arg REPLY name=$1 pfx=${0%::match-by} + shift + + # init in local scope if not using global params + if ! [[ -v namecolors && -v modecolors ]]; then + local -A namecolors modecolors + ${pfx}::init + fi + + if [[ ${1:l} = (g|global) ]]; then + shift + else + local -a stat lstat + declare -ga reply=() + fi + + zmodload -F zsh/stat b:zstat + for arg; do + case ${arg[1]:l} in + n|name) + ${pfx}::from-name $name + reply+=("$REPLY") + ;; + l|lstat) + (($#lstat)) || zstat -A lstat -L $name || return 1 + if ((lstat[3] & 0170000 )); then + # follow symlink + (($#stat)) || zstat -A stat $name 2>/dev/null + fi + ${pfx}::from-mode "$name" "$lstat[3]" $stat[3] + if [[ $REPLY || ${2[1]} = L ]]; then + reply+=("$REPLY") + else # fall back to name + "$0" "$name" g n + fi + ;; + s|stat) + (($#stat)) || zstat -A stat $name || return 1 + ${pfx}::from-mode $name $stat[3] + reply+=("$REPLY") + if [[ $REPLY || ${arg[1]} = S ]]; then + reply+=("$REPLY") + else # fall back to name + "$0" "$name" g n + fi + ;; + f|follow) + (($#lstat)) || zstat -A lstat -L $name || return 1 + reply+=("$lstat[14]") + ;; + a|all) + # Match case + "$0" "$name" g ${${${arg[1]%a}:+L}:-l} + # won't append if empty + reply+=($lstat[14]) + # $stat[14] will be empty if not a symlink + if [[ $lstat[14] ]]; then + if [[ -e $name ]]; then + "$0" "$name" g ${${${arg[1]%a}:+S}:-s} + else + reply+=($reply[-2]) + fi + fi + ;; + *) return 2 ;; + esac + done +} +# }}} +# vim: set foldmethod=marker: diff --git a/.zshrc b/.zshrc index e7e5aad..4461ff0 100644 --- a/.zshrc +++ b/.zshrc @@ -2815,7 +2815,7 @@ PATH=$PATH:$HOME/.local/bin alias t="topydo" alias sysu='systemctl --user' - +source ~/.zsh/fzf-tab/fzf-tab.plugin.zsh source ~/.zsh/zsh-autosuggestions.zsh