feat: re-organise files dir
This commit is contained in:
281
home/.zsh/prompt.zsh
Normal file
281
home/.zsh/prompt.zsh
Normal file
@@ -0,0 +1,281 @@
|
||||
# Prompt
|
||||
(( ${+PROMPT_MIN_DURATION} )) || typeset -gi PROMPT_MIN_DURATION=2 # show duration after N seconds
|
||||
(( ${+PROMPT_FLASH_DELAY} )) || typeset -gi PROMPT_FLASH_DELAY=4 # flash prompt for N centiseconds
|
||||
|
||||
typeset -gi _dots_prompt_cmd_start=0
|
||||
typeset -gi _dots_prompt_cmd_ran=0
|
||||
typeset -gi _dots_prompt_flashing=0
|
||||
typeset -g _dots_prompt_symbol="λ"
|
||||
typeset -g _dots_prompt_base=""
|
||||
typeset -gA _dots_pc
|
||||
|
||||
_dots_init_colors() {
|
||||
if [[ "$COLORTERM" == (truecolor|24bit) ]]; then
|
||||
_dots_pc=(
|
||||
teal $'%{\e[38;2;44;180;148m%}'
|
||||
orange $'%{\e[38;2;248;140;20m%}'
|
||||
red $'%{\e[38;2;244;4;4m%}'
|
||||
grey $'%{\e[38;2;114;144;184m%}'
|
||||
)
|
||||
elif [[ "$TERM" == *256color* ]]; then
|
||||
_dots_pc=(
|
||||
teal $'%{\e[38;5;43m%}'
|
||||
orange $'%{\e[38;5;208m%}'
|
||||
red $'%{\e[38;5;196m%}'
|
||||
grey $'%{\e[38;5;103m%}'
|
||||
)
|
||||
else
|
||||
_dots_pc=(
|
||||
teal $'%{\e[36m%}'
|
||||
orange $'%{\e[33m%}'
|
||||
red $'%{\e[31m%}'
|
||||
grey $'%{\e[34m%}'
|
||||
)
|
||||
fi
|
||||
_dots_pc[reset]=$'%{\e[0m%}'
|
||||
_dots_pc[bold]=$'%{\e[1m%}'
|
||||
}
|
||||
|
||||
_dots_abbrev_path() {
|
||||
local dir="${PWD/#$HOME/~}"
|
||||
local -a parts=( "${(@s:/:)dir}" )
|
||||
local count=${#parts[@]}
|
||||
|
||||
(( count <= 3 )) && { print -r -- "$dir"; return }
|
||||
|
||||
local result=""
|
||||
local i
|
||||
for (( i=1; i <= count-3; i++ )); do
|
||||
result+="${parts[i][1]}/"
|
||||
done
|
||||
print -r -- "${result}${parts[-3]}/${parts[-2]}/${parts[-1]}"
|
||||
}
|
||||
|
||||
_dots_session() {
|
||||
[[ -n "$CODESPACE_NAME" ]] && { print -r -- "$CODESPACE_NAME"; return }
|
||||
[[ -n "$SSH_CONNECTION" || -n "$SSH_CLIENT" || -n "$SSH_TTY" ]] && { print -r -- "%n@%m"; return }
|
||||
[[ -f /.dockerenv ]] && { print -r -- "${DEVCONTAINER_ID:-$(</etc/hostname)}"; return }
|
||||
}
|
||||
|
||||
_dots_git_info_sync() {
|
||||
# Use git status --short with awk for better performance
|
||||
# Avoids shell loops and uses compiled awk for parsing
|
||||
local output
|
||||
output=$(git status --short --branch --ignore-submodules=dirty 2>/dev/null) || return
|
||||
|
||||
local branch="" ahead=0 behind=0 staged=0 unstaged=0 untracked=0
|
||||
|
||||
# Parse efficiently: branch info from first line, counts from rest
|
||||
local first_line="${output%%$'\n'*}"
|
||||
|
||||
# Extract branch from "## branch...remote [ahead N, behind M]"
|
||||
if [[ "$first_line" == "## "* ]]; then
|
||||
branch="${first_line#\#\# }"
|
||||
# Handle detached HEAD
|
||||
if [[ "$branch" == "HEAD (no branch)" || "$branch" == [0-9a-f]##* ]]; then
|
||||
branch="${branch:0:7}"
|
||||
else
|
||||
# Remove tracking info
|
||||
branch="${branch%%...*}"
|
||||
branch="${branch%% *}"
|
||||
fi
|
||||
|
||||
# Extract ahead/behind
|
||||
[[ "$first_line" =~ "ahead ([0-9]+)" ]] && ahead="${match[1]}"
|
||||
[[ "$first_line" =~ "behind ([0-9]+)" ]] && behind="${match[1]}"
|
||||
fi
|
||||
|
||||
[[ -z "$branch" ]] && return
|
||||
|
||||
# Count file status with awk (fast, no shell loops)
|
||||
local -a counts
|
||||
counts=( $(awk '
|
||||
NR == 1 { next } # Skip branch line
|
||||
/^\?\?/ { untracked++; next }
|
||||
{
|
||||
x = substr($0, 1, 1)
|
||||
y = substr($0, 2, 1)
|
||||
if (x != " " && x != "?") staged++
|
||||
if (y != " " && y != "?") unstaged++
|
||||
}
|
||||
END { print staged+0, unstaged+0, untracked+0 }
|
||||
' <<< "$output") )
|
||||
|
||||
staged=${counts[1]:-0}
|
||||
unstaged=${counts[2]:-0}
|
||||
untracked=${counts[3]:-0}
|
||||
|
||||
local info="${_dots_pc[grey]}(${branch})${_dots_pc[reset]}"
|
||||
|
||||
local dirty=""
|
||||
local sep=""
|
||||
if (( staged )); then
|
||||
dirty+="${_dots_pc[teal]}+${staged}${_dots_pc[reset]}"
|
||||
sep=" "
|
||||
fi
|
||||
if (( unstaged )); then
|
||||
dirty+="${sep}${_dots_pc[orange]}~${unstaged}${_dots_pc[reset]}"
|
||||
sep=" "
|
||||
fi
|
||||
if (( untracked )); then
|
||||
dirty+="${sep}${_dots_pc[grey]}?${untracked}${_dots_pc[reset]}"
|
||||
fi
|
||||
|
||||
local arrows=""
|
||||
sep=""
|
||||
if (( ahead )); then
|
||||
arrows+="${_dots_pc[teal]}↑${ahead}${_dots_pc[reset]}"
|
||||
sep=" "
|
||||
fi
|
||||
if (( behind )); then
|
||||
arrows+="${sep}${_dots_pc[orange]}↓${behind}${_dots_pc[reset]}"
|
||||
fi
|
||||
|
||||
if [[ -n "$dirty" || -n "$arrows" ]]; then
|
||||
info+=" "
|
||||
[[ -n "$dirty" ]] && info+="$dirty"
|
||||
[[ -n "$dirty" && -n "$arrows" ]] && info+=" "
|
||||
[[ -n "$arrows" ]] && info+="$arrows"
|
||||
fi
|
||||
|
||||
print -r -- "$info"
|
||||
}
|
||||
|
||||
# Async git info
|
||||
typeset -g _dots_git_info_result=""
|
||||
typeset -g _dots_git_info_pwd=""
|
||||
typeset -g _dots_git_async_fd=""
|
||||
|
||||
_dots_git_async_callback() {
|
||||
local fd=$1
|
||||
local result=""
|
||||
# Use sysread for efficient non-blocking read from fd
|
||||
if [[ -n "$fd" ]] && sysread -i "$fd" result 2>/dev/null; then
|
||||
result="${result%$'\n'}" # trim trailing newline
|
||||
_dots_git_info_result="$result"
|
||||
_dots_build_dots_prompt_base
|
||||
PROMPT="$_dots_prompt_base"
|
||||
zle && zle reset-prompt
|
||||
fi
|
||||
# Clean up
|
||||
exec {fd}<&-
|
||||
zle -F "$fd" 2>/dev/null
|
||||
_dots_git_async_fd=""
|
||||
}
|
||||
|
||||
_dots_git_async_start() {
|
||||
# Check if we're in a git repo
|
||||
local git_dir
|
||||
git_dir=$(git rev-parse --git-dir 2>/dev/null) || return
|
||||
|
||||
# Cancel any pending async job (reuse single worker)
|
||||
if [[ -n "$_dots_git_async_fd" ]]; then
|
||||
exec {_dots_git_async_fd}<&- 2>/dev/null
|
||||
zle -F "$_dots_git_async_fd" 2>/dev/null
|
||||
_dots_git_async_fd=""
|
||||
fi
|
||||
|
||||
# Start background job
|
||||
exec {_dots_git_async_fd}< <(
|
||||
_dots_git_info_sync
|
||||
)
|
||||
zle -F "$_dots_git_async_fd" _dots_git_async_callback
|
||||
}
|
||||
|
||||
_dots_build_dots_prompt_base() {
|
||||
local dir_path="$(_dots_abbrev_path)"
|
||||
local symbol="${_dots_pc[grey]}${_dots_prompt_symbol}${_dots_pc[reset]}"
|
||||
(( EUID == 0 )) && symbol="${_dots_pc[orange]}${_dots_pc[bold]}#${_dots_pc[reset]}"
|
||||
|
||||
local line1="${_dots_pc[teal]}${dir_path}${_dots_pc[reset]}"
|
||||
[[ -n "$_dots_git_info_result" ]] && line1+=" ${_dots_git_info_result}"
|
||||
|
||||
_dots_prompt_base=$'\n'"${line1}"$'\n'"${symbol} "
|
||||
}
|
||||
|
||||
_dots_preexec() {
|
||||
_dots_prompt_cmd_start=$EPOCHSECONDS
|
||||
_dots_prompt_cmd_ran=1
|
||||
}
|
||||
|
||||
_dots_precmd() {
|
||||
local e=$? d=0
|
||||
# Only show exit code if a command actually ran
|
||||
(( _dots_prompt_cmd_ran )) || e=0
|
||||
_dots_prompt_cmd_ran=0
|
||||
# First prompt should never show error from shell init
|
||||
[[ -z "$_dots_first_prompt" ]] && { _dots_first_prompt=1; e=0; }
|
||||
|
||||
# Build RPROMPT: time, error, host
|
||||
local rp_parts=()
|
||||
|
||||
if (( _dots_prompt_cmd_start )); then
|
||||
d=$(( EPOCHSECONDS - _dots_prompt_cmd_start ))
|
||||
_dots_prompt_cmd_start=0
|
||||
if (( d >= PROMPT_MIN_DURATION )); then
|
||||
(( d >= 60 )) && rp_parts+=("${_dots_pc[grey]}($(( d/60 ))m$(( d%60 ))s)${_dots_pc[reset]}") \
|
||||
|| rp_parts+=("${_dots_pc[grey]}(${d}s)${_dots_pc[reset]}")
|
||||
fi
|
||||
fi
|
||||
|
||||
(( e )) && rp_parts+=("${_dots_pc[red]}[${e}]${_dots_pc[reset]}")
|
||||
|
||||
local session="$(_dots_session)"
|
||||
[[ -n "$session" ]] && rp_parts+=("${_dots_pc[orange]}[${session}]${_dots_pc[reset]}")
|
||||
|
||||
RPROMPT="${(j: :)rp_parts}"
|
||||
|
||||
# On directory change, clear git info until async refreshes
|
||||
if [[ "$PWD" != "$_dots_git_info_pwd" ]]; then
|
||||
_dots_git_info_pwd="$PWD"
|
||||
_dots_git_info_result=""
|
||||
fi
|
||||
|
||||
_dots_build_dots_prompt_base
|
||||
_dots_git_async_start
|
||||
PROMPT="$_dots_prompt_base"
|
||||
}
|
||||
|
||||
|
||||
|
||||
TRAPINT() {
|
||||
# Only customize when ZLE is active (at prompt, not during command)
|
||||
if [[ -o zle ]] && [[ -o interactive ]] && (( ${+WIDGET} )); then
|
||||
if [[ -z "$BUFFER" ]] && (( ! _dots_prompt_flashing )); then
|
||||
# Empty buffer: flash the prompt symbol
|
||||
_dots_prompt_flashing=1
|
||||
local git_part=""
|
||||
[[ -n "$_dots_git_info_result" ]] && git_part=" ${_dots_git_info_result}"
|
||||
local flash_prompt=$'\n'"${_dots_pc[teal]}$(_dots_abbrev_path)${_dots_pc[reset]}${git_part}"$'\n'$'%{\e[48;2;248;140;20m\e[30m%}'"${_dots_prompt_symbol}"$' %{\e[0m%}'
|
||||
PROMPT="$flash_prompt"
|
||||
zle reset-prompt
|
||||
zselect -t $PROMPT_FLASH_DELAY
|
||||
_dots_prompt_flashing=0
|
||||
PROMPT="$_dots_prompt_base"
|
||||
zle reset-prompt
|
||||
return 0
|
||||
elif [[ -n "$BUFFER" ]]; then
|
||||
# Buffer has content: clear autosuggest, then default behavior
|
||||
zle autosuggest-clear 2>/dev/null
|
||||
fi
|
||||
fi
|
||||
# Propagate signal: use special return code -1 to let zsh handle normally
|
||||
return $((128 + ${1:-2}))
|
||||
}
|
||||
|
||||
_dots_prompt_init() {
|
||||
zmodload zsh/datetime 2>/dev/null
|
||||
zmodload zsh/zselect 2>/dev/null
|
||||
zmodload zsh/system 2>/dev/null
|
||||
_dots_init_colors
|
||||
_dots_build_dots_prompt_base
|
||||
|
||||
setopt PROMPT_SUBST EXTENDED_HISTORY INC_APPEND_HISTORY_TIME
|
||||
autoload -Uz add-zsh-hook
|
||||
add-zsh-hook preexec _dots_preexec
|
||||
add-zsh-hook precmd _dots_precmd
|
||||
add-zsh-hook chpwd _dots_build_dots_prompt_base
|
||||
|
||||
PROMPT="$_dots_prompt_base" RPROMPT=""
|
||||
}
|
||||
_dots_prompt_init
|
||||
Reference in New Issue
Block a user