From b19de653bf5d917360c72402f06ce52f0c75200a Mon Sep 17 00:00:00 2001 From: Ste Vaidis Date: Mon, 21 Sep 2026 17:04:56 +0300 Subject: [PATCH] Add prompt.ksh --- prompt.ksh | 308 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 prompt.ksh diff --git a/prompt.ksh b/prompt.ksh new file mode 100644 index 0000000..48ae196 --- /dev/null +++ b/prompt.ksh @@ -0,0 +1,308 @@ +# ============================================================ +# Custom OpenBSD ksh prompt +# ============================================================ + + +# ------------------------------------------------------------ +# Colors +# ------------------------------------------------------------ + +RESET="$(printf '\033[0m')" + +RED="$(printf '\033[31m')" +GREEN="$(printf '\033[32m')" +YELLOW="$(printf '\033[33m')" +BLUE="$(printf '\033[34m')" +MAGENTA="$(printf '\033[35m')" +CYAN="$(printf '\033[36m')" +WHITE="$(printf '\033[37m')" + +# 256-color dark gray +DARK_GRAY="$(printf '\033[38;5;238m')" + +BOLD_RED="$(printf '\033[1;31m')" +BOLD_GREEN="$(printf '\033[1;32m')" +BOLD_YELLOW="$(printf '\033[1;33m')" +BOLD_BLUE="$(printf '\033[1;34m')" +BOLD_CYAN="$(printf '\033[1;36m')" + + +# ------------------------------------------------------------ +# User / root +# ------------------------------------------------------------ + +PROMPT_USER="${USER:-$(id -un)}" + +if [ "$(id -u)" -eq 0 ]; then + USER_COLOR="$BOLD_RED" + PROMPT_CHAR="#" +else + USER_COLOR="$BOLD_GREEN" + PROMPT_CHAR="$" +fi + + +# ------------------------------------------------------------ +# Hostname +# ------------------------------------------------------------ + +PROMPT_HOST="$(hostname)" +PROMPT_HOST="${PROMPT_HOST%%.*}" + + +# ------------------------------------------------------------ +# Git +# +# Examples: +# +# git:main ✓ +# git:feature/auth +2 ~3 ?1 +# git:main ↑2 ↓1 +# +# + = staged +# ~ = modified +# ? = untracked +# ↑ = commits ahead of upstream +# ↓ = commits behind upstream +# ------------------------------------------------------------ + +git_prompt() +{ + typeset branch + typeset counts + typeset staged modified untracked + typeset upstream + typeset ahead behind + + # Git must exist. + whence git >/dev/null 2>&1 || return + + # We must be inside a Git repository. + git rev-parse --is-inside-work-tree >/dev/null 2>&1 || return + + # Current branch. + branch="$(git symbolic-ref --quiet --short HEAD 2>/dev/null)" + + # Detached HEAD. + if [ -z "$branch" ]; then + branch="@$(git rev-parse --short HEAD 2>/dev/null)" + fi + + # Count staged, modified and untracked files. + counts="$( + git status --porcelain 2>/dev/null | + awk ' + { + index_status = substr($0, 1, 1) + work_status = substr($0, 2, 1) + + if (index_status == "?" && work_status == "?") { + untracked++ + } + else { + if (index_status != " " && index_status != "?") + staged++ + + if (work_status != " " && work_status != "?") + modified++ + } + } + + END { + printf "%d %d %d", staged, modified, untracked + } + ' + )" + + set -- $counts + + staged="${1:-0}" + modified="${2:-0}" + untracked="${3:-0}" + + ahead=0 + behind=0 + + # Compare with upstream branch, if one exists. + upstream="$( + git rev-list \ + --left-right \ + --count \ + 'HEAD...@{upstream}' \ + 2>/dev/null + )" + + if [ -n "$upstream" ]; then + set -- $upstream + + ahead="${1:-0}" + behind="${2:-0}" + fi + + # Branch name. + print -nr -- " ${MAGENTA}git:${CYAN}${branch}${RESET}" + + # Working tree state. + if [ "$staged" -eq 0 ] && + [ "$modified" -eq 0 ] && + [ "$untracked" -eq 0 ]; then + + print -nr -- " ${GREEN}✓${RESET}" + + else + + if [ "$staged" -gt 0 ]; then + print -nr -- " ${GREEN}+${staged}${RESET}" + fi + + if [ "$modified" -gt 0 ]; then + print -nr -- " ${YELLOW}~${modified}${RESET}" + fi + + if [ "$untracked" -gt 0 ]; then + print -nr -- " ${CYAN}?${untracked}${RESET}" + fi + fi + + # Ahead of upstream. + if [ "$ahead" -gt 0 ]; then + print -nr -- " ${GREEN}↑${ahead}${RESET}" + fi + + # Behind upstream. + if [ "$behind" -gt 0 ]; then + print -nr -- " ${RED}↓${behind}${RESET}" + fi +} + + +# ------------------------------------------------------------ +# Python virtual environment +# +# Example: +# +# py:.venv +# ------------------------------------------------------------ + +venv_prompt() +{ + typeset venv + + if [ -n "${VIRTUAL_ENV:-}" ]; then + + venv="${VIRTUAL_ENV##*/}" + + print -nr -- \ + " ${BLUE}py:${venv}${RESET}" + fi +} + + +# ------------------------------------------------------------ +# SSH indicator +# +# Normal SSH sessions have SSH_* environment variables. +# +# "su -" removes those variables, so who -m is used as a +# fallback to determine whether this terminal originated from +# a remote login. +# ------------------------------------------------------------ + +ssh_prompt() +{ + if [ -n "${SSH_CONNECTION:-}" ] || + [ -n "${SSH_CLIENT:-}" ] || + [ -n "${SSH_TTY:-}" ]; then + + print -nr -- \ + " ${DARK_GRAY}[SSH]${RESET}" + + return + fi + + # Fallback after "su -". + if who -m 2>/dev/null | grep -q '('; then + + print -nr -- \ + " ${DARK_GRAY}[SSH]${RESET}" + fi +} + + +# ------------------------------------------------------------ +# Build prompt +# ------------------------------------------------------------ + +build_prompt() +{ + typeset exit_code="$1" + typeset prompt_dir + + prompt_dir="$PWD" + + # Replace $HOME with ~. + case "$prompt_dir" in + + "$HOME") + prompt_dir="~" + ;; + + "$HOME"/*) + prompt_dir="~${prompt_dir#$HOME}" + ;; + esac + + + # -------------------------------------------------------- + # First line + # -------------------------------------------------------- + + # user@hostname + print -nr -- \ + "${USER_COLOR}${PROMPT_USER}@${PROMPT_HOST}${RESET}" + + # Current directory + print -nr -- \ + " ${BOLD_BLUE}${prompt_dir}${RESET}" + + # Git information + git_prompt + + # Python virtual environment + venv_prompt + + # SSH indicator + ssh_prompt + + # Previous command failed. + if [ "$exit_code" -ne 0 ]; then + + print -nr -- \ + " ${BOLD_RED}[exit:${exit_code}]${RESET}" + fi + + + # -------------------------------------------------------- + # Second line + # + # Root: + # # + # + # Normal user: + # $ + # -------------------------------------------------------- + + print -nr -- " +${USER_COLOR}${PROMPT_CHAR}${RESET} " +} + + +# Don't let Python virtualenv modify PS1 itself. +export VIRTUAL_ENV_DISABLE_PROMPT=1 + + +# ------------------------------------------------------------ +# Enable prompt +# ------------------------------------------------------------ + +PS1='$(build_prompt $?)'