Improve git prompt performance

This commit is contained in:
Justin Hileman
2026-05-15 02:33:01 -04:00
parent 72a89d4246
commit 5f9fa8e3ab
9 changed files with 752 additions and 238 deletions

View File

@@ -19,6 +19,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo green $colorfg
set -x color_repo_work_tree black $colorfg --bold
set -x color_repo_pending black $colorfg --bold
set -x color_repo_dirty brred $colorfg
set -x color_repo_staged yellow $colorfg
@@ -55,6 +56,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo green $colorfg
set -x color_repo_work_tree white $colorfg --bold
set -x color_repo_pending white $colorfg --bold
set -x color_repo_dirty brred $colorfg
set -x color_repo_staged yellow $colorfg
@@ -91,6 +93,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo green $colorfg
set -x color_repo_work_tree brgrey $colorfg --bold
set -x color_repo_pending brgrey $colorfg --bold
set -x color_repo_dirty brred $colorfg
set -x color_repo_staged yellow $colorfg
@@ -127,6 +130,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo green $colorfg
set -x color_repo_work_tree grey $colorfg --bold
set -x color_repo_pending grey $colorfg --bold
set -x color_repo_dirty brred $colorfg
set -x color_repo_staged yellow $colorfg
@@ -169,6 +173,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green $grey
set -x color_repo_work_tree $grey $grey --bold
set -x color_repo_pending $grey $grey --bold
set -x color_repo_dirty $red $grey
set -x color_repo_staged $yellow $grey
@@ -222,6 +227,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $base0B $colorfg
set -x color_repo_work_tree $base06 $colorfg --bold
set -x color_repo_pending $base06 $colorfg --bold
set -x color_repo_dirty $base08 $colorfg
set -x color_repo_staged $base09 $colorfg
@@ -275,6 +281,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $base0B $colorfg
set -x color_repo_work_tree $base02 $colorfg --bold
set -x color_repo_pending $base02 $colorfg --bold
set -x color_repo_dirty $base08 $colorfg
set -x color_repo_staged $base09 $colorfg
@@ -328,6 +335,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green $colorfg
set -x color_repo_work_tree $base2 $colorfg --bold
set -x color_repo_pending $base2 $colorfg --bold
set -x color_repo_dirty $red $colorfg
set -x color_repo_staged $yellow $colorfg
@@ -381,6 +389,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green $colorfg
set -x color_repo_work_tree $base02 $colorfg --bold
set -x color_repo_pending $base02 $colorfg --bold
set -x color_repo_dirty $red $colorfg
set -x color_repo_staged $yellow $colorfg
@@ -427,6 +436,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green[1] $green[3]
set -x color_repo_work_tree $grey[1] $white --bold
set -x color_repo_pending $grey[1] $white --bold
set -x color_repo_dirty $red[2] $white
set -x color_repo_staged $orange[1] $orange[3]
@@ -472,6 +482,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green[2] $bg[1]
set -x color_repo_work_tree $bg[1] $fg[2] --bold
set -x color_repo_pending $bg[1] $fg[2] --bold
set -x color_repo_dirty $red[2] $fg[2]
set -x color_repo_staged $yellow[1] $bg[1]
@@ -516,7 +527,8 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_path_nowrite_basename $red[1] $bg[2] --bold
set -x color_repo $green[2] $fg[1]
set -x color_repo_work_tree $bg[1] $fg[2] --bold
set -x color_repo_work_tree $bg[1] $fg[2] --bold
set -x color_repo_pending $bg[1] $fg[2] --bold
set -x color_repo_dirty $orange[2] $bg[2]
set -x color_repo_staged $yellow[1] $bg[1]
@@ -562,6 +574,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green $bg
set -x color_repo_work_tree $selection $fg --bold
set -x color_repo_pending $selection $fg --bold
set -x color_repo_dirty $red $bg
set -x color_repo_staged $yellow $bg
@@ -615,6 +628,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $base0E $colorfg
set -x color_repo_work_tree $base02 $colorfg --bold
set -x color_repo_pending $base02 $colorfg --bold
set -x color_repo_dirty $base0B $colorfg
set -x color_repo_staged $base0D $colorfg
@@ -675,6 +689,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green $mantle
set -x color_repo_work_tree $surface2 $mantle --bold
set -x color_repo_pending $surface2 $mantle --bold
set -x color_repo_dirty $red $mantle
set -x color_repo_staged $yellow $mantle
@@ -736,6 +751,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green $mantle
set -x color_repo_work_tree $surface2 $mantle --bold
set -x color_repo_pending $surface2 $mantle --bold
set -x color_repo_dirty $red $mantle
set -x color_repo_staged $yellow $mantle
@@ -797,6 +813,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green $mantle
set -x color_repo_work_tree $surface2 $mantle --bold
set -x color_repo_pending $surface2 $mantle --bold
set -x color_repo_dirty $red $mantle
set -x color_repo_staged $yellow $mantle
@@ -858,6 +875,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green $mantle
set -x color_repo_work_tree $surface2 $mantle --bold
set -x color_repo_pending $surface2 $mantle --bold
set -x color_repo_dirty $red $mantle
set -x color_repo_staged $yellow $mantle
@@ -917,7 +935,8 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_path_nowrite_basename $dark_grey $red_orange --bold
set -x color_repo $light_green $darker_grey
set -x color_repo_work_tree $dark_grey $light_grey --bold
set -x color_repo_work_tree $dark_grey $light_grey --bold
set -x color_repo_pending $dark_grey $light_grey --bold
set -x color_repo_dirty $red $darker_grey
set -x color_repo_staged $pale_gold $darker_grey
@@ -962,6 +981,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green[3] $white[4]
set -x color_repo_work_tree $grey[4] $grey[4] --bold
set -x color_repo_pending $grey[4] $grey[4] --bold
set -x color_repo_dirty $red[3] $grey[4]
set -x color_repo_staged $yellow[3] $grey[4]
@@ -1009,6 +1029,7 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_repo $green[1] $green[3]
set -x color_repo_work_tree $grey[3] $white --bold
set -x color_repo_pending $grey[3] $white --bold
set -x color_repo_dirty $red[2] $white
set -x color_repo_staged $orange[1] $orange[3]
@@ -1030,6 +1051,9 @@ function __bobthefish_colors -S -a color_scheme -d 'Define colors used by bobthe
set -x color_desk $blue[2] $grey[1] --bold
set -x color_nix $blue[2] $grey[1] --bold
end
set -q color_repo_pending
or set -x color_repo_pending $color_repo_work_tree
end
function __bobthefish_user_color_scheme_deprecated
@@ -1043,6 +1067,7 @@ function __bobthefish_user_color_scheme_deprecated
set -q __color_path_nowrite_basename; or set -l __color_path_nowrite_basename 660000 cc9999 --bold
set -q __color_repo; or set -l __color_repo addc10 0c4801
set -q __color_repo_work_tree; or set -l __color_repo_work_tree 333333 ffffff --bold
set -q __color_repo_pending; or set -l __color_repo_pending 333333 ffffff --bold
set -q __color_repo_dirty; or set -l __color_repo_dirty ce000f ffffff
set -q __color_repo_staged; or set -l __color_repo_staged f6b117 3a2a03
set -q __color_vi_mode_default; or set -l __color_vi_mode_default 999999 333333 --bold
@@ -1081,6 +1106,7 @@ function __bobthefish_user_color_scheme_deprecated
set -x color_path_nowrite_basename $__color_path_nowrite_basename
set -x color_repo $__color_repo
set -x color_repo_work_tree $__color_repo_work_tree
set -x color_repo_pending $__color_repo_pending
set -x color_repo_dirty $__color_repo_dirty
set -x color_repo_staged $__color_repo_staged
set -x color_vi_mode_default $__color_vi_mode_default

