This repository was archived by the owner on Jun 4, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy path10-prompt.rc
More file actions
226 lines (201 loc) · 8.57 KB
/
Copy path10-prompt.rc
File metadata and controls
226 lines (201 loc) · 8.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# shellcheck disable=SC2148 disable=SC1090 disable=SC2034 disable=SC2154 disable=SC1087 shell=bash
### Prompt! ###
autoload -U colors && colors
if [ -z "$RUNNING_IN_VSCODE" ]; then
# Fancy prompt with git info - only when not in VS Code
autoload -Uz vcs_info add-zsh-hook
setopt prompt_subst
zstyle ':vcs_info:*' enable git
## Async git dirty-state rendering (Feb 2026)
## -------------------------------------------
## Previously, vcs_info ran with `check-for-changes true`, which calls `git diff`
## synchronously on every prompt. In repos with large LFS/xet-managed files
## (e.g. HuggingFace model repos with multi-GB binaries), `git diff` must
## SHA-hash the entire working-tree copy of those files, adding 4-5+ seconds
## of blocking delay per prompt.
##
## Fix: disable synchronous change detection so the prompt renders instantly
## with just the branch name (~10ms). Dirty state (staged/unstaged/untracked
## indicators: u, c, N) is computed in a background process. When it finishes,
## the prompt updates in-place via `zle reset-prompt`.
##
## Uses zsh's built-in `zle -F` fd-watcher -- no external plugins required.
## Works on zsh 5.8+.
##
## Indicators: u = unstaged changes, c = staged changes, N = untracked files
## e.g. [main] -> [mainucN] once the async check completes
# Disable the expensive synchronous change detection in vcs_info.
# With this off, %u (unstaged) and %c (staged) are always empty.
# We compute them asynchronously instead (see _async_git_callback below).
zstyle ':vcs_info:*' check-for-changes false
# Simplified format: just branch (or branch|action during rebase/merge/etc).
# Dirty indicators are appended separately via the async mechanism.
zstyle ':vcs_info:git:*' formats '%b'
zstyle ':vcs_info:git:*' actionformats '%b|%a'
# --- Async state variables ---
typeset -g _git_info="" # Full formatted string for the prompt, e.g. "[mainucN]"
typeset -g _async_git_fd="" # File descriptor for the current async worker process
typeset -g _async_git_branch="" # Branch name captured when async was kicked off
# Detect if we're in a git worktree (fast filesystem check, no git commands)
_get_worktree_name() {
# Walk up to find .git file/dir
local dir="$PWD"
while [[ "$dir" != "/" ]]; do
if [[ -f "$dir/.git" ]]; then
# .git is a file in worktrees, contains: gitdir: /path/.git/worktrees/NAME
local gitdir_line
gitdir_line=$(<"$dir/.git")
if [[ "$gitdir_line" == *"/worktrees/"* ]]; then
# Extract worktree name from path
echo "${gitdir_line##*/}"
return
fi
elif [[ -d "$dir/.git" ]]; then
# Regular repo (not a worktree)
return
fi
dir="${dir:h}"
done
}
# Called by zle when the async worker's fd becomes readable (i.e. it finished).
# Reads the dirty indicators, rebuilds _git_info, and redraws the prompt.
_async_git_callback() {
local fd=$1
local dirty=""
# Read the dirty indicators string from the background process
read -r dirty <&$fd 2>/dev/null
# Clean up the fd watcher and close the descriptor
zle -F "$fd"
exec {fd}<&-
_async_git_fd=""
# Rebuild _git_info with dirty indicators appended to the branch name
if [[ -n "$_async_git_branch" ]]; then
_git_info="[${_async_git_branch}${dirty}]"
fi
# Redraw the prompt (only if zle is active, i.e. user is at a prompt)
zle && zle reset-prompt
}
# Cancel any in-flight async check (e.g. user pressed enter before it finished,
# or cd'd to a different directory)
_async_git_cancel() {
if [[ -n "$_async_git_fd" ]]; then
zle -F "$_async_git_fd" 2>/dev/null
exec {_async_git_fd}<&- 2>/dev/null
_async_git_fd=""
fi
}
precmd() {
# Reset kitty keyboard protocol if a crashed program left it enabled.
# This prevents stale CSI u mode from corrupting keypresses (e.g. 9;5u).
# Pop up to 99 levels - Ghostty silently ignores this when protocol is not active.
[[ "$TERM_PROGRAM" == "ghostty" ]] && printf '\e[<99u' 2>/dev/null
vcs_info
# Cancel any still-pending async check from the previous prompt cycle
_async_git_cancel
# Worktree indicator (fast filesystem check, no git commands)
local wt_name
wt_name=$(_get_worktree_name)
if [[ -n "$wt_name" ]]; then
_worktree_indicator="%{$(tput setaf 214)%}⎇${wt_name}%{$(tput sgr0)%} "
else
_worktree_indicator=""
fi
# Show venv only if it's not the default ~/.venv
if [[ -n "$VIRTUAL_ENV" && "$VIRTUAL_ENV" != "$HOME/.venv" ]]; then
local venv_name="${VIRTUAL_ENV:t}"
# If venv is named .venv, show parent directory (project name) instead
[[ "$venv_name" == ".venv" ]] && venv_name="${VIRTUAL_ENV:h:t}"
_venv_indicator="%{$(tput setaf 34)%}(${venv_name})%{$(tput sgr0)%} "
else
_venv_indicator=""
fi
# Git info: render branch immediately, dirty state arrives async
if [[ -n "$vcs_info_msg_0_" ]]; then
_async_git_branch="$vcs_info_msg_0_"
_git_info="[${_async_git_branch}]" # Immediate: just [branch], no dirty indicators yet
# Kick off background process to compute dirty indicators.
# This is where the expensive `git diff` runs without blocking the prompt.
local async_pwd="$PWD"
exec {_async_git_fd}< <(
builtin cd -q "$async_pwd" 2>/dev/null || exit
local indicators=""
# Unstaged changes (u) -- this is the slow one in large/LFS repos
git diff --no-ext-diff --quiet 2>/dev/null || indicators+="u"
# Staged changes (c)
git diff --cached --no-ext-diff --quiet 2>/dev/null || indicators+="c"
# Untracked files (N)
[[ -n "$(git ls-files --other --exclude-standard 2>/dev/null | head -1)" ]] && indicators+="N"
echo "$indicators"
)
# Register callback for when the background process writes its result
zle -F "$_async_git_fd" _async_git_callback
else
_git_info=""
_async_git_branch=""
fi
}
# %b - Branch information, like main
# Indicators (computed async): u = unstaged, c = staged, N = untracked
# e.g. [main], [mainuc], [mainucN], [main|rebase]
# Directory-aware background tint for Ghostty.
# Sends OSC 11 to change the terminal background based on $PWD.
# OSC 111 resets to the theme default (Challenger Deep = dark purple).
#
# Palette - pick from these or add your own:
typeset -gA _ghostty_bg_palette=(
[plum_red]='#2e1419' # warm red-purple
[petrol]='#082228' # blue-green
[ocean]='#08162e' # deep navy
[ember]='#302008' # warm amber/orange
[emerald]='#06281a' # bright green
[indigo]='#1e0a32' # strong violet
[blood]='#300808' # dark red
[slate]='#181e28' # cool grey-blue
[moss]='#182a08' # yellow-green
[bronze]='#2e1e06' # golden brown
# lighter variants
[sandstone]='#3d3020' # warm tan
[dusk]='#2e2840' # soft lavender
[lagoon]='#183038' # bright teal
[clay]='#3a2218' # terracotta
[sage]='#1e3220' # muted green
[wine]='#381828' # bright magenta-red
[honey]='#383010' # rich gold
)
# Directory -> palette colour mappings (add new case branches as needed):
_ghostty_dir_bg() {
[[ "$TERM_PROGRAM" != "ghostty" ]] && return
local colour
case "$PWD" in
"$HOME"/git/mantel|"$HOME"/git/mantel/*)
colour="${_ghostty_bg_palette[plum_red]}" ;;
"$HOME"/.claude|"$HOME"/.claude/*)
colour="${_ghostty_bg_palette[honey]}" ;;
*)
printf '\e]111\e\\' # no match, reset to theme default
return ;;
esac
printf '\e]11;%s\e\\' "$colour"
}
add-zsh-hook chpwd _ghostty_dir_bg
_ghostty_dir_bg # set initial state for the shell's starting directory
# SSH wrapper: tint background per remote host, restore on disconnect.
# Host matching is against all args so it works with user@host, -p, etc.
ssh() {
if [[ "$TERM_PROGRAM" == "ghostty" ]]; then
local colour
case "$*" in
*example-host*) colour="${_ghostty_bg_palette[ocean]}" ;;
# *prod*) colour="${_ghostty_bg_palette[blood]}" ;;
esac
[[ -n "$colour" ]] && printf '\e]11;%s\e\\' "$colour"
fi
command ssh "$@"
# Restore directory-based colour (or theme default) after disconnect
_ghostty_dir_bg
}
PROMPT="\$_venv_indicator%{$(tput setaf 177)%}\$_git_info \$_worktree_indicator%{$(tput setaf 105)%}%~%{$(tput sgr0)%} %(?..[%?] )▸ "
else
# Simple prompt for VS Code terminal
PROMPT="%{$(tput setaf 105)%}%~%{$(tput sgr0)%} %(?..[%?] )▸ "
fi