# shell-prompt.zsh # Powerline-style prompt untuk Zsh di macOS Tahoe. # Diadaptasi dari konfigurasi synth-shell Bash. # Membutuhkan Nerd Font agar icon dapat tampil dengan benar. [[ -o interactive ]] || return # ----------------------------------------------------------------------------- # Konfigurasi tampilan # ----------------------------------------------------------------------------- SSP_FORMAT=(USER HOST PWD GIT PYENV TF KUBE DATE CLOCK) SSP_SEPARATOR_CHAR=$'\uE0B0' SSP_SEGMENT_PADDING=' ' SSP_MAX_PWD_CHAR=25 # Nerd Font icons. SSP_ICON_USER=$'\uf007' SSP_ICON_FOLDER=$'\uf07b' SSP_ICON_CALENDAR=$'\uf073' SSP_ICON_CLOCK=$'\uf017' SSP_ICON_APPLE=$'\uf179' SSP_ICON_LINUX=$'\uf17c' SSP_GIT_SYNCED='' SSP_GIT_AHEAD=' ▲' SSP_GIT_BEHIND=' ▼' SSP_GIT_DIVERGED=' ◆' SSP_GIT_DIRTY=' ◔' SSP_GIT_DIRTY_AHEAD=' ◔ △' SSP_GIT_DIRTY_BEHIND=' ◔ ▽' SSP_GIT_DIRTY_DIVERGED=' ◔ ◇' SSP_GIT_STASH='🗎' SSP_GIT_UPDATE_PERIOD_MINUTES=15 # color-name -> ANSI foreground/background base codes. _ssp_8bit_code() { case "$1" in default|none) echo 9 ;; black) echo 0 ;; red) echo 1 ;; green) echo 2 ;; yellow) echo 3 ;; blue) echo 4 ;; magenta|purple|pink) echo 5 ;; cyan) echo 6 ;; light-gray) echo 7 ;; dark-gray) echo 60 ;; light-red) echo 61 ;; light-green) echo 62 ;; light-yellow) echo 63 ;; light-blue) echo 64 ;; light-magenta|light-purple) echo 65 ;; light-cyan) echo 66 ;; white) echo 67 ;; *) echo 0 ;; esac } _ssp_fg_code() { local color="$1" if [[ "$color" == <-> ]] && (( color >= 0 && color < 256 )); then printf '38;5;%s' "$color" else local bit=$(_ssp_8bit_code "$color") (( bit == 9 )) && printf '39' || printf '%d' $((bit + 30)) fi } _ssp_bg_code() { local color="$1" if [[ "$color" == <-> ]] && (( color >= 0 && color < 256 )); then printf '48;5;%s' "$color" else local bit=$(_ssp_8bit_code "$color") (( bit == 9 )) && printf '49' || printf '%d' $((bit + 40)) fi } _ssp_effect_code() { case "$1" in bold|bright) echo 1 ;; dim) echo 2 ;; underline) echo 4 ;; blink) echo 5 ;; reverse) echo 7 ;; hidden) echo 8 ;; strikeout) echo 9 ;; *) echo 0 ;; esac } _ssp_ansi() { printf '%%{\033[%sm%%}' "$1" } # ----------------------------------------------------------------------------- # Warna tiap segment: foreground background effect # ----------------------------------------------------------------------------- typeset -gA SSP_COLOR_FG SSP_COLOR_BG SSP_COLOR_EFFECT SSP_COLOR_FG[USER]='black'; SSP_COLOR_BG[USER]='green'; SSP_COLOR_EFFECT[USER]='bold' SSP_COLOR_FG[HOST]='white'; SSP_COLOR_BG[HOST]='light-blue'; SSP_COLOR_EFFECT[HOST]='bold' SSP_COLOR_FG[PWD]='dark-gray'; SSP_COLOR_BG[PWD]='white'; SSP_COLOR_EFFECT[PWD]='bold' SSP_COLOR_FG[GIT]='light-gray'; SSP_COLOR_BG[GIT]='dark-gray'; SSP_COLOR_EFFECT[GIT]='bold' SSP_COLOR_FG[PYENV]='white'; SSP_COLOR_BG[PYENV]='blue'; SSP_COLOR_EFFECT[PYENV]='bold' SSP_COLOR_FG[KUBE]='white'; SSP_COLOR_BG[KUBE]='purple'; SSP_COLOR_EFFECT[KUBE]='bold' SSP_COLOR_FG[TF]='purple'; SSP_COLOR_BG[TF]='light-purple'; SSP_COLOR_EFFECT[TF]='bold' SSP_COLOR_FG[DATE]='white'; SSP_COLOR_BG[DATE]='light-purple'; SSP_COLOR_EFFECT[DATE]='bold' SSP_COLOR_FG[CLOCK]='white'; SSP_COLOR_BG[CLOCK]='black'; SSP_COLOR_EFFECT[CLOCK]='bold' SSP_COLOR_FG[INPUT]='45'; SSP_COLOR_BG[INPUT]='none'; SSP_COLOR_EFFECT[INPUT]='bold' _ssp_git_branch() { command -v git >/dev/null 2>&1 || return git rev-parse --is-inside-work-tree >/dev/null 2>&1 || return local branch branch=$(git symbolic-ref --quiet --short HEAD 2>/dev/null || git rev-parse --short HEAD 2>/dev/null) || return # Periodic fetch. macOS memakai stat -f, bukan GNU stat -c. if (( SSP_GIT_UPDATE_PERIOD_MINUTES >= 0 )); then local gitdir fetch_head last_update now elapsed gitdir=$(git rev-parse --git-dir 2>/dev/null) [[ "$gitdir" != /* ]] && gitdir="$PWD/$gitdir" fetch_head="$gitdir/FETCH_HEAD" if [[ -f "$fetch_head" ]]; then last_update=$(stat -f '%m' "$fetch_head" 2>/dev/null) now=$(date +%s) if [[ -n "$last_update" ]]; then elapsed=$(( (now - last_update) / 60 )) if (( elapsed >= SSP_GIT_UPDATE_PERIOD_MINUTES )); then (git fetch --recurse-submodules >/dev/null 2>&1 &!) fi fi fi fi local porcelain tracking dirty=false ahead=false behind=false symbol='' porcelain=$(git status --porcelain 2>/dev/null) [[ -n "$porcelain" ]] && dirty=true tracking=$(git status --porcelain -uno -b 2>/dev/null) [[ "$tracking" == *ahead* ]] && ahead=true [[ "$tracking" == *behind* ]] && behind=true if $dirty && $ahead && $behind; then symbol=$SSP_GIT_DIRTY_DIVERGED elif $dirty && $ahead; then symbol=$SSP_GIT_DIRTY_AHEAD elif $dirty && $behind; then symbol=$SSP_GIT_DIRTY_BEHIND elif $dirty; then symbol=$SSP_GIT_DIRTY elif $ahead && $behind; then symbol=$SSP_GIT_DIVERGED elif $ahead; then symbol=$SSP_GIT_AHEAD elif $behind; then symbol=$SSP_GIT_BEHIND else symbol=$SSP_GIT_SYNCED fi local stash_count stash='' tags tag='' stash_count=$(git stash list 2>/dev/null | wc -l | tr -d ' ') (( stash_count > 0 )) && stash=" ${stash_count}${SSP_GIT_STASH}" tags=$(git tag --points-at HEAD 2>/dev/null | paste -sd ',' -) [[ -n "$tags" ]] && tag=" $tags" printf '%s%s%s%s' "$branch" "$symbol" "$stash" "$tag" } _ssp_terraform() { command -v terraform >/dev/null 2>&1 || return [[ -d .terraform ]] || return terraform workspace show 2>/dev/null | tr -d '\n' } _ssp_pyenv() { if [[ -n "${CONDA_DEFAULT_ENV:-}" ]]; then printf '%s' "$CONDA_DEFAULT_ENV" elif [[ -n "${VIRTUAL_ENV:-}" ]]; then basename "$VIRTUAL_ENV" fi } _ssp_kube() { command -v kubectl >/dev/null 2>&1 || return local ctx cluster ctx=$(kubectl config current-context 2>/dev/null) || return [[ -n "$ctx" ]] || return cluster=$(kubectl config view -o "jsonpath={.contexts[?(@.name==\"$ctx\")].context.cluster}" 2>/dev/null) [[ -n "$cluster" ]] && printf '%s' "$cluster" || printf '%s' "$ctx" } _ssp_indonesian_date() { local day month hari bulan day=$(LC_ALL=C date +%A) month=$(LC_ALL=C date +%B) case "$day" in Monday) hari='Senin' ;; Tuesday) hari='Selasa' ;; Wednesday) hari='Rabu' ;; Thursday) hari='Kamis' ;; Friday) hari="Jum'at" ;; Saturday) hari='Sabtu' ;; Sunday) hari='Ahad' ;; *) hari="$day" ;; esac case "$month" in January) bulan='Januari' ;; February) bulan='Februari' ;; March) bulan='Maret' ;; April) bulan='April' ;; May) bulan='Mei' ;; June) bulan='Juni' ;; July) bulan='Juli' ;; August) bulan='Agustus' ;; September) bulan='September' ;; October) bulan='Oktober' ;; November) bulan='November' ;; December) bulan='Desember' ;; *) bulan="$month" ;; esac printf '%s, %s %s %s' "$hari" "$(date +%d)" "$bulan" "$(date +%Y)" } _ssp_short_pwd() { local p="$PWD" p=${p/#$HOME/~} if (( ${#p} > SSP_MAX_PWD_CHAR )); then printf '…%s' "${p: -$((SSP_MAX_PWD_CHAR - 1))}" else printf '%s' "$p" fi } _ssp_os_icon() { case "$(uname -s 2>/dev/null)" in Darwin) printf '%s' "$SSP_ICON_APPLE" ;; Linux) printf '%s' "$SSP_ICON_LINUX" ;; *) printf '%s' "$SSP_ICON_LINUX" ;; esac } _ssp_segment_text() { case "$1" in USER) printf '%s %s' "$SSP_ICON_USER" "$USER" ;; HOST) printf '%s %s' "$(_ssp_os_icon)" "$(hostname -s 2>/dev/null || printf '%s' "$HOST")" ;; PWD) printf '%s %s' "$SSP_ICON_FOLDER" "$(_ssp_short_pwd)" ;; GIT) _ssp_git_branch ;; PYENV) _ssp_pyenv ;; TF) _ssp_terraform ;; KUBE) _ssp_kube ;; DATE) printf '%s %s' "$SSP_ICON_CALENDAR" "$(_ssp_indonesian_date)" ;; CLOCK) printf '%s %s' "$SSP_ICON_CLOCK" "$(date +%H:%M:%S)" ;; esac } _ssp_print_segment() { local element="$1" text="$2" next_bg="$3" local fg=${SSP_COLOR_FG[$element]} local bg=${SSP_COLOR_BG[$element]} local effect=${SSP_COLOR_EFFECT[$element]} local text_code sep_code reset text_code=$(_ssp_ansi "$(_ssp_fg_code "$fg");$(_ssp_bg_code "$bg");$(_ssp_effect_code "$effect")") sep_code=$(_ssp_ansi "$(_ssp_fg_code "$bg");$(_ssp_bg_code "$next_bg")") reset=$(_ssp_ansi '0') printf '%s%s%s%s%s%s%s' \ "$text_code" "$SSP_SEGMENT_PADDING" "$text" "$SSP_SEGMENT_PADDING" \ "$sep_code" "$SSP_SEPARATOR_CHAR" "$reset" } _ssp_build_prompt() { local -a active active=() local element text i current next next_bg local built_prompt='' for element in "${SSP_FORMAT[@]}"; do # Segment opsional seperti Git/Terraform/Kubernetes boleh tidak tersedia. # Status non-zero dari segment tidak boleh menghentikan seluruh prompt builder. text=$(_ssp_segment_text "$element" 2>/dev/null) || text='' [[ -n "$text" ]] && active+=("$element:$text") done for (( i=1; i<=${#active[@]}; i++ )); do current=${active[$i]} element=${current%%:*} text=${current#*:} if (( i < ${#active[@]} )); then next=${active[$((i+1))]} next=${next%%:*} next_bg=${SSP_COLOR_BG[$next]} else next_bg='none' fi built_prompt+=$(_ssp_print_segment "$element" "$text" "$next_bg") || true done local input_code reset input_code=$(_ssp_ansi "$(_ssp_fg_code "${SSP_COLOR_FG[INPUT]}");$(_ssp_bg_code "${SSP_COLOR_BG[INPUT]}");$(_ssp_effect_code "${SSP_COLOR_EFFECT[INPUT]}")") reset=$(_ssp_ansi '0') # Tetapkan eksplisit sebagai parameter global Zsh. typeset -g PROMPT=$'\n╭'"${built_prompt}"$'\n╰>'"${input_code}"'$ '"${reset}" # Judul terminal. print -Pn "\e]0;${USER}@$(hostname -s): ${PWD}\a" return 0 } # Hook native Zsh, menggantikan PROMPT_COMMAND milik Bash. autoload -Uz add-zsh-hook add-zsh-hook precmd _ssp_build_prompt # Bangun prompt pertama kali saat file di-source. _ssp_build_prompt