Zsh Prompt

This commit is contained in:
MangoPig
2026-06-01 12:12:41 +01:00
parent b41dcbac2a
commit 84100a85d1
5 changed files with 253 additions and 25 deletions

View File

@@ -21,23 +21,3 @@ clean:
update:
git -C '{{project_root}}' pull origin main
just --justfile '{{project_root}}/Justfile' setup all
# Run distro test containers.
test-ubuntu:
echo "Ubuntu Test"
docker run -it --rm -e TERM=xterm-256color -v '{{project_root}}':/root/dotfiles ubuntu:latest \
bash -c "export DEBIAN_FRONTEND=noninteractive && \
apt-get update && \
apt-get install -y sudo git make curl && \
cd /root/dotfiles && \
make setup"
test-arch:
echo "Spawning Arch Container..."
docker run -it --rm -e TERM=xterm-256color -v '{{project_root}}':/root/dotfiles archlinux:latest \
bash -c "pacman -Sy --noconfirm base-devel git make sudo && cd /root/dotfiles && make setup"
test-fedora:
echo "Spawning Fedora Container..."
docker run -it --rm -e TERM=xterm-256color -v '{{project_root}}':/root/dotfiles fedora:latest \
bash -c "dnf install -y git make sudo curl which passwd procps-ng && cd /root/dotfiles && make setup"

View File

@@ -6,5 +6,8 @@ SCRIPTS_DIR := ./Scripts
all: setup
# Bootstrap entrypoint for first-run setup.
# This intentionally keeps Make lightweight: base packages, then restow dotfiles.
setup:
bash $(SCRIPTS_DIR)/setup.sh
bash $(SCRIPTS_DIR)/base.sh
stow --dir=. -D Zsh --target="$$HOME" 2>/dev/null || true
stow --dir=. Zsh --target="$$HOME"

View File

@@ -16,7 +16,7 @@ alias grep="grep --color=auto"
alias src="source ~/.zshrc && echo '🔄 Reloaded .zshrc'"
alias cat="bat"
# alias cat="bat"
alias fd="fd"
alias tree="eza --icons -T --git-ignore"

245
Zsh/.zsh_prompt Normal file
View File