View File

@@ -0,0 +1,210 @@
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
[ "$theme_git_worktree_support" = yes ]
and return 1
__bobthefish_version_at_least 3.0.0 $version
and status is-interactive
and set -q __bobthefish_git_async_var
and command -q fish
and __bobthefish_git_async_worker_path >/dev/null
end
function __bobthefish_git_async_worker_path -S -d 'Resolve and cache the async git worker file'
set -q __bobthefish_git_async_worker_path_cache
and echo $__bobthefish_git_async_worker_path_cache
and return
set -l prompt_path (functions --details fish_prompt)
[ -f "$prompt_path" ]
or return
set -l resolved (dirname "$prompt_path")/__bobthefish_git_async_worker.fish
[ -f "$resolved" ]
or return
set -g __bobthefish_git_async_worker_path_cache $resolved
echo $resolved
end
function __bobthefish_git_async_fish -S -d 'Resolve and cache the fish binary used for async git workers'
set -q __bobthefish_git_async_fish_path
or set -g __bobthefish_git_async_fish_path (command -s fish)
echo $__bobthefish_git_async_fish_path
end
function __bobthefish_git_async_gc -S -d 'Remove async git metadata left behind by dead fish sessions'
set -l prefix __bobthefish_git_async_
for var in (set --names --universal | string match "$prefix*")
[ "$var" = "$__bobthefish_git_async_var" ]
and continue
set -l pid (string replace $prefix '' -- $var)
string match -qr '^\d+$' -- "$pid"
or continue
command kill -0 $pid 2>/dev/null
or set -e $var
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
echo $interval
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
set -q __bobthefish_git_async_var
or return
set -l worker_path (__bobthefish_git_async_worker_path)
or return
set -l fish_bin (__bobthefish_git_async_fish)
if set -q __bobthefish_git_async_trailing_pid
command kill -0 $__bobthefish_git_async_trailing_pid 2>/dev/null
and [ "$__bobthefish_git_async_trailing_root_dir" = "$git_root_dir" ]
and return
__bobthefish_git_async_clear_trailing
end
command $fish_bin --private -c '
sleep $argv[1]
source $argv[2]
__bobthefish_git_async_worker $argv[3] $argv[4] $argv[5] $argv[6] $argv[7] $argv[8]
' $delay $worker_path $__bobthefish_git_async_var $git_root_dir "$theme_display_git_dirty" "$theme_display_git_dirty_verbose" "$theme_display_git_untracked" "$theme_display_git_stashed_verbose" &
builtin -q disown
and builtin disown
set -g __bobthefish_git_async_trailing_pid $last_pid
set -g __bobthefish_git_async_trailing_root_dir $git_root_dir
end
function __bobthefish_git_async_start -S -a git_root_dir -d 'Start a background git prompt metadata update'
set -q __bobthefish_git_async_var
or return
# 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.
if set -q __bobthefish_git_async_pid
if command kill -0 $__bobthefish_git_async_pid 2>/dev/null
[ "$__bobthefish_git_async_root_dir" = "$git_root_dir" ]
and return
command kill $__bobthefish_git_async_pid 2>/dev/null
end
set -e __bobthefish_git_async_pid
end
if set -q __bobthefish_git_async_trailing_pid
if [ "$__bobthefish_git_async_trailing_root_dir" != "$git_root_dir" ]
__bobthefish_git_async_clear_trailing
else if not command kill -0 $__bobthefish_git_async_trailing_pid 2>/dev/null
set -e __bobthefish_git_async_trailing_pid
end
end
set -l now (date +%s)
set -l interval (__bobthefish_git_async_refresh_interval)
# Git state can change inside a repo between prompts, so this is a short
# debounce rather than a same-repo cache. Set theme_git_async_refresh_interval
# to 0 to spawn a worker on every eligible prompt render.
if [ "$interval" -gt 0 -a "$__bobthefish_git_async_root_dir" = "$git_root_dir" -a -n "$__bobthefish_git_async_last_start" ]
set -l elapsed (math $now - $__bobthefish_git_async_last_start)
if [ "$elapsed" -lt "$interval" ]
__bobthefish_git_async_schedule_trailing $git_root_dir (math $interval - $elapsed)
return
end
end
__bobthefish_git_async_clear_trailing
set -l worker_path (__bobthefish_git_async_worker_path)
or return
set -l fish_bin (__bobthefish_git_async_fish)
set -l cmd source (string escape -- $worker_path) ';' __bobthefish_git_async_worker \
(string escape -- $__bobthefish_git_async_var) \
(string escape -- $git_root_dir) \
(string escape -- "$theme_display_git_dirty") \
(string escape -- "$theme_display_git_dirty_verbose") \
(string escape -- "$theme_display_git_untracked") \
(string escape -- "$theme_display_git_stashed_verbose")
set -g __bobthefish_git_async_root_dir $git_root_dir
set -g __bobthefish_git_async_last_start $now
command $fish_bin --private --command "$cmd" &
builtin -q disown
and builtin disown
set -g __bobthefish_git_async_pid $last_pid
end

