config/zsh/functions/prompt_sjs_setup
Sami Samhuri cb476d4979 Highlight change ID prefix and refine prompt colors
Bold magenta on the unique prefix, gray on the rest, matching jj's own
log output. Gray parens instead of yellow. Empty marker moved to the
diff-stat slot for a uniform layout.
2026-05-29 12:13:31 -07:00

395 lines
10 KiB
Text

# sjs's prompt theme
autoload -U zgitinit
zgitinit
prompt_sjs_help () {
cat <<'EOF'
prompt sjs
EOF
}
revstring() {
git describe --always $1 2>/dev/null ||
git rev-parse --short $1 2>/dev/null
}
coloratom() {
local off=$1 atom=$2
if [[ $atom[1] == [[:upper:]] ]]; then
off=$(( $off + 60 ))
fi
echo $(( $off + $colorcode[${(L)atom}] ))
}
colorword() {
local fg=$1 bg=$2 att=$3
local -a s
if [ -n "$fg" ]; then
s+=$(coloratom 30 $fg)
fi
if [ -n "$bg" ]; then
s+=$(coloratom 40 $bg)
fi
if [ -n "$att" ]; then
s+=$attcode[$att]
fi
echo "%{"$'\e['${(j:;:)s}m"%}"
}
prompt_sjs_setup() {
typeset -A colorcode
colorcode[black]=0
colorcode[red]=1
colorcode[green]=2
colorcode[yellow]=3
colorcode[blue]=4
colorcode[magenta]=5
colorcode[cyan]=6
colorcode[white]=7
colorcode[default]=9
colorcode[k]=$colorcode[black]
colorcode[r]=$colorcode[red]
colorcode[g]=$colorcode[green]
colorcode[y]=$colorcode[yellow]
colorcode[b]=$colorcode[blue]
colorcode[m]=$colorcode[magenta]
colorcode[c]=$colorcode[cyan]
colorcode[w]=$colorcode[white]
colorcode[.]=$colorcode[default]
typeset -A attcode
attcode[none]=00
attcode[bold]=01
attcode[faint]=02
attcode[standout]=03
attcode[underline]=04
attcode[blink]=05
attcode[reverse]=07
attcode[conceal]=08
attcode[normal]=22
attcode[no-standout]=23
attcode[no-underline]=24
attcode[no-blink]=25
attcode[no-reverse]=27
attcode[no-conceal]=28
local -A pc
pc[default]='default'
pc[date]='cyan'
pc[time]='Black'
pc[host]='Green'
pc[user]='cyan'
pc[punc]='yellow'
pc[line]='magenta'
pc[hist]='green'
pc[path]='Cyan'
pc[shortpath]='default'
pc[rc]='red'
pc[scm_branch]='Cyan'
pc[scm_commitid]='Yellow'
pc[scm_changeid]='Magenta'
pc[scm_changeid_rest]='Black'
pc[paren]='Black'
pc[scm_status_dirty]='Red'
pc[scm_status_staged]='Green'
pc[#]='Yellow'
for cn in ${(k)pc}; do
pc[${cn}]=$(colorword $pc[$cn])
done
pc[scm_changeid]=$(colorword Magenta . bold)
pc[scm_changeid_rest]=$(colorword Black . normal)
pc[reset]=$(colorword . . 00)
typeset -Ag sjs_prompt_colors
sjs_prompt_colors=(${(kv)pc})
local p_rc
PROMPT=
PROMPT+="\$(prompt_sjs_scm_jj)"
PROMPT+="$pc[time][%t]$pc[reset] "
PROMPT+="%(?..$pc[rc]-%1v- $pc[reset])"
PROMPT+="$pc[path]%(2~.%~.%/)$pc[reset]"
PROMPT+="\$(prompt_sjs_scm_branch)"
PROMPT+=" $pc[#]%#$pc[reset] "
RPROMPT="$pc[user]%n$pc[reset]@$pc[host]%m$pc[reset]"
export PROMPT RPROMPT
precmd_functions+='prompt_sjs_precmd'
}
prompt_sjs_precmd() {
local ex=$?
psvar=()
if [[ $ex -ge 128 ]]; then
sig=$signals[$ex-127]
psvar[1]="sig${(L)sig}"
else
psvar[1]="$ex"
fi
}
prompt_sjs_scm_status() {
zgit_isgit || return
local -A pc
pc=(${(kv)sjs_prompt_colors})
head=$(zgit_head)
gitcommit=$(revstring $head)
local -a commits
if zgit_rebaseinfo; then
orig_commit=$(revstring $zgit_info[rb_head])
orig_name=$(git name-rev --name-only $zgit_info[rb_head])
orig="$pc[scm_branch]$orig_name$pc[punc]($pc[scm_commitid]$orig_commit$pc[punc])"
onto_commit=$(revstring $zgit_info[rb_onto])
onto_name=$(git name-rev --name-only $zgit_info[rb_onto])
onto="$pc[scm_branch]$onto_name$pc[punc]($pc[scm_commitid]$onto_commit$pc[punc])"
if [ -n "$zgit_info[rb_upstream]" ] && [ $zgit_info[rb_upstream] != $zgit_info[rb_onto] ]; then
upstream_commit=$(revstring $zgit_info[rb_upstream])
upstream_name=$(git name-rev --name-only $zgit_info[rb_upstream])
upstream="$pc[scm_branch]$upstream_name$pc[punc]($pc[scm_commitid]$upstream_commit$pc[punc])"
commits+="rebasing $upstream$pc[reset]..$orig$pc[reset] onto $onto$pc[reset]"
else
commits+="rebasing $onto$pc[reset]..$orig$pc[reset]"
fi
local -a revs
revs=($(git rev-list $zgit_info[rb_onto]..HEAD))
if [ $#revs -gt 0 ]; then
commits+="\n$#revs commits in"
fi
if [ -f $zgit_info[dotest]/message ]; then
mess=$(head -n1 $zgit_info[dotest]/message)
commits+="on $mess"
fi
elif [ -n "$gitcommit" ]; then
local track_merge=$(zgit_tracking_merge)
if [ -n "$track_merge" ]; then
if git rev-parse --verify -q $track_merge >/dev/null; then
local track_remote=$(zgit_tracking_remote)
local tracked=$(revstring $track_merge 2>/dev/null)
local -a revs
revs=($(git rev-list --reverse $track_merge..HEAD))
if [ $#revs -gt 0 ]; then
local base=$(revstring $revs[1]~1)
local base_name=$(git name-rev --name-only $base)
local base_short=$(revstring $base)
local word_commits
if [ $#revs -gt 1 ]; then
word_commits='commits'
else
word_commits='commit'
fi
local conj="since"
if [[ "$base" == "$tracked" ]]; then
conj+=" tracked"
tracked=
fi
commits+="$#revs $word_commits $conj $pc[scm_branch]$base_name$pc[punc]($pc[scm_commitid]$base_short$pc[punc])$pc[reset]"
fi
fi
fi
fi
gitsvn=$(git rev-parse --verify -q --short git-svn)
if [ $? -eq 0 ]; then
gitsvnrev=$(zgit_svnhead $gitsvn)
gitsvn=$(revstring $gitsvn)
if [ -n "$gitsvnrev" ]; then
local svninfo=''
local -a revs
svninfo+="$pc[default]svn$pc[punc]:$pc[scm_branch]r$gitsvnrev"
revs=($(git rev-list git-svn..HEAD))
if [ $#revs -gt 0 ]; then
svninfo+="$pc[punc]@$pc[default]HEAD~$#revs"
svninfo+="$pc[punc]($pc[scm_commitid]$gitsvn$pc[punc])"
fi
commits+=$svninfo
fi
fi
if [ $#commits -gt 0 ]; then
echo -n " ${(j: :)commits}"
fi
}
prompt_sjs_in_jj() {
local d=$PWD
while [[ $d != / && -n $d ]]; do
[[ -d $d/.jj ]] && return 0
d=${d:h}
done
return 1
}
prompt_sjs_scm_jj() {
prompt_sjs_in_jj || return
local -A pc
pc=(${(kv)sjs_prompt_colors})
# sep: outer field separator. rsep: between parent records. fsep: within a parent record.
local sep=$'\x1f' rsep=$'\x1e' fsep=$'\x1d'
local tmpl='
change_id.shortest(8).prefix() ++ "'$sep'" ++
change_id.shortest(8).rest() ++ "'$sep'" ++
bookmarks.join(",") ++ "'$sep'" ++
if(empty, "1", "") ++ "'$sep'" ++
if(conflict, "1", "") ++ "'$sep'" ++
if(divergent, "1", "") ++ "'$sep'" ++
parents.map(|p|
p.bookmarks().join(",") ++ "'$fsep'" ++
p.change_id().shortest(8).prefix() ++ "'$fsep'" ++
p.change_id().shortest(8).rest()
).join("'$rsep'") ++ "'$sep'" ++
parents.map(|p| p.description().first_line()).join(" / ") ++ "'$sep'" ++
description.first_line()
'
local info
info=$(jj log -r @ --no-graph --color=never -T "$tmpl" 2>/dev/null) || return
local change_prefix change_rest bookmarks empty conflict divergent parents_data parent_desc desc
IFS=$sep read -r change_prefix change_rest bookmarks empty conflict divergent parents_data parent_desc desc <<< "$info"
local -a parent_records
parent_records=("${(@ps.$rsep.)parents_data}")
# splice parent_desc into the parens only for the single-parent case
local desc_in_parens=0
if [ -z "$desc" ] && [ -n "$parent_desc" ] && [ ${#parent_records} -eq 1 ] && [ -n "$parent_records[1]" ]; then
desc_in_parens=1
fi
local -a parent_displays
local rec
for rec in "${parent_records[@]}"; do
[ -z "$rec" ] && continue
local -a pf
pf=("${(@ps.$fsep.)rec}")
local p_bookmarks=$pf[1]
local p_prefix=$pf[2]
local p_rest=$pf[3]
local parent_str="$p_bookmarks$pc[paren]($pc[scm_changeid]$p_prefix$pc[scm_changeid_rest]$p_rest"
if [ $desc_in_parens -eq 1 ]; then
local truncated=$parent_desc
[ ${#parent_desc} -gt 60 ] && truncated="${parent_desc[1,60]}…"
truncated=${truncated//\%/%%}
parent_str+=" $pc[default]\"$truncated\"$pc[scm_branch]"
fi
parent_str+="$pc[paren])$pc[scm_branch]"
parent_displays+=$parent_str
done
local parent_display="${(j: / :)parent_displays}"
local desc_suffix=
if [ -n "$desc" ]; then
local truncated=$desc
[ ${#desc} -gt 80 ] && truncated="${desc[1,80]}…"
truncated=${truncated//\%/%%}
desc_suffix=" $pc[default]\"$truncated\"$pc[reset]"
elif [ -n "$parent_desc" ] && [ $desc_in_parens -eq 0 ]; then
local truncated=$parent_desc
[ ${#parent_desc} -gt 60 ] && truncated="${parent_desc[1,60]}…"
truncated=${truncated//\%/%%}
desc_suffix=" $pc[default]← \"$truncated\"$pc[reset]"
fi
local out=
local -a prefix_flags
[ -n "$conflict" ] && prefix_flags+='!'
[ -n "$divergent" ] && prefix_flags+=$'≠'
if [ $#prefix_flags -gt 0 ]; then
out+="$pc[scm_status_dirty]${(j::)prefix_flags}$pc[reset] "
fi
out+="$pc[scm_changeid]$change_prefix$pc[scm_changeid_rest]$change_rest$pc[reset]"
if [ -n "$bookmarks" ]; then
out+="$pc[paren]($pc[scm_branch]$bookmarks$pc[paren])$pc[reset]"
fi
if [ -n "$empty" ]; then
out+=" $pc[scm_status_dirty]∅$pc[reset]"
else
local summary
summary=$(jj diff --stat -r @ --ignore-working-copy 2>/dev/null | tail -n1)
local ins=0 del=0
[[ $summary =~ '([0-9]+) insertion' ]] && ins=$match[1]
[[ $summary =~ '([0-9]+) deletion' ]] && del=$match[1]
if [ "$ins" != 0 ] || [ "$del" != 0 ]; then
out+=" $pc[scm_status_staged]+$ins$pc[reset]/$pc[scm_status_dirty]-$del$pc[reset]"
fi
local summary_out
summary_out=$(jj diff --summary -r @ --ignore-working-copy 2>/dev/null)
local added=0 modified=0 deleted=0
local line
while IFS= read -r line; do
case "$line" in
'A '*) (( added++ )) ;;
'M '*|'R '*|'C '*) (( modified++ )) ;;
'D '*) (( deleted++ )) ;;
esac
done <<< "$summary_out"
local -a amd
[ $added -gt 0 ] && amd+="$pc[scm_status_staged]A$added$pc[reset]"
[ $modified -gt 0 ] && amd+="$pc[scm_commitid]M$modified$pc[reset]"
[ $deleted -gt 0 ] && amd+="$pc[scm_status_dirty]D$deleted$pc[reset]"
if [ $#amd -gt 0 ]; then
out+=" ${(j: :)amd}"
fi
fi
if [ -n "$parent_display" ]; then
out+=" $pc[default]on $pc[scm_branch]$parent_display$pc[reset]"
fi
out+="$desc_suffix"
print -rn -- "$out"$'\n'"%{%}"
}
prompt_sjs_scm_branch() {
zgit_isgit || return
prompt_sjs_in_jj && return
local -A pc
pc=(${(kv)sjs_prompt_colors})
echo -n "$pc[punc]:$pc[scm_branch]$(zgit_head)"
if zgit_inworktree; then
if ! zgit_isindexclean; then
echo -n "$pc[scm_status_staged]+"
fi
local -a dirty
if ! zgit_isworktreeclean; then
dirty+='!'
fi
if zgit_hasunmerged; then
dirty+='*'
fi
if zgit_hasuntracked; then
dirty+='?'
fi
if [ $#dirty -gt 0 ]; then
echo -n "$pc[scm_status_dirty]${(j::)dirty}"
fi
fi
echo $pc[reset]
}
prompt_sjs_setup "$@"