Add script/omarchy-theme-color and expand omarchy-theme-set-templates to derive a full palette from each theme's colors.toml, then render new themed templates (shell, pi, vscode theme, gum, neovim, claude, hermes, t3code) from it. Adds backgrounds and palette updates for existing themes plus new themes (last-horizon, lupine, solitude, tidewater-*, miasma, ...).
305 lines
10 KiB
Bash
Executable File
305 lines
10 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# omarchy:summary=Resolve semantic colors from an Omarchy theme colors.toml
|
|
# omarchy:args=[--file <colors.toml>] (--all | --raw | <key> [fallback])
|
|
# omarchy:hidden=true
|
|
|
|
# Shared colors.toml parser/resolver. The alias/fallback cascade mirrors what
|
|
# omarchy-theme-set-templates bakes into the generated configs at theme-set
|
|
# time, so every consumer (templates, OSC sequences, tmux, GNOME, previews)
|
|
# resolves the exact same palette.
|
|
#
|
|
# --all print every resolved key<TAB>value pair (sorted by key)
|
|
# --raw print only the key<TAB>value pairs defined in the file
|
|
# <key> [fallback] print one resolved value; the fallback is tried as
|
|
# another palette key first, then used verbatim
|
|
#
|
|
# Theme mode precedence: `mode` key, legacy `theme_type` key, a light.mode
|
|
# file beside the colors.toml, background luminance auto-detect, dark.
|
|
|
|
COLORS_FILE="$HOME/.local/state/omarchy/current/theme/colors.toml"
|
|
OUTPUT=""
|
|
QUERY_KEY=""
|
|
QUERY_FALLBACK=""
|
|
|
|
usage() {
|
|
echo "Usage: omarchy-theme-color [--file <colors.toml>] (--all | --raw | <key> [fallback])"
|
|
}
|
|
|
|
while (( $# > 0 )); do
|
|
case "$1" in
|
|
--file)
|
|
COLORS_FILE="${2:-}"
|
|
shift 2
|
|
;;
|
|
--all)
|
|
OUTPUT="all"
|
|
shift
|
|
;;
|
|
--raw)
|
|
OUTPUT="raw"
|
|
shift
|
|
;;
|
|
-h | --help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
if [[ -z $QUERY_KEY ]]; then
|
|
QUERY_KEY="$1"
|
|
elif [[ -z $QUERY_FALLBACK ]]; then
|
|
QUERY_FALLBACK="$1"
|
|
else
|
|
usage >&2
|
|
exit 1
|
|
fi
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z $OUTPUT && -z $QUERY_KEY ]] || [[ -n $OUTPUT && -n $QUERY_KEY ]]; then
|
|
usage >&2
|
|
exit 1
|
|
fi
|
|
|
|
COLORS_DIR=$(dirname "$COLORS_FILE")
|
|
|
|
declare -A THEME_COLORS
|
|
|
|
# Mix two hex colors. Amount may be a fraction (0.30) or percentage (30%).
|
|
mix_color() {
|
|
local start="${1#\#}"
|
|
local end="${2#\#}"
|
|
local amount="$3"
|
|
|
|
awk -v start="$start" -v end="$end" -v amount="$amount" '
|
|
function hex_value(char) {
|
|
return index("0123456789abcdef", tolower(char)) - 1
|
|
}
|
|
|
|
function hex_pair_to_int(hex, idx) {
|
|
return hex_value(substr(hex, idx, 1)) * 16 + hex_value(substr(hex, idx + 1, 1))
|
|
}
|
|
|
|
BEGIN {
|
|
if (amount ~ /%$/) {
|
|
sub(/%$/, "", amount)
|
|
amount = amount / 100
|
|
} else {
|
|
amount += 0
|
|
if (amount > 1) amount = amount / 100
|
|
}
|
|
|
|
if (amount < 0) amount = 0
|
|
if (amount > 1) amount = 1
|
|
|
|
start_r = hex_pair_to_int(start, 1)
|
|
start_g = hex_pair_to_int(start, 3)
|
|
start_b = hex_pair_to_int(start, 5)
|
|
end_r = hex_pair_to_int(end, 1)
|
|
end_g = hex_pair_to_int(end, 3)
|
|
end_b = hex_pair_to_int(end, 5)
|
|
|
|
red = int(start_r * (1 - amount) + end_r * amount + 0.5)
|
|
green = int(start_g * (1 - amount) + end_g * amount + 0.5)
|
|
blue = int(start_b * (1 - amount) + end_b * amount + 0.5)
|
|
|
|
printf "#%02x%02x%02x\n", red, green, blue
|
|
}
|
|
'
|
|
}
|
|
|
|
alias_theme_color() {
|
|
local key="$1"
|
|
local fallback="$2"
|
|
|
|
[[ ${THEME_COLORS[$key]} ]] || THEME_COLORS[$key]="${THEME_COLORS[$fallback]}"
|
|
}
|
|
|
|
resolve_theme_mode() {
|
|
local bg_hex lum
|
|
|
|
[[ ${THEME_COLORS[mode]} ]] || THEME_COLORS[mode]="${THEME_COLORS[theme_type]}"
|
|
[[ ${THEME_COLORS[mode]} ]] && return
|
|
|
|
if [[ -f $COLORS_DIR/light.mode ]]; then
|
|
THEME_COLORS[mode]="light"
|
|
elif [[ ${THEME_COLORS[background]} =~ ^#[0-9A-Fa-f]{6}$ ]]; then
|
|
bg_hex="${THEME_COLORS[background]#\#}"
|
|
lum=$(( $(printf "%d" "0x${bg_hex:0:2}") + $(printf "%d" "0x${bg_hex:2:2}") + $(printf "%d" "0x${bg_hex:4:2}") ))
|
|
(( lum > 382 )) && THEME_COLORS[mode]="light" || THEME_COLORS[mode]="dark"
|
|
else
|
|
THEME_COLORS[mode]="dark"
|
|
fi
|
|
}
|
|
|
|
parse_colors_file() {
|
|
local key value
|
|
|
|
[[ -f $COLORS_FILE ]] || return 0
|
|
|
|
while IFS='=' read -r key value; do
|
|
key="${key//[\"\' ]/}" # strip quotes and spaces from key
|
|
[[ $key && $key != \#* ]] || continue # skip empty lines and comments
|
|
|
|
if [[ $value == *[\"\']* ]]; then
|
|
value="${value#*[\"\']}"
|
|
value="${value%%[\"\']*}" # extract value between quotes (ignores inline comments)
|
|
else
|
|
value="${value#"${value%%[![:space:]]*}"}"
|
|
value="${value%"${value##*[![:space:]]}"}" # trim unquoted values
|
|
fi
|
|
|
|
# Values reach consumers as sed replacement text, so the charset excludes
|
|
# the delimiter, backslash, and & while still covering everything a real
|
|
# palette holds: hex, rgb()/rgba() lists, gradient angles like -45deg,
|
|
# decimals, and bare words. Rejections are announced so a third-party theme
|
|
# doesn't lose a key silently and leave a raw {{ placeholder }} behind.
|
|
if [[ ! $key =~ ^[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-]+$ ]]; then
|
|
printf 'omarchy-theme-color: skipping key with unsupported characters\n' >&2
|
|
continue
|
|
fi
|
|
|
|
if [[ ! $value =~ ^[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#(),._+/%\ -]*$ ]]; then
|
|
printf 'omarchy-theme-color: skipping %s: unsupported characters in value\n' "$key" >&2
|
|
continue
|
|
fi
|
|
|
|
THEME_COLORS[$key]="$value"
|
|
[[ $OUTPUT == "raw" ]] && printf '%s\t%s\n' "$key" "$value"
|
|
done <"$COLORS_FILE"
|
|
|
|
return 0
|
|
}
|
|
|
|
resolve_theme_colors() {
|
|
local key
|
|
|
|
# Accept the complete legacy short-name palette before applying ANSI
|
|
# fallbacks or deriving shades. Canonical names take precedence when a theme
|
|
# defines both forms.
|
|
declare -A legacy_palette_alias=(
|
|
[background]=bg
|
|
[dark_background]=dark_bg
|
|
[darker_background]=darker_bg
|
|
[lighter_background]=lighter_bg
|
|
[foreground]=fg
|
|
[dark_foreground]=dark_fg
|
|
[light_foreground]=light_fg
|
|
[bright_foreground]=bright_fg
|
|
)
|
|
for key in "${!legacy_palette_alias[@]}"; do
|
|
alias_theme_color "$key" "${legacy_palette_alias[$key]}"
|
|
done
|
|
|
|
# Themes generated before the semantic palette may only define ANSI names.
|
|
[[ ${THEME_COLORS[background]} ]] || THEME_COLORS[background]="${THEME_COLORS[color0]}"
|
|
[[ ${THEME_COLORS[foreground]} ]] || THEME_COLORS[foreground]="${THEME_COLORS[color7]}"
|
|
[[ ${THEME_COLORS[background]} ]] && THEME_COLORS[color0]="${THEME_COLORS[background]}"
|
|
[[ ${THEME_COLORS[foreground]} ]] && THEME_COLORS[color7]="${THEME_COLORS[foreground]}"
|
|
|
|
# Legacy compatibility: map ANSI color0..color15 to semantic names.
|
|
declare -A legacy_alias=(
|
|
[red]=color1
|
|
[green]=color2
|
|
[yellow]=color3
|
|
[blue]=color4
|
|
[magenta]=color5
|
|
[cyan]=color6
|
|
[bright_red]=color9
|
|
[bright_green]=color10
|
|
[bright_yellow]=color11
|
|
[bright_blue]=color12
|
|
[bright_magenta]=color13
|
|
[bright_cyan]=color14
|
|
)
|
|
for key in "${!legacy_alias[@]}"; do
|
|
alias_theme_color "$key" "${legacy_alias[$key]}"
|
|
done
|
|
alias_theme_color magenta purple
|
|
alias_theme_color bright_magenta bright_purple
|
|
|
|
[[ ${THEME_COLORS[light_foreground]} ]] || THEME_COLORS[light_foreground]="${THEME_COLORS[color7]:-${THEME_COLORS[foreground]}}"
|
|
[[ ${THEME_COLORS[bright_foreground]} ]] || THEME_COLORS[bright_foreground]="${THEME_COLORS[color15]:-${THEME_COLORS[foreground]}}"
|
|
THEME_COLORS[cursor]="${THEME_COLORS[bright_foreground]}"
|
|
[[ ${THEME_COLORS[lighter_background]} ]] || THEME_COLORS[lighter_background]="${THEME_COLORS[color0]:-${THEME_COLORS[background]}}"
|
|
[[ ${THEME_COLORS[dark_foreground]} ]] || THEME_COLORS[dark_foreground]="${THEME_COLORS[color8]:-${THEME_COLORS[foreground]}}"
|
|
[[ ${THEME_COLORS[muted]} ]] || THEME_COLORS[muted]="${THEME_COLORS[color8]:-${THEME_COLORS[dark_foreground]}}"
|
|
[[ ${THEME_COLORS[selection]} ]] || THEME_COLORS[selection]="${THEME_COLORS[selection_background]:-${THEME_COLORS[color8]:-${THEME_COLORS[color0]:-${THEME_COLORS[background]}}}}"
|
|
[[ ${THEME_COLORS[selection_background]} ]] || THEME_COLORS[selection_background]="${THEME_COLORS[selection]}"
|
|
[[ ${THEME_COLORS[selection_foreground]} ]] || THEME_COLORS[selection_foreground]="${THEME_COLORS[bright_foreground]}"
|
|
[[ ${THEME_COLORS[orange]} ]] || THEME_COLORS[orange]="${THEME_COLORS[yellow]}"
|
|
[[ ${THEME_COLORS[brown]} ]] || THEME_COLORS[brown]=$(mix_color "${THEME_COLORS[orange]}" "#000000" 50%)
|
|
|
|
# Auto-derive shades from base accents when not defined and not aliased from colorN
|
|
[[ ${THEME_COLORS[dark_background]} ]] || THEME_COLORS[dark_background]=$(mix_color "${THEME_COLORS[background]}" "#000000" 25%)
|
|
[[ ${THEME_COLORS[darker_background]} ]] || THEME_COLORS[darker_background]=$(mix_color "${THEME_COLORS[background]}" "#000000" 50%)
|
|
[[ ${THEME_COLORS[bright_red]} ]] || THEME_COLORS[bright_red]=$(mix_color "${THEME_COLORS[red]}" "#ffffff" 20%)
|
|
[[ ${THEME_COLORS[bright_yellow]} ]] || THEME_COLORS[bright_yellow]=$(mix_color "${THEME_COLORS[yellow]}" "#ffffff" 20%)
|
|
[[ ${THEME_COLORS[bright_green]} ]] || THEME_COLORS[bright_green]=$(mix_color "${THEME_COLORS[green]}" "#ffffff" 20%)
|
|
[[ ${THEME_COLORS[bright_cyan]} ]] || THEME_COLORS[bright_cyan]=$(mix_color "${THEME_COLORS[cyan]}" "#ffffff" 20%)
|
|
[[ ${THEME_COLORS[bright_blue]} ]] || THEME_COLORS[bright_blue]=$(mix_color "${THEME_COLORS[blue]}" "#ffffff" 20%)
|
|
[[ ${THEME_COLORS[bright_magenta]} ]] || THEME_COLORS[bright_magenta]=$(mix_color "${THEME_COLORS[magenta]}" "#ffffff" 20%)
|
|
alias_theme_color purple magenta
|
|
alias_theme_color bright_purple bright_magenta
|
|
|
|
# Keep semantic themes compatible with consumers that still reference the
|
|
# legacy ANSI names directly.
|
|
declare -A ansi_alias=(
|
|
[color0]=background
|
|
[color1]=red
|
|
[color2]=green
|
|
[color3]=yellow
|
|
[color4]=blue
|
|
[color5]=magenta
|
|
[color6]=cyan
|
|
[color7]=foreground
|
|
[color8]=muted
|
|
[color9]=bright_red
|
|
[color10]=bright_green
|
|
[color11]=bright_yellow
|
|
[color12]=bright_blue
|
|
[color13]=bright_magenta
|
|
[color14]=bright_cyan
|
|
[color15]=bright_foreground
|
|
)
|
|
for key in "${!ansi_alias[@]}"; do
|
|
alias_theme_color "$key" "${ansi_alias[$key]}"
|
|
done
|
|
|
|
# Keep canonical themes compatible with old user templates and consumers
|
|
# that still query the short palette names directly.
|
|
for key in "${!legacy_palette_alias[@]}"; do
|
|
if [[ ${THEME_COLORS[$key]} ]]; then
|
|
THEME_COLORS["${legacy_palette_alias[$key]}"]="${THEME_COLORS[$key]}"
|
|
fi
|
|
done
|
|
|
|
resolve_theme_mode
|
|
THEME_COLORS[theme_type]="${THEME_COLORS[mode]}"
|
|
}
|
|
|
|
parse_colors_file
|
|
|
|
if [[ $OUTPUT == "raw" ]]; then
|
|
exit 0
|
|
fi
|
|
|
|
resolve_theme_colors
|
|
|
|
if [[ $OUTPUT == "all" ]]; then
|
|
while IFS= read -r key; do
|
|
printf '%s\t%s\n' "$key" "${THEME_COLORS[$key]}"
|
|
done < <(printf '%s\n' "${!THEME_COLORS[@]}" | LC_ALL=C sort)
|
|
exit 0
|
|
fi
|
|
|
|
value="${THEME_COLORS[$QUERY_KEY]:-}"
|
|
if [[ -z $value && -n $QUERY_FALLBACK ]]; then
|
|
value="${THEME_COLORS[$QUERY_FALLBACK]:-$QUERY_FALLBACK}"
|
|
fi
|
|
|
|
[[ -n $value ]] || exit 1
|
|
printf '%s\n' "$value"
|