Poll async git status while prompt is idle

This commit is contained in:
Justin Hileman
2026-05-15 02:58:23 -04:00
parent 5f9fa8e3ab
commit 3d9e51b786
3 changed files with 98 additions and 65 deletions

View File

@@ -216,6 +216,14 @@ Git status is rendered asynchronously by default in interactive shells. Bobthefi
Bobthefish waits at least one second between async Git refresh workers by default. If a prompt render is skipped during that interval, Bobthefish schedules one trailing refresh. Set this to `0` to check Git state on every prompt render.
#### `set -g theme_git_async_first_render_budget_ms 0`
Bobthefish waits up to 200ms for async Git metadata when entering a repo without current cached state, avoiding a pending Git segment when the worker finishes quickly. Set this to `0` to always render immediately.
#### `set -g theme_git_async_poll_interval 0`
Bobthefish polls the current Git repo every five seconds while the prompt is idle, updating and repainting the prompt when Git metadata changes. Set this to `0` or `no` to disable idle polling, or to another number of seconds to adjust the interval.
#### `set -g theme_display_git_dirty no`
Use `no` to hide Git dirty state. Set the Git `bash.showDirtyState` option on a per-repository basis to disable it just for especially large repos.

View File

@@ -1,64 +1,3 @@
function __bobthefish_git_async_enabled -S -d 'Check whether async git prompt updates should be used'
if [ "$argv[1]" = --init ]
if status is-interactive; and __bobthefish_version_at_least 3.0.0 $version
# The git metadata var must be universal: the detached background fish
# writes it, and a universal variable is the only channel that can deliver
# a value back into this (the parent) shell. It's namespaced by pid so
# concurrent sessions don't clobber each other.
set -g __bobthefish_git_async_var __bobthefish_git_async_$fish_pid
set -U $__bobthefish_git_async_var
# Fires when the background worker publishes new metadata. The pending
# flag tells the next fish_prompt that this render is the async repaint
# (so it must not spawn another worker).
function __bobthefish_git_async_repaint --on-variable $__bobthefish_git_async_var
set -g __bobthefish_git_async_repaint_pending 1
commandline --function repaint
end
function __bobthefish_git_async_cleanup --on-event fish_exit
set -q __bobthefish_git_async_pid
and command kill $__bobthefish_git_async_pid 2>/dev/null
set -q __bobthefish_git_async_trailing_pid
and command kill $__bobthefish_git_async_trailing_pid 2>/dev/null
set -q __bobthefish_git_async_var
and set -e $__bobthefish_git_async_var
end
__bobthefish_git_async_gc
end
# The git metadata var must be universal: the detached background fish
# writes it, and a universal variable is the only channel that can deliver
# a value back into this (the parent) shell. It's namespaced by pid so
# concurrent sessions don't clobber each other.
set -g __bobthefish_git_async_var __bobthefish_git_async_$fish_pid
set -U $__bobthefish_git_async_var
# Fires when the background worker publishes new metadata. The pending
# flag tells the next fish_prompt that this render is the async repaint
# (so it must not spawn another worker).
function __bobthefish_git_async_repaint --on-variable $__bobthefish_git_async_var
set -g __bobthefish_git_async_repaint_pending 1
commandline --function repaint
end
function __bobthefish_git_async_cleanup --on-event fish_exit
set -q __bobthefish_git_async_pid
and command kill $__bobthefish_git_async_pid 2>/dev/null
set -q __bobthefish_git_async_trailing_pid
and command kill $__bobthefish_git_async_trailing_pid 2>/dev/null
set -q __bobthefish_git_async_poll_pid
and command kill $__bobthefish_git_async_poll_pid 2>/dev/null
set -q __bobthefish_git_async_var
and set -e $__bobthefish_git_async_var
end
__bobthefish_git_async_gc
end
function __bobthefish_git_async_enabled -S -d 'Check whether async git prompt updates should be used'
[ "$theme_display_git_async" = no ]
and return 1
@@ -111,14 +50,93 @@ function __bobthefish_git_async_gc -S -d 'Remove async git metadata left behind
end
end
function __bobthefish_git_async_refresh_interval -S -d 'Get the minimum seconds between async git workers'
set -l interval 1
string match -qr '^\d+$' -- "$theme_git_async_refresh_interval"
and set interval $theme_git_async_refresh_interval
function __bobthefish_git_async_interval -S -a varname -a fallback -d 'Resolve a positive-integer async git interval, falling back to a default'
set -l interval $fallback
set -l configured $$varname
string match -qr '^\d+$' -- "$configured"
and set interval $configured
echo $interval
end
function __bobthefish_git_async_refresh_interval -S -d 'Get the minimum seconds between async git workers'
__bobthefish_git_async_interval theme_git_async_refresh_interval 1
end
function __bobthefish_git_async_poll_interval -S -d 'Get the interval for idle async git refresh polling (0 if disabled)'
[ "$theme_git_async_poll_interval" = no ]
and echo 0
and return
__bobthefish_git_async_interval theme_git_async_poll_interval 5
end
function __bobthefish_git_async_first_render_budget_ms -S -d 'Get the maximum milliseconds to wait for first async git metadata'
__bobthefish_git_async_interval theme_git_async_first_render_budget_ms 200
end
function __bobthefish_git_async_clear_trailing -S -d 'Kill and forget the trailing async git refresh worker, if any'
set -q __bobthefish_git_async_trailing_pid
or return
command kill $__bobthefish_git_async_trailing_pid 2>/dev/null
set -e __bobthefish_git_async_trailing_pid
end
# Must be called whenever the prompt leaves git-async context (sync mode, dumb
# terminal, hg/fossil/no repo). A poller left running keeps forking workers
# against the repo it was started for, long after the shell has cd'd away.
function __bobthefish_git_async_stop_poller -S -d 'Stop the idle async git refresh poller'
set -q __bobthefish_git_async_poll_pid
or return
command kill $__bobthefish_git_async_poll_pid 2>/dev/null
set -e __bobthefish_git_async_poll_pid
set -e __bobthefish_git_async_poll_key
end
function __bobthefish_git_async_start_poller -S -a git_root_dir -d 'Start an idle poller for async git prompt metadata'
set -q __bobthefish_git_async_var
or return
set -l interval (__bobthefish_git_async_poll_interval)
if [ "$interval" -le 0 ]
__bobthefish_git_async_stop_poller
return
end
set -l worker_path (__bobthefish_git_async_worker_path)
or return
set -l fish_bin (__bobthefish_git_async_fish)
set -l worker_theme_args "$theme_display_git_dirty" "$theme_display_git_dirty_verbose" "$theme_display_git_untracked" "$theme_display_git_stashed_verbose"
# The forked poller bakes in the repo, interval, and display flags, so a
# running poller is only reusable while all of those still match. Re-keying
# on any change (cd to another repo, a mid-session theme tweak) replaces a
# stale poller instead of leaving it publishing metadata under old settings.
set -l poll_key (string join \x1e -- $git_root_dir $interval $worker_theme_args)
if set -q __bobthefish_git_async_poll_pid
if command kill -0 $__bobthefish_git_async_poll_pid 2>/dev/null
[ "$__bobthefish_git_async_poll_key" = "$poll_key" ]
and return
end
__bobthefish_git_async_stop_poller
end
command $fish_bin --private -c '
source $argv[2]
while true
sleep $argv[1]
__bobthefish_git_async_worker $argv[3] $argv[4] $argv[5] $argv[6] $argv[7] $argv[8]
end
' $interval $worker_path $__bobthefish_git_async_var $git_root_dir $worker_theme_args &
builtin -q disown
and builtin disown
set -g __bobthefish_git_async_poll_pid $last_pid
set -g __bobthefish_git_async_poll_key $poll_key
end
function __bobthefish_git_async_schedule_trailing -S -a git_root_dir -a delay -d 'Schedule a trailing async git refresh'
[ "$delay" -gt 0 ]
or return
@@ -152,6 +170,8 @@ function __bobthefish_git_async_start -S -a git_root_dir -d 'Start a background
set -q __bobthefish_git_async_var
or return
__bobthefish_git_async_start_poller $git_root_dir
# Keep at most one worker active per prompt session. Re-rendering while a
# worker is still collecting git state should not kill the worker that will
# publish the fresh cache.

