mirror of
https://github.com/Aloxaf/fzf-tab.git
synced 2026-09-10 07:26:16 -04:00
feat: New style option to fzf-tab group headers: active-group-style (#553)
* Feat: support different styling for the group labels
* Fix: improved portability of code to macOS
* Refactor: use robust template file for ftb_preview_init
* refactor: retrieve active-group-style as array, simplify normalization
Keeps the type prefix consistent with the project history. It covers both changes: switching from -s to -a (which also changes the user-facing syntax to space-separated, consistent with all other multi-value zstyles), and replacing the 60-line anonymous function with a clean four-step variable breakdown.
---
```zsh
test_normalize() {
local -a active_group_style=("$@")
local -a _active_style_lower=(${(L)active_group_style})
local -a _active_style_valid=(${(M)_active_style_lower:#(bold|underline|none)})
local -a _active_style_sorted=(${(ou)_active_style_valid})
local normalized=${${(j:,:)_active_style_sorted}:-none}
echo "input=(${(j: :)active_group_style}) → \"$normalized\""
}
test_normalize bold
test_normalize underline
test_normalize bold underline
test_normalize underline bold # order should not matter
test_normalize BOLD UNDERLINE # uppercase
test_normalize bold bold # duplicates
test_normalize none
test_normalize italic # unknown → none fallback
test_normalize bold italic # mixed valid+invalid
test_normalize # empty → none fallback
```
input=(bold) → "bold"
input=(underline) → "underline"
input=(bold underline) → "bold,underline"
input=(underline bold) → "bold,underline"
input=(BOLD UNDERLINE) → "bold,underline"
input=(bold bold) → "bold"
input=(none) → "none"
input=(italic) → "none"
input=(bold italic) → "bold"
input=() → "none"
* fix: avoid code duplication in `initial_command` array and `reload_command` with 0 appended as the offset
Note: (z) splits the string using shell word-splitting rules, turning the string back into an array of tokens, then 0 is appended as the offset for the initial render.
Co-authored-by: Aloxaf <aloxafx@gmail.com>
* fix: restoring the (z) flag on $fzf_command. The original code used ${(z)fzf_command} which splits the command string into words. The PR changed it to just $fzf_command.
Co-authored-by: Aloxaf <aloxafx@gmail.com>
* refactor: changed `"${initial_command[@]}"` (POSIX-style expansion) to `$initial_command` (zsh-idiomatic expansion of an array)
Co-authored-by: Aloxaf <aloxafx@gmail.com>
* refactor: avoided anonymous function
Co-authored-by: Aloxaf <aloxafx@gmail.com>
* refactor: replaced echo for `print -r --` and removed trailing spaces
Co-authored-by: Aloxaf <aloxafx@gmail.com>
---------
Co-authored-by: Aloxaf <aloxafx@gmail.com>
This commit is contained in:
parent
747c15de85
commit
0fbd5753f9
53
lib/-ftb-fzf
53
lib/-ftb-fzf
|
|
@ -7,29 +7,24 @@ zmathfunc
|
|||
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 description
|
||||
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
|
||||
$(typeset -p words)
|
||||
"
|
||||
local ftb_preview_init
|
||||
ftb_preview_init=$(<"$FZF_TAB_HOME/lib/-ftb-preview.tpl")
|
||||
|
||||
# Inject resolved paths (these MUST refer to the correct pid used to create the files)
|
||||
local compcap_file="$tmp_dir/compcap.$$"
|
||||
local groups_file="$tmp_dir/groups.$$"
|
||||
|
||||
ftb_preview_init=${ftb_preview_init//__FTB_COMPCAP__/${(q)compcap_file}}
|
||||
ftb_preview_init=${ftb_preview_init//__FTB_GROUPS__/${(q)groups_file}}
|
||||
|
||||
# Inject the dump of 'words'
|
||||
ftb_preview_init=${ftb_preview_init//__FTB_WORDS_DUMP__/$(typeset -p words)}
|
||||
|
||||
# command substitution strips all trailing newlines from the output
|
||||
ftb_preview_init+=$'\n'
|
||||
|
||||
local default_binds=tab:down,btab:up,change:top,ctrl-space:toggle,bspace:backward-delete-char/eof,ctrl-h:backward-delete-char/eof
|
||||
local fzf_command fzf_flags fzf_preview debug_command tmp switch_group fzf_pad fzf_min_height binds
|
||||
local fzf_command fzf_flags fzf_preview debug_command tmp switch_group active_group_style fzf_pad fzf_min_height binds
|
||||
local ret=0
|
||||
|
||||
-ftb-zstyle -s fzf-command fzf_command || fzf_command=fzf
|
||||
|
|
@ -41,12 +36,18 @@ local ret=0
|
|||
-ftb-zstyle -s fzf-pad fzf_pad || fzf_pad=2
|
||||
-ftb-zstyle -s fzf-min-height fzf_min_height || fzf_min_height=0
|
||||
-ftb-zstyle -b use-fzf-default-opts use_fzf_default_opts || use_fzf_default_opts="no"
|
||||
-ftb-zstyle -a active-group-style active_group_style || active_group_style=(bold)
|
||||
|
||||
-ftb-zstyle -a debug-command debug_command && {
|
||||
${(eX)debug_command} $fzf_flags
|
||||
return
|
||||
}
|
||||
|
||||
local -a _active_style_lower=(${(L)active_group_style})
|
||||
local -a _active_style_valid=(${(M)_active_style_lower:#(bold|underline|none)})
|
||||
local -a _active_style_sorted=(${(ou)_active_style_valid})
|
||||
local normalized_active_group_style=${${(j:,:)_active_style_sorted}:-none}
|
||||
|
||||
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.$$
|
||||
|
|
@ -55,7 +56,7 @@ 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"
|
||||
local reload_command="$commands[zsh] -f $FZF_TAB_HOME/lib/ftb-switch-group $$ $header_lines $normalized_active_group_style $tmp_dir"
|
||||
|
||||
# detect if we will use tmux popup
|
||||
local use_tmux_popup=0
|
||||
|
|
@ -87,6 +88,8 @@ if [[ "$use_fzf_default_opts" == "yes" ]]; then
|
|||
fzf_default_opts=$FZF_DEFAULT_OPTS
|
||||
fi
|
||||
|
||||
local -a initial_command=(${(z)reload_command} 0)
|
||||
|
||||
FZF_DEFAULT_OPTS=$fzf_default_opts SHELL=$ZSH_NAME ${(z)fzf_command} \
|
||||
--ansi \
|
||||
--bind=$binds \
|
||||
|
|
@ -102,8 +105,8 @@ FZF_DEFAULT_OPTS=$fzf_default_opts SHELL=$ZSH_NAME ${(z)fzf_command} \
|
|||
--print-query \
|
||||
--query=$_ftb_query \
|
||||
--tiebreak=begin \
|
||||
${fzf_preview:+--preview=${ftb_preview_init/{f}/'{f}'}$fzf_preview} \
|
||||
$fzf_flags < $tmp_dir/completions.$$ || ret=$?
|
||||
${fzf_preview:+--preview=${ftb_preview_init}$fzf_preview} \
|
||||
$fzf_flags < <($initial_command) || ret=$?
|
||||
|
||||
if (( ! use_tmux_popup )); then
|
||||
echoti civis >/dev/tty 2>/dev/null
|
||||
|
|
|
|||
21
lib/-ftb-preview.tpl
Executable file
21
lib/-ftb-preview.tpl
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
zmodload zsh/mapfile
|
||||
local -a _ftb_compcap=("${(@f)mapfile[__FTB_COMPCAP__]}")
|
||||
local -a _ftb_groups=("${(@f)mapfile[__FTB_GROUPS__]}")
|
||||
local bs=$'\2'
|
||||
|
||||
# get description
|
||||
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
|
||||
__FTB_WORDS_DUMP__
|
||||
|
||||
|
|
@ -4,35 +4,86 @@ emulate -L zsh -o extended_glob
|
|||
zmodload zsh/mapfile
|
||||
|
||||
# receive arguments
|
||||
local pid=$1 header_lines=$2 tmp_dir=$3 offset=$@[-1]
|
||||
local pid=$1 header_lines=$2 active_group_style=$3 tmp_dir=$4 offset=$5
|
||||
|
||||
# read completion list
|
||||
local -a list=(${(f)mapfile[$tmp_dir/completions.$pid]})
|
||||
|
||||
# get total group count
|
||||
local grep_cmd=''
|
||||
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})
|
||||
local v
|
||||
# Prefer GNU grep: grep first (Linux), then ggrep (macOS/Homebrew)
|
||||
# Use $commands[...] so we get the resolved executable path (not an alias/function).
|
||||
if (( ${+commands[grep]} )); then
|
||||
v=$("$commands[grep]" --version 2>/dev/null)
|
||||
[[ $v == *'(GNU grep)'* ]] && grep_cmd=$commands[grep]
|
||||
fi
|
||||
|
||||
if [[ -z $grep_cmd && ${+commands[ggrep]} -eq 1 ]]; then
|
||||
v=$("$commands[ggrep]" --version 2>/dev/null)
|
||||
[[ $v == *'(GNU grep)'* ]] && grep_cmd=$commands[ggrep]
|
||||
fi
|
||||
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 ))
|
||||
# --- group marker detection (fzf-tab groups) ---------------------------------
|
||||
# When groups exist, fzf-tab prefixes each entry with a group SGR like $'\e[94m'.
|
||||
# When no groups exist (often file lists), entries may start with $'\e[0m'
|
||||
# (reset for LS_COLORS). Treat that as "no groups".
|
||||
|
||||
local -Ua group_sgr_prefixes
|
||||
if [[ -n $grep_cmd ]]; then
|
||||
group_sgr_prefixes=(
|
||||
${(f)"$(
|
||||
print -l -- ${list:$header_lines} | "$grep_cmd" -a -oP $'^\x1b\\[[0-9;]*m'
|
||||
)"}
|
||||
)
|
||||
else
|
||||
group_sgr_prefixes=(${(M)${list:$header_lines}#$'\x1b['[0-9;]#*m})
|
||||
fi
|
||||
(( current > $#total )) && current=1
|
||||
(( current == 0 )) && current=$#total
|
||||
echo $current > $tmp_dir/current-group.$pid
|
||||
|
||||
# if the only marker is reset, there are no real groups
|
||||
if (( $#group_sgr_prefixes == 1 )) && [[ $group_sgr_prefixes[1] == $'\e[0m' ]]; then
|
||||
group_sgr_prefixes=()
|
||||
fi
|
||||
|
||||
# --- current group index (persist across reloads) ----------------------------
|
||||
local current=1
|
||||
if (( $#group_sgr_prefixes > 0 )) && [[ -f $tmp_dir/current-group.$pid ]]; then
|
||||
current=$(( $(<$tmp_dir/current-group.$pid) + offset ))
|
||||
(( current > $#group_sgr_prefixes )) && current=1
|
||||
(( current <= 0 )) && current=$#group_sgr_prefixes
|
||||
fi
|
||||
print -r -- $current > $tmp_dir/current-group.$pid
|
||||
|
||||
# configure style of active header
|
||||
local sgr_on='' sgr_off=''
|
||||
case $active_group_style in
|
||||
(bold) sgr_on=$'\x1b[1m'; sgr_off=$'\x1b[22m' ;;
|
||||
(underline) sgr_on=$'\x1b[4m'; sgr_off=$'\x1b[24m' ;;
|
||||
(bold,underline) sgr_on=$'\x1b[1;4m'; sgr_off=$'\x1b[22;24m' ;;
|
||||
(none) sgr_on=''; sgr_off='' ;;
|
||||
(*) sgr_on=$'\x1b[1m'; sgr_off=$'\x1b[22m' ;; # fallback
|
||||
esac
|
||||
|
||||
# The ANSI SGR code that marks every line in the currently selected group.
|
||||
local current_prefix=${group_sgr_prefixes[current]}
|
||||
|
||||
# print headers
|
||||
if (( header_lines != 0 )); then
|
||||
print -l ${list[1,header_lines]/${total[current]}/$'\x1b[1m'}
|
||||
local -i i
|
||||
local line
|
||||
for i in {1..$header_lines}; do
|
||||
line=$list[i]
|
||||
if [[ $line == *$current_prefix* ]]; then
|
||||
# Apply style only to the group label (up to the first ']'), then disable it
|
||||
line=${line/$current_prefix(#b)([^]]##)\]/$current_prefix$sgr_on${match[1]}]$sgr_off}
|
||||
fi
|
||||
print -r $line
|
||||
done
|
||||
fi
|
||||
|
||||
# print current group
|
||||
if (( $#list > 10000 )); then
|
||||
print -l ${list:$header_lines} | grep -a -F "${total[current]}"
|
||||
if [[ -n $grep_cmd ]]; then
|
||||
print -rl -- ${list:$header_lines} | "$grep_cmd" -a -F -- "${group_sgr_prefixes[current]}"
|
||||
else
|
||||
print -l ${(M)${list:$header_lines}:#${total[current]}*}
|
||||
print -rl -- ${(M)${list:$header_lines}:#${group_sgr_prefixes[current]}*}
|
||||
fi
|
||||
|
|
|
|||
Loading…
Reference in a new issue