The canonical colors.toml keys are now background/foreground, including all permutations (dark_background, darker_background, lighter_background, dark_foreground, light_foreground, bright_foreground). The old short names are gone entirely, not kept as aliases; legacy alacritty-generated themes still resolve through the ANSI color0/color7 fallback. Theme files are also regrouped: accent/selection/muted first, then the backgrounds, then the foregrounds, then the named colors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
440 lines
11 KiB
Bash
Executable File
440 lines
11 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# omarchy:summary=Preview an Omarchy theme palette in the terminal
|
|
# omarchy:args=[theme-name|theme-dir|colors.toml] [--no-color] [--no-osc|--osc]
|
|
# omarchy:examples=omarchy dev theme-preview | omarchy dev theme-preview tokyo-night | omarchy dev theme-preview themes/gruvbox/colors.toml --no-color --no-osc
|
|
|
|
set -o pipefail
|
|
|
|
CURRENT_THEME_PATH="$HOME/.local/state/omarchy/current/theme"
|
|
USER_THEMES_PATH="$HOME/.config/omarchy/themes"
|
|
OMARCHY_THEMES_PATH="$OMARCHY_PATH/themes"
|
|
|
|
COLOR_OUTPUT=1
|
|
APPLY_OSC="auto"
|
|
THEME_REF=""
|
|
declare -A COLORS
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
Usage: omarchy-dev-theme-preview [theme-name|theme-dir|colors.toml] [--no-color] [--no-osc]
|
|
|
|
Preview a theme palette in the terminal. Without an argument, previews the
|
|
current theme from ~/.local/state/omarchy/current/theme/colors.toml.
|
|
|
|
When stdout is a terminal and color output is enabled, the preview also applies
|
|
that theme's OSC palette to the current terminal only. Use --no-osc to suppress
|
|
that, or --osc to force it even when stdout is not detected as a terminal.
|
|
USAGE
|
|
}
|
|
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--no-color | --plain)
|
|
COLOR_OUTPUT=0
|
|
APPLY_OSC="never"
|
|
;;
|
|
--no-osc)
|
|
APPLY_OSC="never"
|
|
;;
|
|
--osc | --apply-osc | --terminal)
|
|
APPLY_OSC="always"
|
|
;;
|
|
-h | --help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
if [[ -n $THEME_REF ]]; then
|
|
usage >&2
|
|
exit 1
|
|
fi
|
|
THEME_REF="$arg"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -n ${NO_COLOR:-} ]]; then
|
|
COLOR_OUTPUT=0
|
|
fi
|
|
|
|
normalize_theme_name() {
|
|
printf '%s' "$1" | sed -E 's/<[^>]+>//g' | tr '[:upper:]' '[:lower:]' | tr ' ' '-'
|
|
}
|
|
|
|
resolve_colors_file() {
|
|
local ref="$1"
|
|
local theme_name
|
|
|
|
if [[ -z $ref ]]; then
|
|
printf '%s/colors.toml' "$CURRENT_THEME_PATH"
|
|
elif [[ -f $ref ]]; then
|
|
printf '%s' "$ref"
|
|
elif [[ -d $ref && -f $ref/colors.toml ]]; then
|
|
printf '%s/colors.toml' "$ref"
|
|
else
|
|
theme_name=$(normalize_theme_name "$ref")
|
|
if [[ -f $USER_THEMES_PATH/$theme_name/colors.toml ]]; then
|
|
printf '%s/colors.toml' "$USER_THEMES_PATH/$theme_name"
|
|
elif [[ -f $OMARCHY_THEMES_PATH/$theme_name/colors.toml ]]; then
|
|
printf '%s/colors.toml' "$OMARCHY_THEMES_PATH/$theme_name"
|
|
else
|
|
return 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
omarchy_tool() {
|
|
local tool="$1"
|
|
shift
|
|
|
|
if [[ -x $OMARCHY_PATH/bin/$tool ]]; then
|
|
"$OMARCHY_PATH/bin/$tool" "$@"
|
|
else
|
|
"$tool" "$@"
|
|
fi
|
|
}
|
|
|
|
load_colors() {
|
|
local colors_file="$1"
|
|
local key value
|
|
local -A resolved=()
|
|
|
|
while IFS=$'\t' read -r key value; do
|
|
resolved[$key]="$value"
|
|
done < <(omarchy_tool omarchy-theme-color --file "$colors_file" --all)
|
|
|
|
# Preview the palette the theme itself defines, at canonically resolved values.
|
|
while IFS=$'\t' read -r key value; do
|
|
COLORS[$key]="${resolved[$key]:-}"
|
|
done < <(omarchy_tool omarchy-theme-color --file "$colors_file" --raw)
|
|
|
|
# Plus the core slots every theme resolves, so sparse and legacy themes
|
|
# still render a complete preview.
|
|
for key in mode background foreground light_foreground bright_foreground selection_background selection_foreground dark_background darker_background; do
|
|
[[ -n ${resolved[$key]:-} ]] && COLORS[$key]="${resolved[$key]}"
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
hex_parts() {
|
|
local hex="${1#\#}"
|
|
|
|
printf '%d %d %d' "0x${hex:0:2}" "0x${hex:2:2}" "0x${hex:4:2}"
|
|
}
|
|
|
|
hex_valid() {
|
|
[[ $1 =~ ^#[0-9A-Fa-f]{6}$ ]]
|
|
}
|
|
|
|
luma_sum() {
|
|
local r g b
|
|
|
|
read -r r g b < <(hex_parts "$1")
|
|
printf '%d' $((r * 2126 + g * 7152 + b * 722))
|
|
}
|
|
|
|
contrast_ratio() {
|
|
local fg="$1"
|
|
local bg="$2"
|
|
|
|
awk -v fg="${fg#\#}" -v bg="${bg#\#}" '
|
|
function hex_value(char) { return index("0123456789abcdef", tolower(char)) - 1 }
|
|
function hex_pair(hex, idx) { return hex_value(substr(hex, idx, 1)) * 16 + hex_value(substr(hex, idx + 1, 1)) }
|
|
function channel(hex, idx) { return hex_pair(hex, idx) / 255 }
|
|
function linear(c) { return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ^ 2.4 }
|
|
function lum(hex) {
|
|
return 0.2126 * linear(channel(hex, 1)) + 0.7152 * linear(channel(hex, 3)) + 0.0722 * linear(channel(hex, 5))
|
|
}
|
|
BEGIN {
|
|
a = lum(fg)
|
|
b = lum(bg)
|
|
if (a < b) { tmp = a; a = b; b = tmp }
|
|
printf "%.2f", (a + 0.05) / (b + 0.05)
|
|
}
|
|
'
|
|
}
|
|
|
|
ansi_bg() {
|
|
local r g b
|
|
|
|
read -r r g b < <(hex_parts "$1")
|
|
printf '\033[48;2;%d;%d;%dm' "$r" "$g" "$b"
|
|
}
|
|
|
|
ansi_fg() {
|
|
local r g b
|
|
|
|
read -r r g b < <(hex_parts "$1")
|
|
printf '\033[38;2;%d;%d;%dm' "$r" "$g" "$b"
|
|
}
|
|
|
|
reset_ansi() {
|
|
printf '\033[0m'
|
|
}
|
|
|
|
apply_terminal_osc() {
|
|
case "$APPLY_OSC" in
|
|
never)
|
|
return
|
|
;;
|
|
auto)
|
|
[[ -t 1 ]] && (( COLOR_OUTPUT )) || return
|
|
;;
|
|
always)
|
|
;;
|
|
esac
|
|
|
|
omarchy_tool omarchy-theme-osc "$colors_file" || true
|
|
}
|
|
|
|
swatch() {
|
|
local hex="$1"
|
|
local width="${2:-18}"
|
|
local i
|
|
|
|
if (( COLOR_OUTPUT )) && hex_valid "$hex"; then
|
|
ansi_bg "$hex"
|
|
for (( i = 0; i < width; i++ )); do
|
|
printf ' '
|
|
done
|
|
reset_ansi
|
|
else
|
|
for (( i = 0; i < width; i++ )); do
|
|
printf '#'
|
|
done
|
|
fi
|
|
}
|
|
|
|
print_color_row() {
|
|
local key="$1"
|
|
local hex="${COLORS[$key]}"
|
|
|
|
[[ -n $hex ]] || return
|
|
printf ' %-22s %-9s ' "$key" "$hex"
|
|
swatch "$hex" 20
|
|
printf '\n'
|
|
}
|
|
|
|
paint_segment() {
|
|
local text="$1"
|
|
local fg="$2"
|
|
local bg="${3:-${COLORS[background]}}"
|
|
|
|
if (( COLOR_OUTPUT )) && hex_valid "$fg" && hex_valid "$bg"; then
|
|
ansi_bg "$bg"
|
|
ansi_fg "$fg"
|
|
printf '%s' "$text"
|
|
reset_ansi
|
|
else
|
|
printf '%s' "$text"
|
|
fi
|
|
}
|
|
|
|
print_group() {
|
|
local title="$1"
|
|
shift
|
|
local key
|
|
|
|
printf '\n%s\n' "$title"
|
|
for key in "$@"; do
|
|
print_color_row "$key"
|
|
done
|
|
}
|
|
|
|
print_selection_sample() {
|
|
local bg="${COLORS[background]}"
|
|
local fg="${COLORS[foreground]}"
|
|
local selection_bg="${COLORS[selection_background]}"
|
|
local selection_fg="${COLORS[selection_foreground]}"
|
|
|
|
[[ -n $bg && -n $fg && -n $selection_bg && -n $selection_fg ]] || return
|
|
|
|
printf '\nSelection sample\n '
|
|
if (( COLOR_OUTPUT )) && hex_valid "$bg" && hex_valid "$fg" && hex_valid "$selection_bg" && hex_valid "$selection_fg"; then
|
|
ansi_bg "$bg"
|
|
ansi_fg "$fg"
|
|
printf ' This is some '
|
|
ansi_bg "$selection_bg"
|
|
ansi_fg "$selection_fg"
|
|
printf 'selected text'
|
|
ansi_bg "$bg"
|
|
ansi_fg "$fg"
|
|
printf ' in a sentence '
|
|
reset_ansi
|
|
printf '\n'
|
|
else
|
|
printf 'This is some [selected text] in a sentence\n'
|
|
fi
|
|
}
|
|
|
|
print_practical_samples() {
|
|
printf '\nTerminal/UI samples\n'
|
|
printf ' '
|
|
paint_segment ' normal text ' "${COLORS[foreground]}"
|
|
paint_segment ' muted/comment ' "${COLORS[muted]}"
|
|
paint_segment ' accent/link ' "${COLORS[accent]}"
|
|
paint_segment ' error ' "${COLORS[red]}"
|
|
paint_segment ' warning ' "${COLORS[yellow]}"
|
|
paint_segment ' success ' "${COLORS[green]}"
|
|
printf '\n '
|
|
paint_segment ' $ omarchy theme-preview ' "${COLORS[green]}"
|
|
paint_segment ' # comment ' "${COLORS[muted]}"
|
|
paint_segment ' "string" ' "${COLORS[green]}"
|
|
paint_segment ' function() ' "${COLORS[blue]}"
|
|
paint_segment ' --flag ' "${COLORS[magenta]}"
|
|
printf '\n '
|
|
paint_segment ' unselected menu row ' "${COLORS[foreground]}"
|
|
printf '\n '
|
|
paint_segment ' selected menu row ' "${COLORS[selection_foreground]}" "${COLORS[selection_background]}"
|
|
printf '\n '
|
|
paint_segment ' status/inverse ' "${COLORS[background]}" "${COLORS[foreground]}"
|
|
paint_segment ' inactive surface ' "${COLORS[foreground]}" "${COLORS[lighter_background]}"
|
|
paint_segment ' urgent ' "${COLORS[background]}" "${COLORS[red]}"
|
|
printf '\n'
|
|
}
|
|
|
|
print_palette_strip() {
|
|
local title="$1"
|
|
shift
|
|
local key hex
|
|
|
|
printf ' %-12s ' "$title"
|
|
for key in "$@"; do
|
|
hex="${COLORS[$key]}"
|
|
if [[ -n $hex ]]; then
|
|
swatch "$hex" 4
|
|
printf ' '
|
|
fi
|
|
done
|
|
printf '\n'
|
|
}
|
|
|
|
print_ansi_palette() {
|
|
printf '\nANSI palette strips\n'
|
|
print_palette_strip normal background red green yellow blue magenta cyan foreground
|
|
print_palette_strip bright muted bright_red bright_green bright_yellow bright_blue bright_magenta bright_cyan bright_foreground
|
|
}
|
|
|
|
mix_hex() {
|
|
local start="$1"
|
|
local end="$2"
|
|
local index="$3"
|
|
local max_index="$4"
|
|
local sr sg sb er eg eb r g b
|
|
|
|
read -r sr sg sb < <(hex_parts "$start")
|
|
read -r er eg eb < <(hex_parts "$end")
|
|
|
|
if (( max_index == 0 )); then
|
|
printf '%s' "$start"
|
|
return
|
|
fi
|
|
|
|
r=$(((sr * (max_index - index) + er * index + max_index / 2) / max_index))
|
|
g=$(((sg * (max_index - index) + eg * index + max_index / 2) / max_index))
|
|
b=$(((sb * (max_index - index) + eb * index + max_index / 2) / max_index))
|
|
printf '#%02x%02x%02x' "$r" "$g" "$b"
|
|
}
|
|
|
|
print_gradient() {
|
|
local start_key="$1"
|
|
local end_key="$2"
|
|
local start="${COLORS[$start_key]}"
|
|
local end="${COLORS[$end_key]}"
|
|
local steps=24
|
|
local i hex
|
|
|
|
[[ -n $start && -n $end ]] || return
|
|
hex_valid "$start" && hex_valid "$end" || return
|
|
|
|
printf '\n%s -> %s gradient\n' "$start_key" "$end_key"
|
|
printf ' %s ' "$start"
|
|
for (( i = 0; i < steps; i++ )); do
|
|
hex=$(mix_hex "$start" "$end" "$i" "$((steps - 1))")
|
|
if (( COLOR_OUTPUT )); then
|
|
ansi_bg "$hex"
|
|
printf ' '
|
|
reset_ansi
|
|
else
|
|
printf '%s ' "$hex"
|
|
fi
|
|
done
|
|
printf ' %s\n' "$end"
|
|
}
|
|
|
|
print_neutral_ramp() {
|
|
local mode="$1"
|
|
local direction sort_flag key hex score entry
|
|
local -a keys entries sorted
|
|
|
|
keys=(darker_background dark_background background lighter_background selection muted dark_foreground foreground light_foreground bright_foreground)
|
|
if [[ $mode == "light" ]]; then
|
|
direction="lightest -> darkest"
|
|
sort_flag="-rn"
|
|
else
|
|
direction="darkest -> lightest"
|
|
sort_flag="-n"
|
|
fi
|
|
|
|
for key in "${keys[@]}"; do
|
|
hex="${COLORS[$key]}"
|
|
[[ -n $hex ]] || continue
|
|
hex_valid "$hex" || continue
|
|
score=$(luma_sum "$hex")
|
|
entries+=("$score $key")
|
|
done
|
|
|
|
(( ${#entries[@]} > 0 )) || return
|
|
|
|
mapfile -t sorted < <(printf '%s\n' "${entries[@]}" | sort "$sort_flag")
|
|
|
|
printf '\nNeutral ramp (%s)\n' "$direction"
|
|
for entry in "${sorted[@]}"; do
|
|
key="${entry#* }"
|
|
print_color_row "$key"
|
|
done
|
|
}
|
|
|
|
colors_file=$(resolve_colors_file "$THEME_REF") || {
|
|
echo "Theme not found: $THEME_REF" >&2
|
|
exit 1
|
|
}
|
|
|
|
if [[ ! -f $colors_file ]]; then
|
|
echo "Missing colors.toml: $colors_file" >&2
|
|
exit 1
|
|
fi
|
|
|
|
load_colors "$colors_file"
|
|
|
|
mode="${COLORS[mode]:-dark}"
|
|
|
|
theme_label="$THEME_REF"
|
|
if [[ -z $theme_label ]]; then
|
|
if [[ -f $HOME/.local/state/omarchy/current/theme.name ]]; then
|
|
theme_label=$(<"$HOME/.local/state/omarchy/current/theme.name")
|
|
else
|
|
theme_label="current"
|
|
fi
|
|
fi
|
|
|
|
apply_terminal_osc
|
|
|
|
printf 'Theme: %s\n' "$theme_label"
|
|
printf 'File: %s\n' "$colors_file"
|
|
printf 'Mode: %s\n' "$mode"
|
|
if [[ ${COLORS[foreground]} =~ ^#[0-9A-Fa-f]{6}$ && ${COLORS[background]} =~ ^#[0-9A-Fa-f]{6}$ ]]; then
|
|
printf 'foreground/background contrast: %s:1\n' "$(contrast_ratio "${COLORS[foreground]}" "${COLORS[background]}")"
|
|
fi
|
|
|
|
print_gradient background bright_foreground
|
|
print_neutral_ramp "$mode"
|
|
print_group "Foundation" background foreground accent selection
|
|
print_selection_sample
|
|
print_practical_samples
|
|
print_ansi_palette
|
|
print_group "Normal colors" red yellow orange green cyan blue magenta brown
|
|
print_group "Bright colors" bright_red bright_yellow bright_green bright_cyan bright_blue bright_magenta bright_foreground
|