View File

@@ -0,0 +1,110 @@
# __bobthefish_git_async_counts / __bobthefish_git_async_dirty_verbose are
# deliberate copies of __bobthefish_git_counts / __bobthefish_git_dirty_verbose
# from __bobthefish_prompt_git_sync.fish. The worker runs in a non-interactive
# `fish --private -c` that bootstraps by sourcing only this file, so it must not
# rely on the theme's autoload path being configured (some shells load omf for
# interactive sessions only). Keep these in sync with their sync counterparts.
function __bobthefish_git_async_counts -S -d 'Print behind and ahead counts for the current branch'
set -l counts (command git rev-list --left-right --count '@{upstream}...HEAD' 2>/dev/null)
or return
set counts (string split \t -- $counts)
if [ (count $counts) -lt 2 ]
set counts (string split ' ' -- $counts)
end
[ (count $counts) -ge 2 ]
or return
printf '%s\n%s\n' $counts[1] $counts[2]
end
function __bobthefish_git_async_dirty_verbose -S -d 'Print a more verbose dirty state for the current working tree'
set -l diff_stats (command git diff --numstat)
or return
set -l added 0
set -l removed 0
for line in $diff_stats
set -l fields (string split \t -- $line)
string match -rq '^\d+$' -- $fields[1]
and set added (math $added + $fields[1])
string match -rq '^\d+$' -- $fields[2]
and set removed (math $removed + $fields[2])
end
set -l changes "+$added/-$removed"
echo "$changes " | string replace -r '(\+0/(-0)?|/-0)' ''
end
function __bobthefish_git_async_worker -S -a git_async_var -a git_root_dir -a display_dirty -a display_dirty_verbose -a display_untracked -a display_stashed_verbose -d 'Compute git prompt metadata in a background fish'
set -l tag (command git describe --tags --exact-match 2>/dev/null)
set -l branch (command git symbolic-ref --quiet --short HEAD 2>/dev/null)
set -l detached
[ -z "$tag" -a -z "$branch" ]
and set detached (command git rev-parse --short HEAD 2>/dev/null)
# Only resolve the configured default branch when it's actually needed
set -l configured_default_branch
[ -n "$branch" -a -z "$theme_git_default_branches" ]
and set configured_default_branch (command git config init.defaultBranch 2>/dev/null)
set -l dirty
set -l dirty_stats
if [ "$display_dirty" != no ]
set -l show_dirty (command git config --bool bash.showDirtyState 2>/dev/null)
if [ "$show_dirty" != false ]
command git diff --no-ext-diff --quiet --exit-code 2>/dev/null
or begin
set dirty 1
[ "$display_dirty_verbose" = yes ]
and set dirty_stats (__bobthefish_git_async_dirty_verbose)
end
end
end
set -l staged
command git diff --cached --no-ext-diff --quiet --exit-code 2>/dev/null
or set staged 1
set -l stashed
if [ "$display_stashed_verbose" = yes ]
set stashed (command git rev-list --walk-reflogs --count refs/stash 2>/dev/null)
or set stashed
else
command git rev-parse --verify --quiet refs/stash >/dev/null
and set stashed 1
end
set -l ahead 0
set -l behind 0
set -l counts (__bobthefish_git_async_counts)
and begin
set behind $counts[1]
set ahead $counts[2]
end
set -l new
if [ "$display_untracked" != no ]
set -l show_untracked (command git config --bool bash.showUntrackedFiles 2>/dev/null)
if [ "$show_untracked" != false ]
set new (command git ls-files --other --exclude-standard --directory --no-empty-directory "$git_root_dir" 2>/dev/null)
[ "$new" ]
and set new 1
end
end
# Field order is a contract shared with __bobthefish_prompt_git_async,
# which unpacks these by position. Keep the two in sync.
set -l next "$git_root_dir" "$tag" "$branch" "$detached" "$dirty" "$dirty_stats" "$staged" "$stashed" "$ahead" "$behind" "$new" "$configured_default_branch"
# Skip the universal-variable write (disk I/O + a repaint broadcast to
# every fish session) when nothing actually changed since the last run.
set -l current $$git_async_var
set -l next_key (string join \x1e -- $next)
set -l current_key (string join \x1e -- $current)
[ "$next_key" = "$current_key" ]
and return
set -U $git_async_var $next
end

