Skip to content

Instantly share code, notes, and snippets.

@fbender
Created March 22, 2024 23:27
Show Gist options
  • Save fbender/9cea193ca5a5143f1764acf6452b118a to your computer and use it in GitHub Desktop.
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)
### 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