From d536783a334a34727477a4f0422a3bde8354bd30 Mon Sep 17 00:00:00 2001 From: "Joey@macstudio" <4296411@qq.com> Date: Thu, 16 Jul 2026 11:24:23 +0800 Subject: [PATCH] Fix group switching with identical colors --- lib/-ftb-generate-complist | 14 +++++++-- lib/ftb-switch-group | 51 +++++++++++++++++------------- test/fzftab.ztst | 64 +++++++++++++++++++++++++++++++++++++- 3 files changed, 105 insertions(+), 24 deletions(-) diff --git a/lib/-ftb-generate-complist b/lib/-ftb-generate-complist index a72a48d..6616b93 100644 --- a/lib/-ftb-generate-complist +++ b/lib/-ftb-generate-complist @@ -82,7 +82,9 @@ fi (( same_word )) && tcandidates[2,-1]=() -# sort and remove sort group or other index +# sort and remove the duplicate group index before the displayed description. +# Keep the leading index so group reloads do not have to use presentation colors +# as group identity. The first field is hidden by fzf's --nth setting. zstyle -T ":completion:$_ftb_curcontext" sort if (( $? != 1 )); then if (( colorful )); then @@ -99,7 +101,15 @@ if (( $? != 1 )); then tcandidates=("${(@o)tcandidates}") fi fi -typeset -gUa _ftb_complist=("${(@)tcandidates//[0-9]#$bs}") +local candidate group_id +local -a indexed_candidates +for candidate in "${(@)tcandidates}"; do + group_id=${candidate%%$bs*} + candidate=${candidate//[0-9]#$bs} + [[ $group_id == <-> ]] && candidate=$group_id$bs$candidate + indexed_candidates+=$candidate +done +typeset -gUa _ftb_complist=("${(@)indexed_candidates}") # hide needless group if (( $#_ftb_groups )); then diff --git a/lib/ftb-switch-group b/lib/ftb-switch-group index 03dd9ae..085d34d 100644 --- a/lib/ftb-switch-group +++ b/lib/ftb-switch-group @@ -8,31 +8,30 @@ 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]}) +local -a groups=(${(f)mapfile[$tmp_dir/groups.$pid]}) +local bs=$'\b' -# --- 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 +# Each grouped entry starts with its numeric group id and a backspace. This +# field is hidden by fzf, but remains stable when group colors are identical. +local -Ua group_ids if (( $#list > 10000 && $+commands[grep] )); then - group_sgr_prefixes=(${(f)"$(print -l -- ${list:$header_lines} | command grep -a -o $'^\x1b\\[[0-9;]*m')"}) + group_ids=(${(f)"$(print -l -- ${list:$header_lines} | command grep -a -o $'^[0-9][0-9]*\b')"}) + group_ids=("${(@)group_ids%$bs}") else - group_sgr_prefixes=(${(M)${list:$header_lines}#$'\x1b['[0-9;]#*m}) -fi - -# 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=() + local item + for item in "${(@)list:$header_lines}"; do + [[ $item == (#b)(<->)$bs* ]] && group_ids+=$match[1] + done fi # calculate and persist current group index # the index starts from 2, because switching from "all" to "group 1" looks weird local current=2 -if (( $#group_sgr_prefixes > 0 )) && [[ -f $tmp_dir/current-group.$pid ]]; then +if (( $#group_ids > 0 )) && [[ -f $tmp_dir/current-group.$pid ]]; then current=$(( $(<$tmp_dir/current-group.$pid) + offset )) fi -(( current > $#group_sgr_prefixes )) && current=1 -(( current == 0 )) && current=$#group_sgr_prefixes +(( current > $#group_ids )) && current=1 +(( current == 0 )) && current=$#group_ids echo $current > $tmp_dir/current-group.$pid # configure style of active header @@ -45,17 +44,27 @@ case $active_group_style in (*) 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] +# The hidden marker on every line in the selected group, and its label. +local current_marker='' current_group='' +if (( $#group_ids )); then + current_marker=$group_ids[current]$bs + current_group=$groups[$group_ids[current]] +fi # print headers if (( header_lines != 0 )); then - # Apply style only to the group label (excluding trailing spaces), then disable it - print -l ${${(S)list[1,header_lines]/(#b)($current_prefix*)($'\x1b[00m')/${match[1]/%(#b)( #)/$sgr_off$match}$match[2]}/$current_prefix/$current_prefix$sgr_on} + # Apply style only to the exact group label, excluding its padding. + local header + for header in "${(@)list[1,header_lines]}"; do + if [[ -n $current_group && $current_group != __hide__* ]]; then + header=${header/(#b)($'\x1b['[0-9;]#m)(${(b)current_group})( #)($'\x1b[00m')/$match[1]$sgr_on$match[2]$sgr_off$match[3]$match[4]} + fi + print -r -- $header + done fi if (( $#list > 10000 && $+commands[grep] )); then - print -l -- ${list:$header_lines} | command grep -a -F -- "${group_sgr_prefixes[current]}" + print -l -- ${list:$header_lines} | command grep -a -- "^$current_marker" else - print -l -- ${(M)${list:$header_lines}:#${group_sgr_prefixes[current]}*} + print -l -- ${(M)${list:$header_lines}:#${current_marker}*} fi diff --git a/test/fzftab.ztst b/test/fzftab.ztst index e0b5b2a..2fa58c9 100644 --- a/test/fzftab.ztst +++ b/test/fzftab.ztst @@ -248,7 +248,69 @@ >C1:{package } >C1:{openpgp-keys-gentoo-release} >C1:{-MERGING-pnpm-bin} + + () { + emulate -L zsh -o extended_glob + local bs=$'\b' + local _ftb_curcontext=identical-colors + local -Ua _ftb_groups=('group one' 'group two') + local -a _ftb_compcap=( + $'alpha\2<\x00>\x00group\x001\x00word\x00alpha' + $'beta\2<\x00>\x00group\x002\x00word\x00beta' + ) + local -a _ftb_group_colors=($'\e[0m') + -ftb-zstyle() { + zstyle $1 ":fzf-tab:$_ftb_curcontext" ${@:2} + } + zstyle ':fzf-tab:identical-colors' group-colors $'\e[0m' $'\e[0m' + . $ZTST_srcdir/../lib/-ftb-generate-complist + zstyle -d ':fzf-tab:identical-colors' group-colors + local -a first_fields=("${(@0)_ftb_complist[1]}") + local -a second_fields=("${(@0)_ftb_complist[2]}") + [[ $first_fields[1] == 1${bs}* && $first_fields[2] == alpha ]] && + [[ $second_fields[1] == 2${bs}* && $second_fields[2] == beta ]] + } +0:completion list retains hidden group identity with identical colors + + () { + emulate -L zsh + local tmp_dir=$ZTST_tmp/identical-group-colors + local pid=$$ + local bs=$'\b' + mkdir -p $tmp_dir + print -rl -- \ + $'\e[0mgroup one \e[00m\e[0mgroup two\e[00m' \ + 1${bs}$'\e[0m·alpha\0alpha\0' \ + 2${bs}$'\e[0m·beta\0beta\0' \ + > $tmp_dir/completions.$pid + print -rl -- 'group one' 'group two' > $tmp_dir/groups.$pid + local -a switched=("${(@f)$( + $commands[zsh] -f $ZTST_srcdir/../lib/ftb-switch-group $pid 1 bold $tmp_dir 0 + )}") + (( $#switched == 2 )) && + [[ $switched[1] == $'\e[0mgroup one \e[00m\e[0m\e[1mgroup two\e[22m\e[00m' ]] && + [[ $switched[2] == 2${bs}$'\e[0m·beta\0beta\0' ]] + } +0:group switching uses hidden identity when colors are identical + + () { + emulate -L zsh + local tmp_dir=$ZTST_tmp/ungrouped-reset-colors + local pid=$$ + mkdir -p $tmp_dir + print -rl -- \ + $'\e[0m·alpha\0alpha\0' \ + $'\e[0m·beta\0beta\0' \ + > $tmp_dir/completions.$pid + : > $tmp_dir/groups.$pid + local -a switched=("${(@f)$( + $commands[zsh] -f $ZTST_srcdir/../lib/ftb-switch-group $pid 0 none $tmp_dir 0 + )}") + (( $#switched == 2 )) && + [[ $switched[1] == $'\e[0m·alpha\0alpha\0' ]] && + [[ $switched[2] == $'\e[0m·beta\0beta\0' ]] + } +0:group switching preserves an ungrouped reset-colored list %clean zmodload -ui zsh/zpty -