View File

@@ -0,0 +1,25 @@
function __bobthefish_git_branch_from_parts -S -a tag -a branch -a detached -a configured_default_branch -d 'Format a git branch from git metadata'
[ -n "$tag" ]
and echo "$tag_glyph $tag "
if [ -n "$branch" ]
[ -n "$theme_git_default_branches" ]
or set -l theme_git_default_branches master main $configured_default_branch
[ "$theme_display_git_master_branch" != yes -a "$theme_display_git_default_branch" != yes ]
and contains $branch $theme_git_default_branches
and echo $branch_glyph
and return
set -l truncname $branch
[ "$theme_use_abbreviated_branch_name" = yes ]
and set truncname (string replace -r '^(.{17}).{3,}(.{5})$' "\$1…\$2" $branch)
echo $branch_glyph $truncname
and return
end
if [ -z "$tag" -a -n "$detached" ]
echo "$detached_glyph $detached"
end
end

View File

@@ -0,0 +1,137 @@
function __bobthefish_git_count_or_zero -S -a count -d 'Normalize a git count for numeric comparisons'
string match -qr '^\d+$' -- "$count"
and echo $count
or echo 0
end
function __bobthefish_git_pending_branch -S -a git_root_dir -d 'Get cheap branch metadata while async git state is pending'
# The placeholder is only shown until the worker first publishes for this
# repo. Caching by root keeps rapid renders (e.g. holding Enter in a fresh
# repo) from re-forking git on every prompt during that brief window.
if set -q __bobthefish_git_pending_root
and [ "$__bobthefish_git_pending_root" = "$git_root_dir" ]
echo $__bobthefish_git_pending_branch_cache
return
end
set -l rendered
set -l branch (command git symbolic-ref --quiet --short HEAD 2>/dev/null)
if [ -n "$branch" ]
# Keep the common branch path cheap. We intentionally do not call
# `git config init.defaultBranch` here; the worker will apply the full
# default-branch rules when it publishes complete metadata.
set rendered (__bobthefish_git_branch_from_parts '' "$branch" '')
else
set -l detached (command git rev-parse --short HEAD 2>/dev/null)
[ -n "$detached" ]
and set rendered (__bobthefish_git_branch_from_parts '' '' "$detached")
end
set -g __bobthefish_git_pending_root $git_root_dir
set -g __bobthefish_git_pending_branch_cache $rendered
echo $rendered
end
function __bobthefish_prompt_git_pending -S -a git_root_dir -a real_pwd -d 'Display an indeterminate repo segment while async git state is pending'
__bobthefish_path_segment $git_root_dir project
__bobthefish_start_segment $color_repo_pending
echo -ns (__bobthefish_git_pending_branch $git_root_dir) ' '
set_color normal
__bobthefish_prompt_git_pwd $git_root_dir $real_pwd
end
function __bobthefish_prompt_git_async -S -a git_root_dir -a real_pwd -a repaint -d 'Display git state from cached async metadata'
if [ "$repaint" != 1 ]
__bobthefish_git_async_start $git_root_dir
end
# Field order is the contract published by __bobthefish_git_async_worker.
set -l git_data $$__bobthefish_git_async_var
if [ "$git_data[1]" != "$git_root_dir" ]
if [ "$repaint" = 1 ]
__bobthefish_git_async_start $git_root_dir
end
set -l budget_ms (__bobthefish_git_async_first_render_budget_ms)
set -l waited_ms 0
while [ "$budget_ms" -gt 0 -a "$waited_ms" -lt "$budget_ms" ]
sleep 0.01
set git_data $$__bobthefish_git_async_var
[ "$git_data[1]" = "$git_root_dir" ]
and break
set waited_ms (math $waited_ms + 10)
end
end
if [ "$git_data[1]" != "$git_root_dir" ]
__bobthefish_prompt_git_pending $git_root_dir $real_pwd
return
end
set -l tag $git_data[2]
set -l branch $git_data[3]
set -l detached $git_data[4]
set -l dirty $git_data[5]
set -l dirty_stats $git_data[6]
set -l staged $git_data[7]
set -l stashed $git_data[8]
set -l ahead (__bobthefish_git_count_or_zero $git_data[9])
set -l behind (__bobthefish_git_count_or_zero $git_data[10])
set -l new $git_data[11]
set -l configured_default_branch $git_data[12]
[ "$dirty" ]
and set dirty "$git_dirty_glyph$dirty_stats"
[ "$staged" ]
and set staged "$git_staged_glyph"
if [ "$stashed" ]
[ "$theme_display_git_stashed_verbose" = yes ]
and set stashed "$git_stashed_glyph$stashed"
or set stashed "$git_stashed_glyph"
end
set -l ahead_flag
set -l verbose
[ "$theme_display_git_ahead_verbose" = yes ]
and set verbose 1
switch "$ahead $behind"
case '0 0' # equal to upstream
case '* 0' # ahead of upstream
[ -n "$verbose" ]
and set ahead_flag "$git_ahead_glyph$ahead"
or set ahead_flag "$git_plus_glyph"
case '0 *' # behind upstream
[ -n "$verbose" ]
and set ahead_flag "$git_behind_glyph$behind"
or set ahead_flag "$git_minus_glyph"
case '*' # diverged from upstream
[ -n "$verbose" ]
and set ahead_flag "$git_ahead_glyph$ahead$git_behind_glyph$behind"
or set ahead_flag "$git_plus_minus_glyph"
end
[ "$new" ]
and set new "$git_untracked_glyph"
set -l flags "$dirty$staged$stashed$ahead_flag$new"
[ "$flags" ]
and set flags " $flags"
set -l flag_colors $color_repo
if [ "$dirty" ]
set flag_colors $color_repo_dirty
else if [ "$staged" ]
set flag_colors $color_repo_staged
end
__bobthefish_path_segment $git_root_dir project
__bobthefish_start_segment $flag_colors
echo -ns (__bobthefish_git_branch_from_parts "$tag" "$branch" "$detached" "$configured_default_branch") $flags ' '
set_color normal
__bobthefish_prompt_git_pwd $git_root_dir $real_pwd
end

