Created
March 22, 2024 23:27
-
-
Save fbender/9cea193ca5a5143f1764acf6452b118a to your computer and use it in GitHub Desktop.
Personal ZSH theme based on steeef from official OMZ repository, heavily modified and quite verbose (WIP)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
### custom Oh-My-ZSH theme by fbender | |
# | |
# verbose theme based on "steeef" from official omz repository | |
# - vcs_info reduced to show type, name, and branch (if available) of repository only | |
# - built-in slightly modified "timer" plugin from official omz repository | |
# - extended Python virtual environment logic, i.a. adding conda support | |
# - multi-line input (PS2) and debug (PS4) formatting | |
# - more and different colors, date and time information, running job info, and more | |
# | |
# recommended setopts: EXTENDED_HISTORY, RM_STAR_WAIT, CORRECT, CORRECT_ALL | |
# | |
# See: https://github.com/ohmyzsh/ohmyzsh/wiki/Customization#overriding-and-adding-themes | |
### VAR definitions/defaults ### | |
#TIMER_FORMAT | |
#TIMER_PRECISION | |
#TIMER_THRESHOLD | |
#ZSH_THEME_PREFIX_DATE="%F{236}GMT%D{%j} [%D{%F}]%f " | |
#ZSH_THEME_PROMPT_SIGN # root #, privileged highlighted, other $ | |
#ZSH_THEME_PROMPT_SIGN_NEWLINE="true" | |
#ZSH_THEME_PROMPT_SIGN_PREFIX="" | |
#ZSH_THEME_PROMPT_SIGN_SUFFIX=" " | |
#ZSH_THEME_PROMPT_DEBUG_PREFIX="" | |
#ZSH_THEME_PROMPT_DEBUG_SUFFIX=" " | |
#ZSH_THEME_INPUT_PREFIX="%F{220}" | |
#ZSH_THEME_INPUT_SUFFIX="%f" | |
#ZSH_THEME_PYTHONICENV_PREFIX="🐍 " | |
#ZSH_THEME_PYTHONICENV_ADJOIN=" " | |
#ZSH_THEME_PYTHONICENV_SUFFIX="" | |
#ZSH_THEME_VIRTUALENV_PREFIX="❲ⓥ " | |
#ZSH_THEME_VIRTUALENV_SUFFIX="❳" | |
#ZSH_THEME_CONDAENV_PREFIX="❲ⓒ " | |
#ZSH_THEME_CONDAENV_SUFFIX="❳" | |
#ZSH_THEME_CMDERR_MSG='%(?.. ➥ FAIL: %? )' | |
#ZSH_THEME_CMDERR_FMT_PREFIX="%F{255}%K{red}" | |
#ZSH_THEME_CMDERR_FMT_SUFFIX="%k%f" | |
# use extended color palette if available | |
if [[ $terminfo[colors] -ge 256 ]] | |
then | |
local turquoise="%F{81}" | |
local orange="%F{166}" | |
local gold="%F{220}" | |
local dandelion="%F{222}" | |
local purple="%F{135}" | |
local tan="%F{130}" | |
local hotpink="%F{161}" | |
local keylime="%F{193}" | |
local limegreen="%F{118}" | |
local lemongreen="%F{106}" #148 alt | |
local charcoal="%F{236}" | |
local gray="%F{8}" | |
local white="%F{15}" | |
else | |
local turquoise="%F{cyan}" | |
local orange="%F{yellow}" | |
local gold="%F{yellow}" | |
local dandelion="%F{yellow}" | |
local purple="%F{magenta}" | |
local tan="%F{red}" | |
local hotpink="%F{red}" | |
local keylime="%F{green}" | |
local limegreen="%F{green}" | |
local lemongreen="%F{green}" | |
local charcoal="%F{7}" | |
local gray="%F{7}" | |
local white="%F{white}" | |
fi | |
### configuration of 3rd party tools ### | |
# prevent Python virtual_env to modify prompt itself | |
export VIRTUAL_ENV_DISABLE_PROMPT=1 | |
# helper function to change built-in prompt modification behaviour of conda for user | |
function conda_changeps1 { | |
# this function only works after conda has been initalized for the current shell | |
local changeps1="false" | |
if [[ -n "$1" && "$1" = "true" ]] # adding this may be a security risk: || $1 -eq true ]] | |
then | |
changeps1="true" | |
fi | |
conda config --system --set changeps1 "$changeps1" | |
} | |
# helper function to automatically disable conda prompt modification, since we have our own solution | |
function stop_conda_prompt { | |
# this check only works after conda has been initialized for the current shell | |
if [[ ! -z "$CONDA_PROMPT_MODIFIER" ]] | |
then | |
conda_changeps1 "false" | |
echo "Removed CONDA built-in PS1 modification. Reloading shell ..." | |
omz reload | |
fi | |
} | |
### prompt helper functions ### | |
# return string without formatting for counting without escape sequences, ref. https://stackoverflow.com/a/10564427/1331956 | |
function remove_formatting { | |
local orig=$1 | |
local zero='%([BSUbfksu]|([FK]|){*})' | |
echo ${(S%%)orig//$~zero/} | |
} | |
# return Python VIRUTAL_ENV and CONDA environment information if set | |
function pythonic_env_info { | |
# theme modifiers with default values | |
local ENV_PREFIX="${ZSH_THEME_PYTHONICENV_PREFIX:-❲🐍 }" | |
local ENV_ADJOIN="${ZSH_THEME_PYTHONICENV_ADJOIN:- }" | |
local ENV_SUFFIX="${ZSH_THEME_PYTHONICENV_SUFFIX:-❳}" | |
local VIRTUALENV_PREFIX="${ZSH_THEME_VIRTUALENV_PREFIX:-ⓥ }" | |
local VIRTUALENV_SUFFIX="${ZSH_THEME_VIRTUALENV_SUFFIX:-}" | |
local CONDAENV_PREFIX="${ZSH_THEME_CONDAENV_PREFIX:-ⓒ }" | |
local CONDAENV_SUFFIX="${ZSH_THEME_CONDAENV_SUFFIX}" | |
# initialize list of environments | |
local ENV_LIST=() # empty array | |
# virtual_env, code adapted from virtualenv plugin | |
[[ -n ${VIRTUAL_ENV} ]] && ENV_LIST+=${VIRTUALENV_PREFIX}${VIRTUAL_ENV:t:gs/%/%%}${VIRTUALENV_SUFFIX} | |
# original steeef code: [ $VIRTUAL_ENV ] && echo '('%F{blue}`basename $VIRTUAL_ENV`%f') ' | |
# conda env | |
[[ -n ${CONDA_DEFAULT_ENV} ]] && ENV_LIST+=${CONDAENV_PREFIX}${CONDA_DEFAULT_ENV}${CONDAENV_SUFFIX} | |
# return env info only if any is set | |
[[ ${#ENV_LIST[@]} -gt 0 ]] && echo "${ENV_PREFIX}${(pj.$ENV_ADJOIN.)ENV_LIST}${ENV_SUFFIX}" | |
} | |
### timer.plugin.sh code START, modified ### | |
zmodload zsh/datetime | |
__timer_current_time() { | |
#zmodload zsh/datetime #TODO: check if can be removed? | |
echo $EPOCHREALTIME | |
} | |
__timer_format_duration() { | |
local mins=$(printf '%.0f' $(($1 / 60))) | |
local secs=$(printf "%.${TIMER_PRECISION:-1}f" $(($1 - 60 * mins))) | |
local duration_str=$(echo "${mins}m${secs}s") | |
local format="${TIMER_FORMAT:-/%d}" | |
echo "${format//\%d/${duration_str#0m}}" | |
} | |
__timer_save_time_preexec() { | |
__timer_cmd_start_time=$(__timer_current_time) | |
} | |
# removed __timer_display_timer_precmd as it's unused and modified code included with new hook funciton directly | |
# hook registration removed since we want to control output by ourselves | |
### vcs_info configuration ### | |
# load vcs_info functions | |
autoload -Uz vcs_info | |
# enable VCS systems you use | |
#zstyle ':vcs_info:*' enable git svn | |
#zstyle ':vcs_info:*' disable git hg | |
zstyle ':vcs_info:*' use-simple true | |
# set formats | |
# %s - vcs system | |
# %r - repository name | |
# %b - branch name | |
# %a - action (e.g. rebase-i) | |
local FMT_VCSINFO_PREFIX=" %{$gray%}❲%s/%r" | |
local FMT_VCSINFO_SUFFIX="%{$gray%}❳%f" | |
local FMT_VCSINFO_BRANCH=" ⌥%{$turquoise%}%%U%b%%u%f" | |
local FMT_VCSINFO_ACTION=" ❮%{$hotpink%}%a%f❯" | |
zstyle ':vcs_info:*:prompt:*' actionformats "${FMT_VCSINFO_PREFIX}${FMT_VCSINFO_BRANCH}${FMT_VCSINFO_SUFFIX}${FMT_VCSINFO_ACTION}" | |
zstyle ':vcs_info:*:prompt:*' formats "${FMT_VCSINFO_PREFIX}${FMT_VCSINFO_BRANCH}${FMT_VCSINFO_SUFFIX}" | |
zstyle ':vcs_info:*:prompt:*' nvcsformats "" | |
### hooks ### | |
# before displaying prompt | |
function fbender_precmd { | |
# save error code if previous command failed | |
local CMD_ERR=$(print -P ${ZSH_THEME_CMDERR_MSG:-'%(?.. ➥ FAIL: %? )'}) | |
# adapted "timer" plugin __timer_display_timer_precmd function from official omz repository | |
if [ -n "${__timer_cmd_start_time}" ] | |
then | |
local cmd_end_time=$(__timer_current_time) | |
local tdiff=$((cmd_end_time - __timer_cmd_start_time)) | |
unset __timer_cmd_start_time | |
if [[ -z "${TIMER_THRESHOLD}" || ${tdiff} -ge "${TIMER_THRESHOLD}" ]] | |
then | |
TIMER_FORMAT=${TIMER_FORMAT:- /⏳ %d} | |
local CMD_EXEC_TIME=$(__timer_format_duration ${tdiff}) | |
fi | |
fi | |
# declare initial padding offset | |
local FMT_PRECMD_OFFSET=$(($COLUMNS - 1)) # emojis need extra space thus substract 1 for the hourglass | |
# if previous command failed, substract error code length from offset and format fail message | |
if [[ -n "$CMD_ERR" ]] | |
then | |
let "FMT_PRECMD_OFFSET -= ${#CMD_ERR}" | |
local FMT_CMDERR_OUT=$(print -P ${ZSH_THEME_CMDERR_FMT_PREFIX:-"%{$white%}%K{red}"}${CMD_ERR}${ZSH_THEME_CMDERR_FMT_SUFFIX:-"%k%f"}) | |
fi | |
# prepare time + timer info and adjust offset for proper right-alignment | |
if [[ -n "$CMD_EXEC_TIME" ]] | |
then | |
local CURR_DATE=$(print -P '%D{%F}') | |
#print $PROMPT_LAST_DATE $CURR_DATE 1>&2 | |
# add date to show with time if date has changed | |
if [[ $PROMPT_LAST_DATE -ne $CURR_DATE ]] | |
then | |
local FMT_CMD_TIME_RAW="%{$charcoal%}[%{$hotpink%}${CURR_DATE}%{$charcoal%} %*]${CMD_EXEC_TIME}%f" | |
else | |
local FMT_CMD_TIME_RAW="%{$charcoal%}[%*]${CMD_EXEC_TIME}%f" | |
fi | |
local FMT_CMD_TIME=$(print -P $FMT_CMD_TIME_RAW) | |
let "FMT_PRECMD_OFFSET += ${#FMT_CMD_TIME} - ${#$(remove_formatting $FMT_CMD_TIME_RAW)}" | |
fi | |
# print pre-prompt line with info from previous command execution (or empty line if there was no previous command) | |
local FMT_PRECMD_SPEC="%s%${FMT_PRECMD_OFFSET}s\n" | |
printf $FMT_PRECMD_SPEC "$FMT_CMDERR_OUT" "$FMT_CMD_TIME" | |
# for alternate approach for left + right aligned status line ref. https://superuser.com/a/974942 | |
# gather vcs info for 'prompt' context | |
vcs_info 'prompt' | |
# remember current date for use in preexec hook | |
export PROMPT_LAST_DATE=$(print -P '%D{%F}') | |
} | |
# before writing to history | |
function fbender_addhistory { | |
local CURR_DATE=$(print -P '%D{%F}') | |
# re-print mininmal command prompt if date has changed | |
if [[ $PROMPT_LAST_DATE -ne $CURR_DATE ]] | |
then | |
# "zle reset-prompt" unfortunately does not work here, consider https://superuser.com/a/1029103 | |
print -P "%{$hotpink%}+# date changed to ${FMT_PROMPT_PREFIX_DATE}" | |
PROMPT_LAST_DATE=$CURR_DATE | |
fi | |
} | |
# before executing a command | |
function fbender_preexec { | |
# reset color of input, ref. https://github.com/ohmyzsh/ohmyzsh/wiki/External-themes | |
local FMT_INPUT_SUFFIX=${ZSH_THEME_INPUT_SUFFIX:-"%f"} | |
print -nP '$FMT_INPUT_SUFFIX' | |
# record current time before command execution | |
__timer_save_time_preexec | |
} | |
# after changing directory | |
function fbender_chpwd { | |
} | |
# register hooks | |
autoload -U add-zsh-hook | |
add-zsh-hook precmd fbender_precmd | |
add-zsh-hook zshaddhistory fbender_addhistory | |
add-zsh-hook preexec fbender_preexec | |
add-zsh-hook chpwd fbender_chpwd | |
### the prompt ### | |
setopt prompt_subst | |
# prompt elements with formatting | |
local FMT_PROMPT_PREFIX_DATE=${ZSH_THEME_PREFIX_DATE:-"%{$charcoal%}GMT%D{%j} [%D{%F}]%f "} | |
local FMT_PROMPT_USER="%{$purple%}%n%f" | |
local FMT_PROMPT_JOBS="%(1j. running %U%j job%(2j.s.)%u.)" | |
local FMT_PROMPT_HOST=" @ %{$tan%}%m%f${SSH_TTY:+📟 }" | |
local FMT_PROMPT_PWD=" in %B%F{yellow}%9~%f%b " | |
local FMT_PROMPT_ENV_INFO="%{$gray%}$(pythonic_env_info)%f " | |
# Add %NN<…< before "%~" to cut path (+ remainder) on left after NN characters. NN=90 | |
# Alternatively, %N~ cuts i > N path elements. | |
# Newline needs "POSIX quote", ref. §5.1.3 at https://zsh.sourceforge.io/Guide/zshguide05.html | |
if [[ -n "${ZSH_THEME_PROMPT_SIGN_NEWLINE}" && "${ZSH_THEME_PROMPT_SIGN_NEWLINE}" = "false" ]] # adding this may be a security risk: || ${ZSH_THEME_PROMPT_SIGN_NEWLINE} -eq true ]] | |
then | |
local FMT_PROMPT_SIGN_NEWLINE=$'' # just show nothing if disabled | |
else | |
local FMT_PROMPT_SIGN_NEWLINE=$'\n%{\r%}' | |
fi | |
# show different prompt character for admin users (macOS `id` doesn't seem to have -z option) | |
if id -Gn | tr ' ' '\n' | grep -qxF 'admin' | |
then | |
local FMT_PROMPT_SIGN=$'%S%F{magenta}%#%f%s' | |
else | |
local FMT_PROMPT_SIGN=$'%(!.%S%F{magenta}\#%f%s.\$)' | |
fi | |
# misc. formatting | |
local FMT_PROMPT_SIGN_PREFIX=${ZSH_THEME_PROMPT_SIGN_PREFIX:-"%F{green}"} | |
local FMT_PROMPT_SIGN_SUFFIX=${ZSH_THEME_PROMPT_SIGN_SUFFIX:-"%f "} | |
local FMT_INPUT_PREFIX=${ZSH_THEME_INPUT_PREFIX:-"%{$keylime%}"} | |
# set the prompt | |
PROMPT='\ | |
${FMT_PROMPT_PREFIX_DATE}\ | |
${FMT_PROMPT_USER}\ | |
${FMT_PROMPT_JOBS}\ | |
${FMT_PROMPT_HOST}\ | |
${FMT_PROMPT_PWD}\ | |
${vcs_info_msg_0_}\ | |
${FMT_PROMPT_SIGN_NEWLINE}\ | |
${FMT_PROMPT_ENV_INFO}\ | |
${FMT_PROMPT_SIGN_PREFIX}\ | |
${FMT_PROMPT_SIGN}\ | |
${FMT_PROMPT_SIGN_SUFFIX}\ | |
${FMT_INPUT_PREFIX}\ | |
' | |
# show python environment in RPROMPT | |
#RPROMPT='%f$(pythonic_env_info)' | |
# colorize multiline input indicators the same way | |
PROMPT2="\ | |
${FMT_PROMPT_SIGN_PREFIX}\ | |
%S%_ %s❭\ | |
${FMT_PROMPT_SIGN_SUFFIX}\ | |
${FMT_INPUT_PREFIX}\ | |
" | |
# ⤷ %(%^.%^ .)❯❱❭≫\ | |
# add info and formatting for debugging statements | |
PROMPT4="\ | |
${ZSH_THEME_PROMPT_DEBUG_PREFIX:-'%{$hotpink%}'}\ | |
+# %B%1N%b.%i (line %B%I%b in %U%9x%u) @ $(__timer_current_time) | |
+ \ | |
${ZSH_THEME_PROMPT_DEBUG_SUFFIX:-'%f'}\ | |
" | |
# Ref. https://zsh.sourceforge.io/Doc/Release/User-Contributions.html#Writing-Themes | |
#prompt_cleanup command | |
#EOF |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment