summaryrefslogtreecommitdiff
path: root/dotfiles/system/.zsh/lib
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2025-05-21 22:01:35 -0500
committerCraig Jennings <c@cjennings.net>2025-05-21 22:01:35 -0500
commitb4463015b97912658d630377fafbf630f7588d1e (patch)
treed04b66d992fe2ce88391889c21c5d8dc97acd0ef /dotfiles/system/.zsh/lib
parent548154ea395356868e87980b149dfc0abdc84e17 (diff)
moving arch dotfiles into archsetup
Diffstat (limited to 'dotfiles/system/.zsh/lib')
-rw-r--r--dotfiles/system/.zsh/lib/-ftb-colorize34
-rwxr-xr-xdotfiles/system/.zsh/lib/-ftb-fzf102
-rw-r--r--dotfiles/system/.zsh/lib/-ftb-generate-complist113
-rw-r--r--dotfiles/system/.zsh/lib/-ftb-generate-header35
-rw-r--r--dotfiles/system/.zsh/lib/-ftb-generate-query36
-rw-r--r--dotfiles/system/.zsh/lib/ftb-switch-group38
-rwxr-xr-xdotfiles/system/.zsh/lib/ftb-tmux-popup83
-rw-r--r--dotfiles/system/.zsh/lib/zsh-ls-colors/LICENSE21
-rw-r--r--dotfiles/system/.zsh/lib/zsh-ls-colors/README.md114
-rwxr-xr-xdotfiles/system/.zsh/lib/zsh-ls-colors/demo65
-rw-r--r--dotfiles/system/.zsh/lib/zsh-ls-colors/ls-colors.zsh186
11 files changed, 827 insertions, 0 deletions
diff --git a/dotfiles/system/.zsh/lib/-ftb-colorize b/dotfiles/system/.zsh/lib/-ftb-colorize
new file mode 100644
index 0000000..3b1909b
--- /dev/null
+++ b/dotfiles/system/.zsh/lib/-ftb-colorize
@@ -0,0 +1,34 @@
+#!/hint/zsh
+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 [[ -n $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
diff --git a/dotfiles/system/.zsh/lib/-ftb-fzf b/dotfiles/system/.zsh/lib/-ftb-fzf
new file mode 100755
index 0000000..19adf04
--- /dev/null
+++ b/dotfiles/system/.zsh/lib/-ftb-fzf
@@ -0,0 +1,102 @@
+#!/hint/zsh
+
+local tmp_dir=${TMPPREFIX:-/tmp/zsh}-fzf-tab-$USER
+[[ -d $tmp_dir ]] || command mkdir $tmp_dir
+
+local ftb_preview_init="
+zmodload zsh/mapfile
+local -a _ftb_compcap=(\"\${(@f)mapfile[$tmp_dir/compcap.$$]}\")
+local -a _ftb_groups=(\"\${(@f)mapfile[$tmp_dir/groups.$$]}\")
+local bs=\$'\2'
+# get descriptoin
+export desc=\${\${\"\$(<{f})\"%\$'\0'*}#*\$'\0'}
+# get ctxt for current completion
+local -A ctxt=(\"\${(@0)\${_ftb_compcap[(r)\${(b)desc}\$bs*]#*\$bs}}\")
+# get group
+if (( \$+ctxt[group] )); then
+ export group=\$_ftb_groups[\$ctxt[group]]
+fi
+# get original word
+export word=\${(Q)ctxt[word]}
+# get real path if it is file
+if (( \$+ctxt[realdir] )); then
+ export realpath=\${ctxt[realdir]}\$word
+fi
+"
+local binds=tab:down,btab:up,change:top,ctrl-space:toggle
+local fzf_command fzf_flags fzf_preview debug_command tmp switch_group fzf_pad
+local ret=0
+
+-ftb-zstyle -s fzf-command fzf_command || fzf_command=fzf
+-ftb-zstyle -a fzf-bindings tmp && binds+=,${(j:,:)tmp}
+-ftb-zstyle -a fzf-flags fzf_flags
+-ftb-zstyle -s fzf-preview fzf_preview
+-ftb-zstyle -a switch-group switch_group || switch_group=(F1 F2)
+-ftb-zstyle -s fzf-pad fzf_pad || fzf_pad=2
+
+-ftb-zstyle -a debug-command debug_command && {
+ ${(eX)debug_command} $fzf_flags
+ return
+}
+
+print -rl -- $_ftb_compcap > $tmp_dir/compcap.$$
+print -rl -- $_ftb_groups > $tmp_dir/groups.$$
+print -r -- ${ftb_preview_init/{f}/\$1} > $tmp_dir/ftb_preview_init.$$
+
+binds=${binds//{_FTB_INIT_}/. $tmp_dir/ftb_preview_init.$$ {f} $'\n'}
+
+local -i header_lines=$#_ftb_headers
+local -i lines=$(( $#_ftb_compcap + fzf_pad + header_lines ))
+local reload_command="$commands[zsh] -f $FZF_TAB_HOME/lib/ftb-switch-group $$ $header_lines $tmp_dir"
+
+# detect if we will use tmux popup
+local use_tmux_popup=0
+if [[ $fzf_command == "ftb-tmux-popup" ]]; then
+ use_tmux_popup=1
+fi
+
+if (( ! use_tmux_popup )); then
+ # fzf will cause the current line to refresh, so move the cursor down.
+ echoti cud1 >/dev/tty
+ # reset cursor before call fzf
+ echoti cnorm >/dev/tty 2>/dev/null
+fi
+
+cat > $tmp_dir/completions.$$
+
+local dd='gdd'
+if (( ${+commands[$dd]} == 0 )) ; then
+ dd='dd'
+fi
+if (( ${+commands[$dd]} == 0 )) ; then
+ dd='true' # nop if dd is not installed
+fi
+
+_ftb_query="${_ftb_query}$(command "$dd" bs=1G count=1 status=none iflag=nonblock < /dev/tty 2>/dev/null)" || true
+
+$fzf_command \
+ --ansi \
+ --bind=$binds \
+ --bind="${switch_group[1]}:reload($reload_command -1),${switch_group[2]}:reload($reload_command 1)" \
+ --color=hl:$(( header_lines == 0 ? 188 : 255 )) \
+ --cycle \
+ --delimiter='\x00' \
+ --expect=$continuous_trigger,$print_query,$accept_line \
+ --header-lines=$header_lines \
+ --height=${FZF_TMUX_HEIGHT:=$(( lines > LINES / 3 * 2 ? LINES / 3 * 2 : lines ))} \
+ --layout=reverse \
+ --multi \
+ --nth=2,3 \
+ --print-query \
+ --query=$_ftb_query \
+ --tiebreak=begin \
+ ${fzf_preview:+--preview=$ftb_preview_init$fzf_preview} \
+ $fzf_flags < $tmp_dir/completions.$$ || ret=$?
+
+if (( ! use_tmux_popup )); then
+ echoti civis >/dev/tty 2>/dev/null
+ echoti cuu1 >/dev/tty
+fi
+
+command rm $tmp_dir/*.$$ 2>/dev/null
+return $ret
diff --git a/dotfiles/system/.zsh/lib/-ftb-generate-complist b/dotfiles/system/.zsh/lib/-ftb-generate-complist
new file mode 100644
index 0000000..42dd033
--- /dev/null
+++ b/dotfiles/system/.zsh/lib/-ftb-generate-complist
@@ -0,0 +1,113 @@
+#!/hint/zsh
+
+local dsuf dpre k _v filepath first_word show_group default_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=()
+
+(( $#_ftb_compcap == 0 )) && return
+
+-ftb-zstyle -s show-group show_group || show_group=full
+-ftb-zstyle -s default-color default_color || default_color=$'\x1b[37m'
+-ftb-zstyle -s prefix prefix || {
+ zstyle -m ':completion:*:descriptions' format '*' && prefix='·'
+}
+-ftb-zstyle -a group-colors group_colors || group_colors=($_ftb_group_colors)
+zstyle -a ":completion:$_ftb_curcontext" list-colors list_colors
+
+# init colorize
+if (( $+builtins[fzf-tab-candidates-generate] )); then
+ fzf-tab-candidates-generate -i list_colors
+else
+ local -A namecolors=(${(@s:=:)${(@s.:.)list_colors}:#[[:alpha:]][[:alpha:]]=*})
+ local -A modecolors=(${(@Ms:=:)${(@s.:.)list_colors}:#[[:alpha:]][[:alpha:]]=*})
+ (( $#namecolors == 0 && $#modecolors == 0 )) && list_colors=()
+fi
+
+if (( $#_ftb_groups == 1 )); then
+ -ftb-zstyle -m single-group prefix || prefix=''
+ -ftb-zstyle -m single-group color || group_colors=("$default_color")
+fi
+
+if (( $+builtins[fzf-tab-candidates-generate] )); then
+ fzf-tab-candidates-generate
+else
+ for k _v in "${(@ps:\2:)_ftb_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[realdir] )); then
+ filepath=$v[realdir]${(Q)v[word]}
+ 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
+ -ftb-colorize $filepath
+ colorful=1
+ elif [[ -L $filepath ]]; then
+ dsuf=@
+ fi
+ if [[ $options[list_types] == off ]]; then
+ dsuf=''
+ 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+=$default_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
+fi
+
+(( same_word )) && tcandidates[2,-1]=()
+
+# sort and remove sort group or other index
+zstyle -T ":completion:$_ftb_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 _ftb_complist=("${(@)tcandidates//[0-9]#$bs}")
+
+# hide needless group
+if (( $#_ftb_groups )); then
+ local i to_hide indexs=({1..$#_ftb_groups})
+ case $show_group in
+ brief) to_hide=(${indexs:|duplicate_groups}) ;;
+ none) to_hide=($indexs) ;;
+ esac
+ for i in $to_hide; do
+ # NOTE: _ftb_groups is unique array
+ _ftb_groups[i]="__hide__$i"
+ done
+fi
diff --git a/dotfiles/system/.zsh/lib/-ftb-generate-header b/dotfiles/system/.zsh/lib/-ftb-generate-header
new file mode 100644
index 0000000..a54fee1
--- /dev/null
+++ b/dotfiles/system/.zsh/lib/-ftb-generate-header
@@ -0,0 +1,35 @@
+#!/hint/zsh
+
+typeset -ga _ftb_headers=()
+local i tmp group_colors
+local -i mlen=0 len=0
+
+if (( $#_ftb_groups == 1 )) && { ! -ftb-zstyle -m single-group "header" }; then
+ return
+fi
+
+# calculate the max column width
+for i in $_ftb_groups; do
+ (( $#i > mlen )) && mlen=$#i
+done
+mlen+=1
+
+-ftb-zstyle -a group-colors group_colors || group_colors=($_ftb_group_colors)
+
+for (( i=1; i<=$#_ftb_groups; i++ )); do
+ [[ $_ftb_groups[i] == "__hide__"* ]] && continue
+
+ if (( len + $#_ftb_groups[i] > COLUMNS - 5 )); then
+ _ftb_headers+=$tmp
+ tmp='' && len=0
+ fi
+ if (( len + mlen > COLUMNS - 5 )); then
+ # the last column doesn't need padding
+ _ftb_headers+=$tmp$group_colors[i]$_ftb_groups[i]$'\033[00m'
+ tmp='' && len=0
+ else
+ tmp+=$group_colors[i]${(r:$mlen:)_ftb_groups[i]}$'\033[00m'
+ len+=$mlen
+ fi
+done
+(( $#tmp )) && _ftb_headers+=$tmp
diff --git a/dotfiles/system/.zsh/lib/-ftb-generate-query b/dotfiles/system/.zsh/lib/-ftb-generate-query
new file mode 100644
index 0000000..87ebb75
--- /dev/null
+++ b/dotfiles/system/.zsh/lib/-ftb-generate-query
@@ -0,0 +1,36 @@
+#!/hint/zsh
+
+local key qtype tmp query_string
+typeset -g _ftb_query=
+-ftb-zstyle -a query-string query_string || query_string=(prefix input first)
+for qtype in $query_string; do
+ if [[ $qtype == prefix ]]; then
+ # find the longest common prefix among descriptions
+ local -a keys=(${_ftb_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=${_ftb_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 > $#_ftb_query )) && _ftb_query=$tmp
+ elif [[ -n $tmp ]]; then
+ _ftb_query=$tmp && break
+ fi
+done
diff --git a/dotfiles/system/.zsh/lib/ftb-switch-group b/dotfiles/system/.zsh/lib/ftb-switch-group
new file mode 100644
index 0000000..8d06956
--- /dev/null
+++ b/dotfiles/system/.zsh/lib/ftb-switch-group
@@ -0,0 +1,38 @@
+#!/hint/zsh
+emulate -L zsh -o extended_glob
+
+zmodload zsh/mapfile
+
+# receive arguments
+local pid=$1 header_lines=$2 tmp_dir=$3 offset=$@[-1]
+
+# read completion list
+local -a list=(${(f)mapfile[$tmp_dir/completions.$pid]})
+
+# get total group count
+if (( $#list > 10000 )); then
+ local -Ua total=(${(f)"$(print -l ${list:$header_lines} | grep -a -oP '^\x1b\[[0-9;]*m')"})
+else
+ local -Ua total=(${(M)${list:$header_lines}#$'\x1b['[0-9;]#*m})
+fi
+
+# get current group index, start from 2
+local current=2
+if [[ -f $tmp_dir/current-group.$pid ]]; then
+ current=$(( $(<$tmp_dir/current-group.$pid) + offset ))
+fi
+(( current > $#total )) && current=1
+(( current == 0 )) && current=$#total
+echo $current > $tmp_dir/current-group.$pid
+
+# print headers
+if (( header_lines != 0 )); then
+ print -l ${list[1,header_lines]/${total[current]}/$'\x1b[1m'}
+fi
+
+# print current group
+if (( $#list > 10000 )); then
+ print -l ${list:$header_lines} | grep -a -F "${total[current]}"
+else
+ print -l ${(M)${list:$header_lines}:#${total[current]}*}
+fi
diff --git a/dotfiles/system/.zsh/lib/ftb-tmux-popup b/dotfiles/system/.zsh/lib/ftb-tmux-popup
new file mode 100755
index 0000000..7e74d3c
--- /dev/null
+++ b/dotfiles/system/.zsh/lib/ftb-tmux-popup
@@ -0,0 +1,83 @@
+#!/hint/zsh
+# Show results with tmux popup
+# Example usage:
+# zstyle ':fzf-tab:*' fzf-command ftb-tmux-popup
+# zstyle ':fzf-tab:*' popup-pad 0 0
+# It can also be used as a standalone tool, like:
+# ls | ftb-tmux-popup
+emulate -L zsh -o extended_glob
+
+# import min
+autoload -Uz zmathfunc
+zmathfunc
+
+: ${tmp_dir:=${TMPPREFIX:-/tmp/zsh}-fzf-tab-$USER}
+
+# fallback to fzf if it is not running in tmux
+if (( ! $+TMUX_PANE )); then
+ fzf $@
+ return
+fi
+
+local ret=0
+
+local -a fzf_opts=($@)
+fzf_opts=(${${fzf_opts/--height*}/--layout*})
+
+# get position of cursor and size of window
+local -a tmp=($(command tmux display-message -p "#{pane_top} #{cursor_y} #{pane_left} #{cursor_x} #{window_height} #{window_width}"))
+local cursor_y=$((tmp[1] + tmp[2])) cursor_x=$((tmp[3] + tmp[4])) window_height=$tmp[5] window_width=$tmp[6]
+
+# if not called by fzf-tab
+if (( ! $+IN_FZF_TAB )); then
+ [[ -d $tmp_dir ]] || mkdir -p $tmp_dir
+ cat > $tmp_dir/completions.$$
+fi
+
+local text REPLY comp_lines comp_length length popup_pad
+
+zstyle -a ":fzf-tab:$_ftb_curcontext" popup-pad popup_pad || popup_pad=(0 0)
+
+# get the size of content, note we should remove all ANSI color code
+comp_lines=$(( ${#${(f)mapfile[$tmp_dir/completions.$$]}} + $popup_pad[2] ))
+if (( comp_lines <= 500 )); then
+ comp_length=0
+ for line in ${(f)mapfile[$tmp_dir/completions.$$]}; do
+ length=${(m)#${(S)line//$'\x1b['[0-9]#*m}}
+ (( length >= comp_length )) && comp_length=$length
+ done
+else
+ # FIXME: can't get the correct width of CJK characters.
+ comp_length=$(command sed 's/\x1b\[[0-9;]*m//g;s/\x00//g' $tmp_dir/completions.$$ | command awk 'length > max_length { max_length = length; } END { print max_length }')
+fi
+comp_length=$(( comp_length + $popup_pad[1] ))
+
+local popup_height popup_y popup_width popup_x
+
+# calculate the popup height and y position
+if (( cursor_y * 2 > window_height )); then
+ # show above the cursor
+ popup_height=$(( min(comp_lines + 4, cursor_y) ))
+ popup_y=$cursor_y
+else
+ # show below the cursor
+ popup_height=$(( min(comp_lines + 4, window_height - cursor_y) ))
+ popup_y=$(( cursor_y + popup_height + 1 ))
+ fzf_opts+=(--layout=reverse)
+fi
+
+# calculate the popup width and x position
+popup_width=$(( min(comp_length + 5, window_width) ))
+popup_x=$(( cursor_x + popup_width > window_width ? window_width - popup_width : cursor_x ))
+
+echo -E "$commands[fzf] ${(qq)fzf_opts[@]} < $tmp_dir/completions.$$ > $tmp_dir/result-$$" > $tmp_dir/fzf-$$
+{
+ tmux popup -x $popup_x -y $popup_y \
+ -w $popup_width -h $popup_height \
+ -d $PWD -E ". $tmp_dir/fzf-$$" || ret=$?
+ echo -E "$(<$tmp_dir/result-$$)"
+} always {
+ command rm $tmp_dir/*-$$
+ (( $+IN_FZF_TAB )) || command rm $tmp_dir/completions.$$
+}
+return $ret
diff --git a/dotfiles/system/.zsh/lib/zsh-ls-colors/LICENSE b/dotfiles/system/.zsh/lib/zsh-ls-colors/LICENSE
new file mode 100644
index 0000000..940b4c2
--- /dev/null
+++ b/dotfiles/system/.zsh/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/dotfiles/system/.zsh/lib/zsh-ls-colors/README.md b/dotfiles/system/.zsh/lib/zsh-ls-colors/README.md
new file mode 100644
index 0000000..7736ce6
--- /dev/null
+++ b/dotfiles/system/.zsh/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/dotfiles/system/.zsh/lib/zsh-ls-colors/demo b/dotfiles/system/.zsh/lib/zsh-ls-colors/demo
new file mode 100755
index 0000000..a5e468d
--- /dev/null
+++ b/dotfiles/system/.zsh/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/dotfiles/system/.zsh/lib/zsh-ls-colors/ls-colors.zsh b/dotfiles/system/.zsh/lib/zsh-ls-colors/ls-colors.zsh
new file mode 100644
index 0000000..276a7bb
--- /dev/null
+++ b/dotfiles/system/.zsh/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: