mirror of
https://github.com/arthur-pbty/arthur-os.git
synced 2026-08-06 12:39:35 +02:00
454 lines
12 KiB
Bash
Executable File
454 lines
12 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
|
|
}
|
|
|
|
clean_toml_value() {
|
|
local value="$1"
|
|
|
|
value="${value#*[\"\']}"
|
|
value="${value%%[\"\']*}"
|
|
printf '%s' "$value"
|
|
}
|
|
|
|
load_colors() {
|
|
local colors_file="$1"
|
|
local key value
|
|
|
|
while IFS='=' read -r key value; do
|
|
key="${key//[\"\' ]/}"
|
|
[[ $key && $key != \#* ]] || continue
|
|
value=$(clean_toml_value "$value")
|
|
COLORS[$key]="$value"
|
|
done <"$colors_file"
|
|
|
|
# Legacy semantic names win when present so older themes keep their intended
|
|
# primary text/background colors even if they also had dimmer fg/bg slots.
|
|
[[ ${COLORS[background]} ]] && COLORS[bg]="${COLORS[background]}"
|
|
[[ ${COLORS[foreground]} ]] && COLORS[fg]="${COLORS[foreground]}"
|
|
[[ ${COLORS[bg]} ]] || COLORS[bg]="${COLORS[color0]}"
|
|
[[ ${COLORS[fg]} ]] || COLORS[fg]="${COLORS[color7]}"
|
|
[[ ${COLORS[light_fg]} ]] || COLORS[light_fg]="${COLORS[color7]:-${COLORS[fg]}}"
|
|
[[ ${COLORS[bright_fg]} ]] || COLORS[bright_fg]="${COLORS[color15]:-${COLORS[fg]}}"
|
|
[[ ${COLORS[selection_background]} ]] || COLORS[selection_background]="${COLORS[selection]}"
|
|
[[ ${COLORS[selection_foreground]} ]] || COLORS[selection_foreground]="${COLORS[bright_fg]}"
|
|
|
|
if hex_valid "${COLORS[bg]}"; then
|
|
[[ ${COLORS[dark_bg]} ]] || COLORS[dark_bg]=$(mix_hex "${COLORS[bg]}" "#000000" 1 4)
|
|
[[ ${COLORS[darker_bg]} ]] || COLORS[darker_bg]=$(mix_hex "${COLORS[bg]}" "#000000" 1 2)
|
|
fi
|
|
}
|
|
|
|
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
|
|
|
|
if [[ -x $OMARCHY_PATH/bin/omarchy-theme-osc ]]; then
|
|
"$OMARCHY_PATH/bin/omarchy-theme-osc" "$colors_file" || true
|
|
else
|
|
omarchy-theme-osc "$colors_file" || true
|
|
fi
|
|
}
|
|
|
|
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[bg]}}"
|
|
|
|
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[bg]}"
|
|
local fg="${COLORS[fg]}"
|
|
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[fg]}"
|
|
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[fg]}"
|
|
printf '\n '
|
|
paint_segment ' selected menu row ' "${COLORS[selection_foreground]}" "${COLORS[selection_background]}"
|
|
printf '\n '
|
|
paint_segment ' status/inverse ' "${COLORS[bg]}" "${COLORS[fg]}"
|
|
paint_segment ' inactive surface ' "${COLORS[fg]}" "${COLORS[lighter_bg]}"
|
|
paint_segment ' urgent ' "${COLORS[bg]}" "${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 bg red green yellow blue magenta cyan fg
|
|
print_palette_strip bright muted bright_red bright_green bright_yellow bright_blue bright_magenta bright_cyan bright_fg
|
|
}
|
|
|
|
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_bg dark_bg bg lighter_bg selection muted dark_fg fg light_fg bright_fg)
|
|
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]}"
|
|
if [[ -z $mode && ${COLORS[bg]} =~ ^#[0-9A-Fa-f]{6}$ ]]; then
|
|
if (( $(luma_sum "${COLORS[bg]}") > 1275000 )); then
|
|
mode="light"
|
|
else
|
|
mode="dark"
|
|
fi
|
|
fi
|
|
[[ -n $mode ]] || 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[fg]} =~ ^#[0-9A-Fa-f]{6}$ && ${COLORS[bg]} =~ ^#[0-9A-Fa-f]{6}$ ]]; then
|
|
printf 'fg/bg contrast: %s:1\n' "$(contrast_ratio "${COLORS[fg]}" "${COLORS[bg]}")"
|
|
fi
|
|
|
|
print_gradient bg bright_fg
|
|
print_neutral_ramp "$mode"
|
|
print_group "Foundation" bg fg 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_fg
|