View File

@@ -1065,6 +1065,7 @@ function __bobthefish_prompt_git -S -a git_root_dir -a real_pwd -a repaint -d 'D
if __bobthefish_git_async_enabled
__bobthefish_prompt_git_async $git_root_dir $real_pwd $repaint
else
__bobthefish_git_async_stop_poller
__bobthefish_prompt_git_sync $git_root_dir $real_pwd
end
end
@@ -1104,6 +1105,7 @@ function fish_prompt -d 'bobthefish, a fish theme optimized for awesome'
# Use a simple prompt on dumb terminals.
if [ "$TERM" = dumb ]
__bobthefish_git_async_stop_poller
echo '> '
return
end
@@ -1153,12 +1155,15 @@ function fish_prompt -d 'bobthefish, a fish theme optimized for awesome'
# only show the closest parent
switch (__bobthefish_closest_parent "$git_root_dir" "$hg_root_dir" "$fossil_root_dir")
case ''
__bobthefish_git_async_stop_poller
__bobthefish_prompt_dir $real_pwd
case "$git_root_dir"
__bobthefish_prompt_git $git_root_dir $real_pwd $git_async_repaint
case "$hg_root_dir"
__bobthefish_git_async_stop_poller
__bobthefish_prompt_hg $hg_root_dir $real_pwd
case "$fossil_root_dir"
__bobthefish_git_async_stop_poller
__bobthefish_prompt_fossil $fossil_root_dir $real_pwd
end