View File

@@ -0,0 +1,13 @@
function __bobthefish_prompt_git_pwd -S -a git_root_dir -a real_pwd -d 'Display the in-project path segment'
set -l project_pwd (__bobthefish_project_pwd $git_root_dir $real_pwd)
[ "$project_pwd" ]
or return
if [ -w "$real_pwd" ]
__bobthefish_start_segment $color_path
else
__bobthefish_start_segment $color_path_nowrite
end
echo -ns $project_pwd ' '
end

View File

@@ -0,0 +1,212 @@
function __bobthefish_git_counts -S -d 'Print behind and ahead counts for the current branch'
set -l counts (command git rev-list --left-right --count '@{upstream}...HEAD' 2>/dev/null)
or return
set counts (string split \t -- $counts)
if [ (count $counts) -lt 2 ]
set counts (string split ' ' -- $counts)
end
[ (count $counts) -ge 2 ]
or return
printf '%s\n%s\n' $counts[1] $counts[2]
end
function __bobthefish_git_branch -S -d 'Get the current git branch (or commitish)'
set -l tag (command git describe --tags --exact-match 2>/dev/null)
set -l branch (command git symbolic-ref --quiet --short HEAD 2>/dev/null)
set -l detached
[ -z "$tag" -a -z "$branch" ]
and set detached (command git rev-parse --short HEAD 2>/dev/null)
# Only resolve the configured default branch when it's actually needed
set -l configured_default_branch
[ -n "$branch" -a -z "$theme_git_default_branches" ]
and set configured_default_branch (command git config init.defaultBranch 2>/dev/null)
__bobthefish_git_branch_from_parts "$tag" "$branch" "$detached" "$configured_default_branch"
end
function __bobthefish_git_ahead -S -d 'Print the ahead/behind state for the current branch'
if [ "$theme_display_git_ahead_verbose" = yes ]
__bobthefish_git_ahead_verbose
return
end
set -l counts (__bobthefish_git_counts)
or return
set -l behind $counts[1]
set -l ahead $counts[2]
if [ $ahead -gt 0 -a $behind -gt 0 ]
echo "$git_plus_minus_glyph"
else if [ $ahead -gt 0 ]
echo "$git_plus_glyph"
else if [ $behind -gt 0 ]
echo "$git_minus_glyph"
end
end
function __bobthefish_git_ahead_verbose -S -d 'Print a more verbose ahead/behind state for the current branch'
set -l counts (__bobthefish_git_counts)
or return
set -l behind $counts[1]
set -l ahead $counts[2]
switch "$ahead $behind"
case '' # no upstream
case '0 0' # equal to upstream
return
case '* 0' # ahead of upstream
echo "$git_ahead_glyph$ahead"
case '0 *' # behind upstream
echo "$git_behind_glyph$behind"
case '*' # diverged from upstream
echo "$git_ahead_glyph$ahead$git_behind_glyph$behind"
end
end
function __bobthefish_git_dirty_verbose -S -d 'Print a more verbose dirty state for the current working tree'
set -l diff_stats (command git diff --numstat)
or return
set -l added 0
set -l removed 0
for line in $diff_stats
set -l fields (string split \t -- $line)
string match -rq '^\d+$' -- $fields[1]
and set added (math $added + $fields[1])
string match -rq '^\d+$' -- $fields[2]
and set removed (math $removed + $fields[2])
end
set -l changes "+$added/-$removed"
echo "$changes " | string replace -r '(\+0/(-0)?|/-0)' ''
end
function __bobthefish_git_stashed -S -d 'Print the stashed state for the current branch'
if [ "$theme_display_git_stashed_verbose" = yes ]
set -l stashed (command git rev-list --walk-reflogs --count refs/stash 2>/dev/null)
or return
echo -n "$git_stashed_glyph$stashed"
else
command git rev-parse --verify --quiet refs/stash >/dev/null
and echo -n "$git_stashed_glyph"
end
end
function __bobthefish_prompt_git_sync -S -a git_root_dir -a real_pwd -d 'Display the actual git state synchronously'
set -l dirty ''
if [ "$theme_display_git_dirty" != no ]
set -l show_dirty (command git config --bool bash.showDirtyState 2>/dev/null)
if [ "$show_dirty" != false ]
set dirty (command git diff --no-ext-diff --quiet --exit-code 2>/dev/null; or echo -n "$git_dirty_glyph")
if [ "$dirty" -a "$theme_display_git_dirty_verbose" = yes ]
set dirty "$dirty"(__bobthefish_git_dirty_verbose)
end
end
end
set -l staged (command git diff --cached --no-ext-diff --quiet --exit-code 2>/dev/null; or echo -n "$git_staged_glyph")
set -l stashed (__bobthefish_git_stashed)
set -l ahead (__bobthefish_git_ahead)
set -l new ''
if [ "$theme_display_git_untracked" != no ]
set -l show_untracked (command git config --bool bash.showUntrackedFiles 2>/dev/null)
if [ "$show_untracked" != false ]
set new (command git ls-files --other --exclude-standard --directory --no-empty-directory "$git_root_dir" 2>/dev/null)
if [ "$new" ]
set new "$git_untracked_glyph"
end
end
end
set -l flags "$dirty$staged$stashed$ahead$new"
[ "$flags" ]
and set flags " $flags"
set -l flag_colors $color_repo
if [ "$dirty" ]
set flag_colors $color_repo_dirty
else if [ "$staged" ]
set flag_colors $color_repo_staged
end
__bobthefish_path_segment $git_root_dir project
__bobthefish_start_segment $flag_colors
echo -ns (__bobthefish_git_branch) $flags ' '
set_color normal
if [ "$theme_git_worktree_support" != yes ]
__bobthefish_prompt_git_pwd $git_root_dir $real_pwd
return
end
set -l project_pwd (command git rev-parse --show-prefix 2>/dev/null | string trim --right --chars=/)
set -l work_dir (command git rev-parse --show-toplevel 2>/dev/null)
# only show work dir if it's a parent…
if [ "$work_dir" ]
switch $real_pwd/
case $work_dir/\*
string match "$git_root_dir*" $work_dir >/dev/null
and set work_dir (string sub -s (math 1 + (string length $git_root_dir)) $work_dir)
case \*
set -e work_dir
end
end
if [ "$project_pwd" -o "$work_dir" ]
set -l colors $color_path
if not [ -w "$real_pwd" ]
set colors $color_path_nowrite
end
__bobthefish_start_segment $colors
# handle work_dir != project dir
if [ "$work_dir" ]
set -l work_parent (__bobthefish_dirname $work_dir)
if [ "$work_parent" ]
echo -n "$work_parent/"
end
set_color normal
set_color -b $color_repo_work_tree
echo -n (__bobthefish_basename $work_dir)
set_color normal
set_color -b $colors
[ "$project_pwd" ]
and echo -n /
end
echo -ns $project_pwd ' '
else
set project_pwd $real_pwd
string match "$git_root_dir*" $project_pwd >/dev/null
and set project_pwd (string sub -s (math 1 + (string length $git_root_dir)) $project_pwd)
set project_pwd (string trim --left --chars=/ -- $project_pwd)
if [ "$project_pwd" ]
set -l colors $color_path
if not [ -w "$real_pwd" ]
set colors $color_path_nowrite
end
__bobthefish_start_segment $colors
echo -ns $project_pwd ' '
end
end
end

