fuzzy tabbing

This commit is contained in:
xfnw 2020-07-03 22:43:48 -05:00
parent 1067282ae4
commit 662ba4a2d1
13 changed files with 1075 additions and 1 deletions

2
.zsh/fzf-tab/.gitattributes vendored Normal file
View file

@ -0,0 +1,2 @@
modules/* linguist-vendored
modules/Src/aloxaf/*.c -linguist-vendored

View file

@ -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 <kbd>Tab</kbd>
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.

View file

@ -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.

View file

@ -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

1
.zsh/fzf-tab/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.zwc

21
.zsh/fzf-tab/LICENSE Normal file
View file

@ -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.

View file

@ -0,0 +1 @@
fzf-tab.zsh

588
.zsh/fzf-tab/fzf-tab.zsh Normal file
View file

@ -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'

View file

@ -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.

View file

@ -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
...
}
```

View file

@ -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

View file

@ -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:

2
.zshrc
View file

@ -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