@@ -0,0 +1,245 @@
# Prompt styling and context
autoload -Uz add-zsh-hook vcs_info
zstyle ':vcs_info:*' enable git
zstyle ':vcs_info:git:*' formats ' %b'
typeset -g PROMPT_SHOW_SPACER=0
typeset -gA PROMPT_PROJECT_MATCH_CACHE
prompt_preexec() {
case "$1" in
clear|clear\ *|reset|reset\ *)
PROMPT_SHOW_SPACER=0
;;
*)
PROMPT_SHOW_SPACER=1
;;
esac
}
get_command_version() {
local command_name="$1"
local binary_path=""
local raw_version=""
binary_path="$(whence -p "$command_name" 2>/dev/null || true)"
[ -n "$binary_path" ] && [ -x "$binary_path" ] || return 0
case "$command_name" in
python)
raw_version="$($binary_path --version 2>&1 || true)"
raw_version="${raw_version#Python }"
;;
node)
raw_version="$($binary_path -v 2>/dev/null || true)"
raw_version="${raw_version#v}"
;;
go)
raw_version="$($binary_path version 2>/dev/null || true)"
raw_version="${raw_version#go version go}"
raw_version="${raw_version%% *}"
;;
rustc)
raw_version="$($binary_path -V 2>/dev/null || true)"
raw_version="${raw_version#rustc }"
raw_version="${raw_version%% *}"
;;
*)
return 0
;;
esac
[ -n "$raw_version" ] || return 0
print -r -- "$raw_version"
}
get_project_root() {
command git rev-parse --show-toplevel 2>/dev/null || print -r -- "$PWD"
}
project_has_pattern() {
local project_root=""
local cache_key=""
local pattern
local matches=()
project_root="$(get_project_root)"
cache_key="${project_root}::${(j:|:)@}"
if [[ -n ${PROMPT_PROJECT_MATCH_CACHE[$cache_key]+x} ]]; then
[[ ${PROMPT_PROJECT_MATCH_CACHE[$cache_key]} == 1 ]]
return
fi
for pattern in "$@"; do
matches=("${project_root}"/${~pattern}(N))
if [ "${#matches[@]}" -gt 0 ]; then
PROMPT_PROJECT_MATCH_CACHE[$cache_key]=1
return 0
fi
done
PROMPT_PROJECT_MATCH_CACHE[$cache_key]=0
return 1
}
build_git_status_segment() {
local git_status_output=""
local line=""
local staged=0
local unstaged=0
local untracked=0
local status_parts=()
command git rev-parse --is-inside-work-tree >/dev/null 2>&1 || return 0
git_status_output="$(command git status --porcelain 2>/dev/null || true)"
if [ -z "$git_status_output" ]; then
print -r -- "%F{green}✓ clean%f"
return 0
fi
while IFS= read -r line; do
[ -n "$line" ] || continue
if [ "${line:0:2}" = "??" ]; then
((untracked++))
continue
fi
[ "${line:0:1}" = " " ] || ((staged++))
[ "${line:1:1}" = " " ] || ((unstaged++))
done <<< "$git_status_output"
[ "$staged" -eq 0 ] || status_parts+=("+$staged")
[ "$unstaged" -eq 0 ] || status_parts+=("~$unstaged")
[ "$untracked" -eq 0 ] || status_parts+=("?$untracked")
if [ "${#status_parts[@]}" -eq 0 ]; then
print -r -- "%F{green}✓ clean%f"
else
print -r -- "%F{yellow}${(j: :)status_parts}%f"
fi
}
build_prompt_separator_line() {
local last_status="$1"
local line_width="${COLUMNS:-80}"
local separator_line=""
local color_prefix=""
local color_suffix=""
local i=0
if [ "$line_width" -lt 1 ]; then
line_width=1
fi
if [ "$last_status" -ne 0 ]; then
color_prefix="%F{red}"
color_suffix="%f"
fi
while [ "$i" -lt "$line_width" ]; do
separator_line+="─"
((i++))
done
print -r -- "${color_prefix}${separator_line}${color_suffix}"
}
build_prompt() {
local last_status=$?
local git_segment=""
local git_status_segment=""
local env_segment=""
local project_symbols=()
local tool_versions=()
local python_version=""
local node_version=""
local go_version=""
local rust_version=""
local context_line=""
local newline=$'\n'
local prompt_spacer=""
local separator="::"
vcs_info
git_segment="${vcs_info_msg_0_}"
git_status_segment="$(build_git_status_segment)"
if [ -n "${CONDA_DEFAULT_ENV:-}" ]; then
env_segment=" ${CONDA_DEFAULT_ENV}"
elif [ -n "${DIRENV_DIR:-}" ] || [ -n "${DIRENV_FILE:-}" ]; then
env_segment=" direnv"
fi
if project_has_pattern '**/docker-compose*.yml' '**/docker-compose*.yaml' '**/compose*.yml' '**/compose*.yaml'; then
project_symbols+=("%F{blue}%f")
fi
if project_has_pattern '**/Makefile' '**/makefile' '**/GNUmakefile' '**/Justfile' '**/justfile' '**/.justfile'; then
project_symbols+=("%F{yellow}%f")
fi
if project_has_pattern '**/pyproject.toml' '**/requirements.txt' '**/.python-version' '**/setup.py'; then
python_version="$(get_command_version python)"
[ -z "$python_version" ] || tool_versions+=("%F{magenta} ${python_version}%f")
fi
if project_has_pattern '**/package.json' '**/.nvmrc' '**/pnpm-workspace.yaml' '**/yarn.lock' '**/package-lock.json'; then
node_version="$(get_command_version node)"
[ -z "$node_version" ] || tool_versions+=("%F{green} ${node_version}%f")
fi
if project_has_pattern '**/go.mod' '**/go.work'; then
go_version="$(get_command_version go)"
[ -z "$go_version" ] || tool_versions+=("%F{cyan} ${go_version}%f")
fi
if project_has_pattern '**/Cargo.toml' '**/Cargo.lock' '**/rust-toolchain.toml' '**/rust-toolchain'; then
rust_version="$(get_command_version rustc)"
[ -z "$rust_version" ] || tool_versions+=("%F{yellow} ${rust_version}%f")
fi
if [ -n "$git_segment" ]; then
context_line="%F{cyan}${git_segment}%f"
[ -z "$git_status_segment" ] || context_line+=" ${git_status_segment}"
fi
if [ -n "$env_segment" ]; then
[ -z "$context_line" ] || context_line+=" %F{240}·%f "
context_line+="%F{green}${env_segment}%f"
fi
if [ "${#project_symbols[@]}" -gt 0 ]; then
[ -z "$context_line" ] || context_line+=" %F{240}·%f "
context_line+="${(j: :)project_symbols}"
fi
if [ "${#tool_versions[@]}" -gt 0 ]; then
[ -z "$context_line" ] || context_line+=" %F{240}·%f "
context_line+="${(j: :)tool_versions}"
fi
if [ -n "$context_line" ]; then
context_line+="${newline}"
fi
if [ "$last_status" -ne 0 ]; then
separator="%F{red}::%f"
fi
if [ "$PROMPT_SHOW_SPACER" -eq 1 ]; then
prompt_spacer="$(build_prompt_separator_line "$last_status")${newline}"
fi
PROMPT="${prompt_spacer}${context_line}%B%~%b ${separator} "
RPROMPT="%n@%m"
}
add-zsh-hook preexec prompt_preexec
add-zsh-hook precmd build_prompt
build_prompt

View File

@@ -6,9 +6,6 @@ export ZSH="$HOME/.oh-my-zsh"
# Programming Languages Root
export PROG_DIR="$HOME/.programming"
PROMPT="%B%~%b :: "
RPROMPT="%n@%m"
# Plugins
plugins=(git zsh-syntax-highlighting zsh-autosuggestions sudo rclone rust nvm golang conda pyenv)
@@ -78,3 +75,6 @@ fi
if command -v direnv >/dev/null 2>&1; then
eval "$(direnv hook zsh)"
fi
# Prompt Styling
[ -f ~/.zsh_prompt ] && source ~/.zsh_prompt