View File

@@ -65,35 +65,7 @@ function __bobthefish_version_at_least -S -a required -a actual -d 'Compare dott
return 0
end
function __bobthefish_git_branch -S -d 'Get the current git branch (or commitish)'
set -l tag (command git describe --tags --exact-match 2>/dev/null)
and echo "$tag_glyph $tag "
set -l branch (command git symbolic-ref --quiet --short HEAD 2>/dev/null)
and begin
[ -n "$theme_git_default_branches" ]
or set -l theme_git_default_branches master main (command git config init.defaultBranch 2>/dev/null)
[ "$theme_display_git_master_branch" != yes -a "$theme_display_git_default_branch" != yes ]
and contains $branch $theme_git_default_branches
and echo $branch_glyph
and return
# truncate the middle of the branch name, but only if it's 25+ characters
set -l truncname $branch
[ "$theme_use_abbreviated_branch_name" = yes ]
and set truncname (string replace -r '^(.{17}).{3,}(.{5})$' "\$1…\$2" $branch)
echo $branch_glyph $truncname
and return
end
# If we've already shown a tag we don't need to show a detached branch
if [ -z "$tag" ]
set -l branch (command git rev-parse --short HEAD 2>/dev/null)
echo "$detached_glyph $branch"
end
end
__bobthefish_git_async_init
function __bobthefish_fossil_branch -S -d 'Get the current fossil branch'
set -l branch (command fossil branch 2>/dev/null | string trim --left --chars=' *')
@@ -271,96 +243,6 @@ function __bobthefish_project_pwd -S -a project_root_dir -a real_pwd -d 'Print t
string replace -ar '(\.?[^/]{'"$theme_project_dir_length"'})[^/]*/' '$1/' $project_dir
end
function __bobthefish_git_ahead -S -d 'Print the ahead/behind state for the current branch'
if [ "$theme_display_git_ahead_verbose" = yes ]
__bobthefish_git_ahead_verbose
return
end
set -l ahead 0
set -l behind 0
for line in (command git rev-list --left-right '@{upstream}...HEAD' 2>/dev/null)
switch "$line"
case '>*'
if [ $behind -eq 1 ]
echo '±'
return
end
set ahead 1
case '<*'
if [ $ahead -eq 1 ]
echo "$git_plus_minus_glyph"
return
end
set behind 1
end
end
if [ $ahead -eq 1 ]
echo "$git_plus_glyph"
else if [ $behind -eq 1 ]
echo "$git_minus_glyph"
end
end
function __bobthefish_git_ahead_verbose -S -d 'Print a more verbose ahead/behind state for the current branch'
set -l commits (command git rev-list --left-right '@{upstream}...HEAD' 2>/dev/null)
or return
set -l behind 0
set -l ahead 0
for commit in $commits
switch "$commit"
case '<*'
set behind (math $behind + 1)
case '>*'
set ahead (math $ahead + 1)
end
end
switch "$ahead $behind"
case '' # no upstream
case '0 0' # equal to upstream
return
case '* 0' # ahead of upstream
echo "$git_ahead_glyph$ahead"
case '0 *' # behind upstream
echo "$git_behind_glyph$behind"
case '*' # diverged from upstream
echo "$git_ahead_glyph$ahead$git_behind_glyph$behind"
end
end
function __bobthefish_git_dirty_verbose -S -d 'Print a more verbose dirty state for the current working tree'
set -l diff_stats (command git diff --numstat)
or return
set -l added 0
set -l removed 0
for line in $diff_stats
set -l fields (string split \t -- $line)
string match -rq '^\d+$' -- $fields[1]
and set added (math $added + $fields[1])
string match -rq '^\d+$' -- $fields[2]
and set removed (math $removed + $fields[2])
end
set -l changes "+$added/-$removed"
echo "$changes " | string replace -r '(\+0/(-0)?|/-0)' ''
end
function __bobthefish_git_stashed -S -d 'Print the stashed state for the current branch'
if [ "$theme_display_git_stashed_verbose" = yes ]
set -l stashed (command git rev-list --walk-reflogs --count refs/stash 2>/dev/null)
or return
echo -n "$git_stashed_glyph$stashed"
else
command git rev-parse --verify --quiet refs/stash >/dev/null
and echo -n "$git_stashed_glyph"
end
end
# ==============================
# Segment functions
# ==============================
@@ -1179,123 +1061,11 @@ function __bobthefish_prompt_screen -S -d 'Display the screen name'
set_color normal
end
function __bobthefish_prompt_git -S -a git_root_dir -a real_pwd -d 'Display the actual git state'
set -l dirty ''
if [ "$theme_display_git_dirty" != no ]
set -l show_dirty (command git config --bool bash.showDirtyState 2>/dev/null)
if [ "$show_dirty" != false ]
set dirty (command git diff --no-ext-diff --quiet --exit-code 2>/dev/null; or echo -n "$git_dirty_glyph")
if [ "$dirty" -a "$theme_display_git_dirty_verbose" = yes ]
set dirty "$dirty"(__bobthefish_git_dirty_verbose)
end
end
end
set -l staged (command git diff --cached --no-ext-diff --quiet --exit-code 2>/dev/null; or echo -n "$git_staged_glyph")
set -l stashed (__bobthefish_git_stashed)
set -l ahead (__bobthefish_git_ahead)
set -l new ''
if [ "$theme_display_git_untracked" != no ]
set -l show_untracked (command git config --bool bash.showUntrackedFiles 2>/dev/null)
if [ "$show_untracked" != false ]
set new (command git ls-files --other --exclude-standard --directory --no-empty-directory "$git_root_dir" 2>/dev/null)
if [ "$new" ]
set new "$git_untracked_glyph"
end
end
end
set -l flags "$dirty$staged$stashed$ahead$new"
[ "$flags" ]
and set flags " $flags"
set -l flag_colors $color_repo
if [ "$dirty" ]
set flag_colors $color_repo_dirty
else if [ "$staged" ]
set flag_colors $color_repo_staged
end
__bobthefish_path_segment $git_root_dir project
__bobthefish_start_segment $flag_colors
echo -ns (__bobthefish_git_branch) $flags ' '
set_color normal
if [ "$theme_git_worktree_support" != yes ]
set -l project_pwd (__bobthefish_project_pwd $git_root_dir $real_pwd)
if [ "$project_pwd" ]
if [ -w "$real_pwd" ]
__bobthefish_start_segment $color_path
else
__bobthefish_start_segment $color_path_nowrite
end
echo -ns $project_pwd ' '
end
return
end
set -l project_pwd (command git rev-parse --show-prefix 2>/dev/null | string trim --right --chars=/)
set -l work_dir (command git rev-parse --show-toplevel 2>/dev/null)
# only show work dir if it's a parent…
if [ "$work_dir" ]
switch $real_pwd/
case $work_dir/\*
string match "$git_root_dir*" $work_dir >/dev/null
and set work_dir (string sub -s (math 1 + (string length $git_root_dir)) $work_dir)
case \*
set -e work_dir
end
end
if [ "$project_pwd" -o "$work_dir" ]
set -l colors $color_path
if not [ -w "$real_pwd" ]
set colors $color_path_nowrite
end
__bobthefish_start_segment $colors
# handle work_dir != project dir
if [ "$work_dir" ]
set -l work_parent (__bobthefish_dirname $work_dir)
if [ "$work_parent" ]
echo -n "$work_parent/"
end
set_color normal
set_color -b $color_repo_work_tree
echo -n (__bobthefish_basename $work_dir)
set_color normal
set_color -b $colors
[ "$project_pwd" ]
and echo -n /
end
echo -ns $project_pwd ' '
function __bobthefish_prompt_git -S -a git_root_dir -a real_pwd -a repaint -d 'Display the actual git state'
if __bobthefish_git_async_enabled
__bobthefish_prompt_git_async $git_root_dir $real_pwd $repaint
else
set project_pwd $real_pwd
string match "$git_root_dir*" $project_pwd >/dev/null
and set project_pwd (string sub -s (math 1 + (string length $git_root_dir)) $project_pwd)
set project_pwd (string trim --left --chars=/ -- $project_pwd)
if [ "$project_pwd" ]
set -l colors $color_path
if not [ -w "$real_pwd" ]
set colors $color_path_nowrite
end
__bobthefish_start_segment $colors
echo -ns $project_pwd ' '
end
__bobthefish_prompt_git_sync $git_root_dir $real_pwd
end
end
@@ -1328,6 +1098,9 @@ end
function fish_prompt -d 'bobthefish, a fish theme optimized for awesome'
# Save the last status for later (do this before anything else)
set -l last_status $status
set -l git_async_repaint
set -e __bobthefish_git_async_repaint_pending
and set git_async_repaint 1
# Use a simple prompt on dumb terminals.
if [ "$TERM" = dumb ]
@@ -1382,7 +1155,7 @@ function fish_prompt -d 'bobthefish, a fish theme optimized for awesome'
case ''
__bobthefish_prompt_dir $real_pwd
case "$git_root_dir"
__bobthefish_prompt_git $git_root_dir $real_pwd
__bobthefish_prompt_git $git_root_dir $real_pwd $git_async_repaint
case "$hg_root_dir"
__bobthefish_prompt_hg $hg_root_dir $real_pwd
case "$fossil_root_dir"