ohmyzsh/lib/theme-and-appearance.zsh
Andrew Janke d24e84255b Themes: Fold themes plugin in to core oh-my-zsh
Rewrite theme() and lstheme() to remove side effects, add debugging,
and fix `local` inside theme definitions.
Add `theme next` for stepping through theme list, and "blacklist" support.
Modify tools/theme_chooser to use new theme functions.
2017-07-19 22:31:33 -04:00

418 lines
14 KiB
Bash

# Configure and enable ls colors
autoload -U colors && colors
export LSCOLORS="Gxfxcxdxbxegedabagacad"
# TODO organise this chaotic logic
if [[ $DISABLE_LS_COLORS != "true" ]]; then
# Find the option for using colors in ls, depending on the version: Linux or BSD
if [[ "$OSTYPE" == netbsd* ]]; then
# On NetBSD, test if "gls" (GNU ls) is installed (this one supports colors);
# otherwise, leave ls as is, because NetBSD's ls doesn't support -G
gls --color -d . &>/dev/null && alias ls='gls --color=tty'
elif [[ "$OSTYPE" == openbsd* ]]; then
# On OpenBSD, "gls" (ls from GNU coreutils) and "colorls" (ls from base,
# with color and multibyte support) are available from ports. "colorls"
# will be installed on purpose and can't be pulled in by installing
# coreutils, so prefer it to "gls".
gls --color -d . &>/dev/null && alias ls='gls --color=tty'
colorls -G -d . &>/dev/null && alias ls='colorls -G'
elif [[ "$OSTYPE" == darwin* ]]; then
# this is a good alias, it works by default just using $LSCOLORS
ls -G . &>/dev/null && alias ls='ls -G'
# only use coreutils ls if there is a dircolors customization present ($LS_COLORS or .dircolors file)
# otherwise, gls will use the default color scheme which is ugly af
[[ -n "$LS_COLORS" || -f "$HOME/.dircolors" ]] && gls --color -d . &>/dev/null && alias ls='gls --color=tty'
else
# For GNU ls, we use the default ls color theme. They can later be overwritten by themes.
if [[ -z "$LS_COLORS" ]]; then
(( $+commands[dircolors] )) && eval "$(dircolors -b)"
fi
ls --color -d . &>/dev/null && alias ls='ls --color=tty' || { ls -G . &>/dev/null && alias ls='ls -G' }
# Take advantage of $LS_COLORS for completion as well.
zstyle ':completion:*' list-colors "${(s.:.)LS_COLORS}"
fi
fi
if [[ -n $WINDOW ]]; then
SCREEN_NO="%B$WINDOW%b "
else
SCREEN_NO=""
fi
# OMZ themes use promptsubst, so make sure it's on
setopt prompt_subst
# Set to true to enable debugging output for theme functions
# Note that this can cause unstable behavior, especially with these themes:
# agnoster dstufft
ZSH_THEME_DEBUG=${ZSH_THEME_DEBUG:-false}
# These themes are known to interact badly with our debugging support
_OMZ_DEBUG_BLACKLISTED_THEMES=(agnoster dstufft dogenpunk fino fino-time half-life \
kolo peepcode smt steeef suvash zhann)
# Loads a theme
#
# Usage:
# theme <name> - load named theme
# theme random - load a random theme
# theme <n> - load a theme by index (where <n> is an integer)
# theme next - load the next theme in the list of defined themes
#
# If no argument is given, `theme random` is the default behavior.
#
# Return value:
# 0 on success
# Nonzero on failure, such as requested theme not being found or theme index
# out of range.
#
# The "random" and "next" forms will skip over themes in the ZSH_BLACKLISTED_THEMES
# variable.
#
# As a consequence of this argument syntax, themes may not be named "random", "next",
# or an integer number.
function theme() {
local themes n_themes random_theme blacklist
if [[ -z $1 || $1 == "random" ]]; then
# Special case: random theme
blacklist=($(_omz_theme_blacklist))
themes=($(lstheme))
#themes=(${themes:|blacklist})
_omz_array_setdiff themes themes blacklist
n_themes=${#themes[@]}
((n=(RANDOM%n_themes)+1))
random_theme=${themes[$n]}
echo "[oh-my-zsh] Loading random theme '$random_theme'..."
theme $random_theme
elif [[ $1 == "next" ]]; then
# Auto-advance to next theme
_omz_theme_n next
elif [[ $1 =~ '^[0-9]+$' ]]; then
# Select theme by index
_omz_theme_n $1
else
# Main case: load named theme
_omz_load_theme $1
fi
}
function _omz_load_theme() {
local name=$1
local theme_dir found
found=false
for theme_dir ($ZSH_CUSTOM $ZSH_CUSTOM/themes $ZSH/themes); do
if [[ -f "$theme_dir/$name.zsh-theme" ]]; then
found=true
_omz_load_theme_from_file $name "$theme_dir/$name.zsh-theme"
break
fi
done
if [[ $found == false ]]; then
echo "[oh-my-zsh] Theme not found: '$name'"
return 1
fi
}
# Loads a theme by index
#
# Usage:
# themen - advance to the next theme
# themen next - advance to the next theme
# themen <n> - load theme number <n>
#
# Loads a theme selected by index into list of defined themes, instead of by
# name. This is to make it easy to cycle through all the themes for debugging
# or browsing purposes.
#
# If called without an argument, it just advances to the next theme in the list
#
# `themen next` will ignore themes in the ZSH_BLACKLISTED_THEMES variable.
function _omz_theme_n() {
# Numeric index argument: select theme by index
local themes n name n_themes blacklist
themes=($(lstheme))
n_themes=${#themes[@]}
if [[ -z $1 || $1 == "next" ]]; then
# Auto-advance to next theme
# Find index of currently loaded theme
local last_n=${themes[(i)$ZSH_THEME]}
(( n = (last_n % n_themes) + 1 ))
# Advance past blacklisted themes. Do it this way instead of removing blacklist
# from themes list to keep indexes stable
blacklist=($(_omz_theme_blacklist))
while [[ ${blacklist[(i)${themes[n]}]} -le ${#blacklist} ]]; do
(( n = n % n_themes + 1 ))
done
else
n=$1
if [[ $n -gt $n_themes ]]; then
printf "[oh-my-zsh]: Theme index %s is out of range (max is %s)\n" $n $n_themes
return 1
fi
fi
name=${themes[$n]}
echo "Loading theme #$n/$n_themes: '$name'"
theme $name
}
# Computes the set difference between two arrays using only ZSH 4.x features
#
# _omz_array_setdiff(c, a, b)
#
# Determines all the items in $a that are not in $b, and outputs them.
# This is the same functionality as ZSH 5.x's "c=(${a:|b})" expansion operation,
# but is re-implemented here for compatibility with ZSH 4.x.
#
# This operates by "reference", so caller should pass in names of the input
# parameters, not their values.
#
# Implementation note: the input variables may not be named "omz_asd_a", "omz_asd_b",
# or "omz_asd_out_name".
# due to dynamic scoping conflicts. And you cannot use the numeric parameters ($1, $2, etc)
# as inputs.
#
# Example:
#
# a=(foo bar baz)
# b=(bar qux)
# _omz_array_setdiff c a b
#
# After the function call, $c will contain (foo baz).
#
function _omz_array_setdiff() {
local omz_asd_a omz_asd_b omz_asd_out_name
omz_asd_out_name=$1
omz_asd_a=(${(P)2})
omz_asd_b=(${(P)3})
local my_out item
my_out=()
for item (${omz_asd_a}); do
if [[ ${omz_asd_b[(i)$item]} -gt ${#omz_asd_b} ]]; then
my_out+=($item)
fi
done
set -A $omz_asd_out_name $my_out
}
# Variables to track hook functions installed by themes
_OMZ_THEME_CHPWD_FUNCTIONS=()
_OMZ_THEME_PRECMD_FUNCTIONS=()
_OMZ_THEME_PREEXEC_FUNCTIONS=()
# Outputs the effective theme blacklist
function _omz_theme_blacklist() {
local blacklist=$ZSH_BLACKLISTED_THEMES
if [[ $ZSH_THEME_DEBUG == true ]]; then
blacklist+=($_OMZ_DEBUG_BLACKLISTED_THEMES)
fi
echo ${(F)blacklist}
}
# Implementation of loading a theme
# Most of the code in here is for tracking hooks and debugging support
function _omz_load_theme_from_file() {
local name=$1 file=$2 params_before exclude_params
local -A values_before
# Reset so theme is loaded into a clean slate
_omz_reset_theme
# Set up tracking and debugging
local chpwd_fcns_0 precmd_fcns_0 preexec_fcns_0 chpwd_0 precmd_0 preexec_0
local params_after params_added param ignore_params
chpwd_0=$(which chpwd)
precmd_0=$(which precmd)
preexec_0=$(which preexec)
chpwd_fcns_0=($chpwd_functions)
precmd_fcns_0=($precmd_functions)
preexec_fcns_0=($preexec_functions)
if [[ $ZSH_THEME_DEBUG == true ]]; then
params_before=($(set +))
fi
for param ($params_before); do
values_before[$param]=${(P)param}
done
# Actually load the theme, using an indirection function
_omz_source_theme_file $file
# Debugging stuff
if [[ $ZSH_THEME_DEBUG == true ]]; then
params_after=($(set +))
#params_added=(${params_after:|params_before})
_omz_array_setdiff params_added params_after params_before
ignore_params=(LINENO RANDOM _ parameters prompt values_before modules \
params_added ignore_params params_before params_after params_changed \
SECONDS TTYIDLE PS1 PS2 PS3 PS4 RPS1 RPS2)
#params_before=(${params_before:|ignore_params})
_omz_array_setdiff params_before params_before ignore_params
local params_changed
params_changed=()
for param ($params_before); do
if [[ "${(P)param}" != "${values_before[$param]}" ]]; then
params_changed+=($param)
fi
done
fi
# Record which theme was loaded
ZSH_THEME=$name
# Track changes to hooks
#_OMZ_THEME_CHPWD_FUNCTIONS=(${chpwd_functions:|chpwd_fcns_0})
_omz_array_setdiff _OMZ_THEME_CHPWD_FUNCTIONS chpwd_functions chpwd_fcns_0
#_OMZ_THEME_PRECMD_FUNCTIONS=(${precmd_functions:|precmd_fcns_0})
_omz_array_setdiff _OMZ_THEME_PRECMD_FUNCTIONS precmd_functions precmd_fcns_0
#_OMZ_THEME_PREEXEC_FUNCTIONS=(${preexec_functions:|preexec_fcns_0})
_omz_array_setdiff _OMZ_THEME_PREEXEC_FUNCTIONS preexec_functions preexec_fcns_0
# Post-loading debugging
if [[ $ZSH_THEME_DEBUG == true ]]; then
echo "Loaded theme '$name' from file $file"
if [[ -n $params_added ]]; then
printf '=== %s ===\n%s\n' "Theme added parameters:" ${(F)params_added}
fi
if [[ -n $params_changed ]]; then
printf '=== %s ===\n%s\n' "Theme changed parameters:" ${(F)params_changed}
fi
if [[ -n "${_OMZ_THEME_CHPWD_FUNCTIONS}${_OMZ_THEME_PRECMD_FUNCTIONS}${_OMZ_THEME_PREEXEC_FUNCTIONS}" ]]; then
printf '=== %s ===\n' "Theme added hooks:"
if [[ -n $_OMZ_THEME_CHPWD_FUNCTIONS ]]; then
echo "Theme added chpwd hooks: $_OMZ_THEME_CHPWD_FUNCTIONS"
fi
if [[ -n $_OMZ_THEME_PRECMD_FUNCTIONS ]]; then
echo "Theme added precmd hooks: $_OMZ_THEME_PRECMD_FUNCTIONS"
fi
if [[ -n $_OMZ_THEME_PREEXEC_FUNCTIONS ]]; then
echo "Theme added preexec hooks: $_OMZ_THEME_PREEXEC_FUNCTIONS"
fi
fi
if [[ $(which chpwd) != $chpwd_0 ]]; then
echo "WARNING: Theme changed chpwd()"
fi
if [[ $(which preexec) != $preexec_0 ]]; then
echo "WARNING: Theme changed preexec()"
fi
if [[ $(which precmd) != $precmd_0 ]]; then
echo "WARNING: Theme changed precmd()"
fi
fi
}
# Sources the given file
# The only reason this function exists is to provide a layer of
# indirection so that the theme file runs in its own function call stack frame
# and "local" statements in the theme definitions work as intended.
function _omz_source_theme_file() {
source $1
}
# Resets all theme settings to their default state
# (To the extent that we know what themes do, that is.)
# This will reset all variables used by the core OMZ *_prompt_info functions.
# It will also remove any hook functions installed by the current theme, if it
# was loaded by the theme() function
function _omz_reset_theme() {
# Prompts
PROMPT="%n@%m:%~%# "
PROMPT2='%_> '
PROMPT3='?# '
PROMPT4='+%N:%i> '
unset RPROMPT RPROMPT2
# This assumes that all ZSH_THEME_<aspect>_* variables are owned by
# OMZ theming, and can be reset en masse
# All the commented-out variables are there to serve as a list of things
# that are used by theme support and could be given default values
# git_prompt_info variables
unset -m 'ZSH_THEME_GIT_PROMPT_*'
ZSH_THEME_GIT_PROMPT_PREFIX="git:(" # Prefix at the very beginning of the prompt, before the branch name
ZSH_THEME_GIT_PROMPT_SUFFIX=")" # At the very end of the prompt
#ZSH_THEME_GIT_PROMPT_PREFIX=
#ZSH_THEME_GIT_PROMPT_SUFFIX=
#ZSH_THEME_GIT_COMMITS_AHEAD_PREFIX=
#ZSH_THEME_GIT_COMMITS_AHEAD_SUFFIX=
ZSH_THEME_GIT_PROMPT_DIRTY="*" # Text to display if the branch is dirty
ZSH_THEME_GIT_PROMPT_CLEAN="" # Text to display if the branch is clean
#ZSH_THEME_GIT_PROMPT_BEHIND_REMOTE=
#ZSH_THEME_GIT_PROMPT_AHEAD_REMOTE=
#ZSH_THEME_GIT_PROMPT_DIVERGED_REMOTE=
#ZSH_THEME_GIT_PROMPT_AHEAD=
#ZSH_THEME_GIT_PROMPT_BEHIND=
#ZSH_THEME_GIT_PROMPT_SHA_BEFORE=
#ZSH_THEME_GIT_PROMPT_SHA_AFTER=
#ZSH_THEME_GIT_PROMPT_ADDED=
#ZSH_THEME_GIT_PROMPT_MODIFIED=
#ZSH_THEME_GIT_PROMPT_RENAMED=
#ZSH_THEME_GIT_PROMPT_DELETED=
#ZSH_THEME_GIT_PROMPT_STASHED=
#ZSH_THEME_GIT_PROMPT_UNMERGED=
#ZSH_THEME_GIT_PROMPT_DIVERGED=
#ZSH_THEME_GIT_PROMPT_UNTRACKED=
# nvm_prompt_info variables
unset -m 'ZSH_THEME_NVM_PROMPT_*'
#ZSH_THEME_NVM_PROMPT_PREFIX=
#ZSH_THEME_NVM_PROMPT_SUFFIX=
# rvm_prompt_info variables
unset -m 'ZSH_THEME_RVM_PROMPT_*'
#ZSH_THEME_RVM_PROMPT_PREFIX=
#ZSH_THEME_RVM_PROMPT_SUFFIX=
#ZSH_THEME_RVM_PROMPT_OPTIONS=
# svn_prompt_info variables
unset -m 'ZSH_THEME_SVN_PROMPT_*'
#ZSH_THEME_SVN_PROMPT_CLEAN
#ZSH_THEME_SVN_PROMPT_DIRTY
#ZSH_THEME_SVN_PROMPT_PREFIX
#ZSH_THEME_SVN_PROMPT_SUFFIX
# Hook functions
if [[ -n $_OMZ_THEME_CHPWD_FUNCTIONS ]]; then
#echo Removing chpwd hooks: $_OMZ_THEME_CHPWD_FUNCTIONS
#chpwd_functions=(${chpwd_functions:|_OMZ_THEME_CHPWD_FUNCTIONS})
_omz_array_setdiff chpwd_functions chpwd_functions _OMZ_THEME_CHPWD_FUNCTIONS
fi
if [[ -n $_OMZ_THEME_PRECMD_FUNCTIONS ]]; then
#echo Removing precmd hooks: $_OMZ_THEME_PRECMD_FUNCTIONS
#precmd_functions=(${precmd_functions:|_OMZ_THEME_PRECMD_FUNCTIONS})
_omz_array_setdiff precmd_functions precmd_functions _OMZ_THEME_PRECMD_FUNCTIONS
fi
if [[ -n $_OMZ_THEME_PREEXEC_FUNCTIONS ]]; then
#echo Removing preexec hooks: $_OMZ_THEME_PREEXEC_FUNCTIONS
#preexec_functions=(${preexec_functions:|_OMZ_THEME_PREEXEC_FUNCTIONS})
_omz_array_setdiff preexec_functions preexec_functions _OMZ_THEME_PREEXEC_FUNCTIONS
fi
}
# List available themes by name
#
# The list will always be in sorted order, so you can index in to it.
# Themes in the "blacklist" are still included in lstheme, because they're
# still eligible for direct reference by explicit name or index, and that
# keeps the order stable.
function lstheme() {
local themes x theme_dir
themes=()
for theme_dir ($ZSH_CUSTOM $ZSH_CUSTOM/themes $ZSH/themes); do
if [[ -d $theme_dir ]]; then
themes+=($(_omz_lstheme_dir $theme_dir))
fi
done
themes=(${(ou)themes})
echo ${(F)themes}
}
# List themes defined in a given dir
function _omz_lstheme_dir() {
ls $1 | grep '.zsh-theme$' | sed 's,\.zsh-theme$,,'
}