mirror of
https://github.com/robbyrussell/oh-my-zsh.git
synced 2026-09-21 18:46:06 +02:00
171 lines
6.3 KiB
Bash
171 lines
6.3 KiB
Bash
# Loaded only by `omz theme browse`. The UI runs in a subshell; stdout is a
|
|
# two-line data handoff, while all terminal I/O goes through a private tty fd.
|
|
function _omz_theme_browser() (
|
|
emulate -L zsh
|
|
setopt extendedglob
|
|
# Displayed names, filter text, and worker output are data, not prompt code.
|
|
unsetopt promptsubst
|
|
zmodload zsh/terminfo && zmodload zsh/zpty && zmodload zsh/system && zmodload zsh/datetime || return 1
|
|
local tty saved filter=$1 key suffix name previous='' preview='' chunk
|
|
local mode=browse action='' pty=omz-browser-$sysparams[pid] record pgid
|
|
local -a names matches lines size
|
|
local -i selected=1 page=1 height=0 width=0 rows=0 cols=0 active=0 dirty=1 i row cancelled=0
|
|
local -F render_after=0
|
|
[[ -n $terminfo[smcup] && -n $terminfo[rmcup] && -n $terminfo[cup] ]] || {
|
|
print -u2 -r -- 'Theme browser requires a terminal with alternate-screen support.'
|
|
return 1
|
|
}
|
|
exec {tty}<>/dev/tty || return 1
|
|
saved=$(command stty -g <&$tty) || return 1
|
|
names=("${(@f)$(_omz_theme_names)}")
|
|
names=("${(@)names:#}")
|
|
trap 'cancelled=1' INT TERM HUP
|
|
trap 'dirty=1' WINCH
|
|
|
|
function _omz_browser_stop {
|
|
if (( active )); then
|
|
[[ $pgid == <2-> ]] && kill -HUP -- -$pgid 2>/dev/null
|
|
# Let the preview supervisor clean up its nested worker group first.
|
|
local -i wait_count=0
|
|
while zpty -t "$pty" && (( wait_count++ < 100 )); do
|
|
zselect -t 1
|
|
done
|
|
zpty -d "$pty" 2>/dev/null
|
|
active=0
|
|
fi
|
|
}
|
|
|
|
function _omz_browser_line {
|
|
local text=${1//$'\t'/ } formatted='' MATCH MBEGIN MEND
|
|
text=${text//\%/%%}
|
|
# Tell prompt truncation that SGR has zero display width. All other terminal
|
|
# controls have already been removed by the preview backend.
|
|
while [[ $text =~ $'\e\\[[0-9;:]*m' ]]; do
|
|
formatted+=${text[1,$(( MBEGIN - 1 ))]}"%{"$MATCH"%}"
|
|
text=${text[$(( MEND + 1 )),-1]}
|
|
done
|
|
formatted+=$text
|
|
echoti cup $row 0 >&$tty
|
|
print -Pn -u $tty -- "%${width}<..<${formatted}%<<%f%k%b"
|
|
(( row++ ))
|
|
}
|
|
|
|
{
|
|
zmodload zsh/zselect || return 1
|
|
command stty -echo -icanon min 0 time 0 <&$tty || return 1
|
|
print -rn -u $tty -- "$terminfo[smcup]$terminfo[civis]"
|
|
while (( ! cancelled )); do
|
|
size=(${=$(command stty size <&$tty)})
|
|
rows=${size[1]:-24} cols=${size[2]:-80}
|
|
if (( height != rows || width != cols - 1 )); then
|
|
height=$rows width=$(( cols - 1 )) dirty=1
|
|
fi
|
|
matches=()
|
|
for name in "${names[@]}"; do
|
|
[[ ${(L)name} == *${(L)filter}* ]] && matches+=("$name")
|
|
done
|
|
(( selected > $#matches )) && selected=$#matches
|
|
(( selected < 1 )) && selected=1
|
|
name=${matches[$selected]}
|
|
if [[ $name != "$previous" ]]; then
|
|
_omz_browser_stop
|
|
previous=$name preview='' dirty=1
|
|
render_after=$(( EPOCHREALTIME + 0.12 ))
|
|
fi
|
|
# Coalesce typing and key repeats rather than starting a worker per key.
|
|
if (( ! active && EPOCHREALTIME >= render_after )); then
|
|
if [[ -n $name ]]; then
|
|
# zpty joins arguments as shell text; quote the literal theme name.
|
|
zpty -b "$pty" _omz_theme_preview "${(q)name}" || return 1
|
|
active=1
|
|
pgid=''
|
|
for record in "${(@f)$(zpty)}"; do
|
|
[[ $record == \(<->\)\ "$pty":* ]] && pgid=${${record#\(}%%\)*}
|
|
done
|
|
fi
|
|
fi
|
|
if (( active )); then
|
|
while zpty -r -t "$pty" chunk; do
|
|
preview+=${chunk//$'\r'/}
|
|
dirty=1
|
|
done
|
|
fi
|
|
page=$(( height > 24 ? 8 : 4 ))
|
|
if (( dirty )); then
|
|
print -rn -u $tty -- "$terminfo[clear]"
|
|
row=0
|
|
if (( width < 39 || height < 16 )); then
|
|
_omz_browser_line 'Resize to at least 40 columns x 16 rows.'
|
|
_omz_browser_line 'Esc / Ctrl-C: cancel'
|
|
else
|
|
_omz_browser_line 'Oh My Zsh theme browser | Esc / Ctrl-C: cancel'
|
|
_omz_browser_line "Filter: ${(V)filter} ($#matches themes)"
|
|
if [[ $mode == actions ]]; then
|
|
_omz_browser_line '[u] Use in session [s] Save + reload [b] Back'
|
|
_omz_browser_line 'Use keeps old theme hooks. Save edits .zshrc + reloads.'
|
|
else
|
|
_omz_browser_line 'Arrows / Ctrl-P,N: move | PgUp,Dn | Enter: actions'
|
|
_omz_browser_line 'Type to filter | Backspace: delete | Ctrl-U: clear'
|
|
fi
|
|
for (( i = ((selected - 1) / page) * page + 1; i <= $#matches && row < page + 4; i++ )); do
|
|
if (( i == selected )); then
|
|
_omz_browser_line "> ${(V)matches[$i]}"
|
|
else
|
|
_omz_browser_line " ${(V)matches[$i]}"
|
|
fi
|
|
done
|
|
_omz_browser_line '--- Static preview (right prompt shown separately) ---'
|
|
lines=("${(@f)preview}")
|
|
if [[ -z $preview ]]; then
|
|
[[ -n $name ]] && lines=('Rendering...') || lines=('No matching themes.')
|
|
fi
|
|
for chunk in "${lines[@]}"; do
|
|
(( row < height - 1 )) || break
|
|
_omz_browser_line "$chunk"
|
|
done
|
|
fi
|
|
dirty=0
|
|
fi
|
|
key=''
|
|
read -r -k 1 -t 0.1 -u $tty key || continue
|
|
if [[ $key == $'\e' ]]; then
|
|
suffix=''
|
|
while read -r -k 1 -t 0.03 -u $tty chunk; do
|
|
suffix+=$chunk
|
|
[[ $chunk == [A-Za-z~] || ${#suffix} -ge 8 ]] && break
|
|
done
|
|
[[ -n $suffix ]] || break
|
|
key=$'\e'$suffix
|
|
fi
|
|
[[ $key == $'\x03' ]] && break
|
|
if [[ $mode == actions ]]; then
|
|
case $key in
|
|
u) action=use; break ;;
|
|
s) action=set; break ;;
|
|
b|$'\r'|$'\n') mode=browse ;;
|
|
esac
|
|
else
|
|
case $key in
|
|
$'\e[A'|$'\eOA'|$'\x10') (( selected-- )) ;;
|
|
$'\e[B'|$'\eOB'|$'\x0e') (( selected++ )) ;;
|
|
$'\e[5~') (( selected -= page )) ;;
|
|
$'\e[6~') (( selected += page )) ;;
|
|
$'\x7f'|$'\b') filter=${filter[1,-2]}; selected=1 ;;
|
|
$'\x15') filter=''; selected=1 ;;
|
|
$'\r'|$'\n') [[ -n $name ]] && mode=actions ;;
|
|
[[:print:]]) filter+=$key; selected=1 ;;
|
|
esac
|
|
fi
|
|
dirty=1
|
|
done
|
|
} always {
|
|
_omz_browser_stop
|
|
print -rn -u $tty -- $'\e[0m'"$terminfo[cnorm]$terminfo[rmcup]"
|
|
command stty "$saved" <&$tty
|
|
exec {tty}>&-
|
|
}
|
|
(( cancelled )) && return 130
|
|
[[ -n $action ]] && print -rl -- "$action" "$name"
|
|
return 0
|
|
)
|