#!/bin/bash set -euo pipefail : ${HOME:="/Users/$(whoami)"} : ${XDG_CONFIG_HOME:=$HOME/.config} : ${XDG_DATA_HOME:=$HOME/.local/share} _sed_i() { if [[ "$(uname)" == "Darwin" ]]; then sed -i '' "$@" else sed -i'' "$@" fi } is_auto_dark_mode() { if [[ "$OSTYPE" == "darwin"* ]]; then defaults read -g AppleInterfaceStyleSwitchesAutomatically >/dev/null 2>&1 else true fi } is_ubuntu_desktop() { [[ "$(lsb_release -is)" == "Ubuntu" ]] \ && dpkg -l | grep -q desktop } is_dark_mode() { if [[ "$OSTYPE" == "darwin"* ]]; then defaults read -g AppleInterfaceStyle >/dev/null 2>&1 elif [[ -n "${SSH_CONNECTION:-}" ]]; then true elif is_ubuntu_desktop ; then [[ "$(gsettings get org.gnome.desktop.interface color-scheme 2>&1)" != *'light'* ]] else true fi } set_macos_dark_mode_to() { osascript -e 'tell application "System Events" to tell appearance preferences to set dark mode to '"$1" } set_dark_mode() { if [[ "$OSTYPE" == "darwin"* ]]; then set_macos_dark_mode_to "true" elif is_ubuntu_desktop ; then gsettings set org.gnome.desktop.interface color-scheme prefer-dark > /dev/null || true fi } unset_dark_mode() { if [[ "$OSTYPE" == "darwin"* ]]; then set_macos_dark_mode_to "false" elif is_ubuntu_desktop ; then gsettings set org.gnome.desktop.interface color-scheme prefer-light > /dev/null || true fi } get_mode() { local mode="${1:-}" if [[ -z "$mode" ]]; then if is_dark_mode ; then mode="dark" else mode="light" fi fi if [[ "$mode" != "dark" && "$mode" != "light" ]] then >&2 echo "ERROR: unsupported mode '$mode'" exit 1 fi if [[ "${mode}" == 'dark' ]] ; then echo "dark" else echo "light" fi } set_mode() { local mode="${1:-}" if [[ "$(get_mode "$mode")" == 'dark' ]] then set_dark_mode else unset_dark_mode fi } # List the available color config options list_colors() { local ext="$1" local dir="$2" if [ ! -e "$dir" ] ; then echo "(none)" return fi find "${dir}/" -mindepth 1 -type f -print0 \ | xargs -r -0 -n 1 basename \ | sed 's/'"$ext"'$//' \ | sort } # Select the first readable file from a list of filepaths # specified as arguments. first_file_in() { cd "$1" shift while [[ $# -ne 0 ]] do if [[ -r "$1" ]] then echo "$(pwd)/$1" return 0 else shift fi done return 1 } color_file() { local dir="$1" local ext="$2" local colors="$3" local mode="$4" if ! first_file_in "$dir" \ "${colors}-${mode}${ext}" \ "${colors}${mode}${ext}" \ "${colors}${ext}" \ "${mode}${ext}" then >&2 echo "ERROR: unable to load $ext colors for $colors $mode from $dir" return 1 fi } # Create or replace colors in a given config file from another # file specifying the color config content. set_colors_in_config() { config_file="$1" colors_file="$2" line_comment_prefix="${3:-## }" if [[ -L "$config_file" ]] then config_file="$(readlink "$config_file")" fi mkdir -p "$(dirname "$config_file")" if [[ -r "$config_file" ]] then _sed_i '/GEN COLORS START/,/GEN COLORS END/d' "$config_file" fi cat >> "$config_file" </dev/null || \ color_file "${alacritty_colors_dir2}" "${alacritty_ext}" "$1" "$2" 2>/dev/null || \ color_file "${alacritty_colors_dir1}" "${alacritty_ext}" "$1" "$2" } alacritty_color_check_exists() { alacritty_color_file "$1" "$2" > /dev/null } alacritty_colors_set() { set_colors_in_config "${alacritty_cfg}" "$(alacritty_color_file "$1" "$2")" touch "$XDG_CONFIG_HOME/alacritty/alacritty${alacritty_ext}" } bat_ext='.sh' bat_cfg="$XDG_DATA_HOME/bat/color.sh" bat_colors_dir="$XDG_CONFIG_HOME/bat/colors" bat_colors() { # list_colors "${bat_ext}" "$bat_colors_dir" bat --list-themes | cat } bat_color_file() { color_file "${bat_colors_dir}" "${bat_ext}" "$1" "$2" } bat_color_check_exists() { bat_color_file "$1" "$2" > /dev/null } bat_colors_set() { local mode="$2" # best for light: Coldark-Cold, Github, Monokai Extended Light, OneHalfLight, gruvbox-light # best for dark: 1337, Coldark-Dark, Monokai Extended, OneHalfDark, gruvbox-dark # works well with both: ansi local theme="1337" if [[ "$mode" == "light" ]] then theme="gruvbox-light" fi [[ -d "$(dirname "${bat_cfg}")" ]] || mkdir -p "$(dirname "${bat_cfg}")" echo "--theme='$theme'" > "${bat_cfg}" } tmux_ext='.conf' tmux_cfg="$XDG_DATA_HOME/tmux/color.conf" tmux_colors_dir="$XDG_CONFIG_HOME/tmux/colors" tmux_colors() { list_colors "${tmux_ext}" "$tmux_colors_dir" } tmux_color_file() { color_file "${tmux_colors_dir}" "${tmux_ext}" "$1" "$2" } tmux_color_check_exists() { tmux_color_file "$1" "$2" > /dev/null } tmux_colors_set() { set_colors_in_config "${tmux_cfg}" "$(tmux_color_file "$1" "$2")" tmux source-file "${tmux_cfg}" } neovim_ext='.lua' neovim_cfg="${XDG_DATA_HOME}/nvim/colors.lua" neovim_colors_dir="$XDG_CONFIG_HOME/nvim/colors" neovim_colors() { list_colors "${neovim_ext}" "$neovim_colors_dir" } neovim_color_file() { color_file "${neovim_colors_dir}" "${neovim_ext}" "$1" "$2" } neovim_color_check_exists() { neovim_color_file "$1" "$2" 2>/dev/null > /dev/null } neovim_colors_set() { colors="$1" mode="$2" if neovim_color_check_exists "${colors}" "${mode}" then set_colors_in_config "${neovim_cfg}" "$(neovim_color_file "$1" "$2")" '-- ' else cat < "${neovim_cfg}" -- GEN COLORS START local theme_mode = "$mode" local theme_colors = "$colors" $(cat "${neovim_colors_dir}/default.lua") -- GEN COLORS END EOF fi } ghostty_cfg="$XDG_DATA_HOME/ghostty/color" ghostty_colors_dir="$XDG_CONFIG_HOME/ghostty/colors" # Check if a custom Ghostty color file exists ghostty_custom_color_file() { local colors="$1" local mode="$2" # Try to find custom color file (similar to other tools) if ! first_file_in "$ghostty_colors_dir" \ "${colors}-${mode}" \ "${colors}${mode}" \ "${colors}" then return 1 fi } # Map colorscheme names to Ghostty built-in theme names ghostty_theme_name() { local colors="$1" local mode="$2" # Direct mappings for common colorschemes case "$colors" in molokai) # echo "Monokai Soda" # echo "Monokai Pro Spectrum" echo "Brogrammer" ;; gruvbox) if [[ "$mode" == "dark" ]]; then echo "Gruvbox Dark" else echo "Gruvbox Light" fi ;; papercolor) if [[ "$mode" == "dark" ]]; then echo "GitHub Dark Default" else echo "GitHub Light Default" fi ;; solarized) if [[ "$mode" == "dark" ]]; then echo "Solarized Dark Patched" else echo "Builtin Solarized Light" fi ;; nord) echo "Nord" ;; dracula) echo "Dracula" ;; *) # Fallback: try to find a matching theme name (case-insensitive) local search_term="$colors" ghostty +list-themes --plain 2>/dev/null | sed 's/ (resources)//' | grep -i "^${search_term}$" | head -n 1 || echo "" ;; esac } ghostty_colors() { { # List custom color files first if [[ -d "$ghostty_colors_dir" ]]; then find "$ghostty_colors_dir" -mindepth 1 -type f -print0 \ | xargs -r -0 -n 1 basename \ | sort fi # Then list built-in themes ghostty +list-themes --plain 2>/dev/null | sed 's/ (resources)//' } | sort -u || echo "(ghostty not found)" } ghostty_color_check_exists() { local colors="$1" local mode="$2" # # First check for custom color file # if ghostty_custom_color_file "$colors" "$mode" >/dev/null 2>&1; then # return 0 # fi # Then check for built-in theme local theme_name theme_name="$(ghostty_theme_name "$colors" "$mode")" if [[ -n "$theme_name" ]]; then return 0 else return 1 fi } ghostty_colors_set() { local colors="$1" local mode="$2" local custom_file local theme_name mkdir -p "$(dirname "$ghostty_cfg")" # First priority: use custom color file if it exists if custom_file="$(ghostty_custom_color_file "$colors" "$mode" 2>/dev/null)"; then cat > "$ghostty_cfg" <&2 echo "WARNING: No Ghostty theme found for '$colors' in $mode mode, skipping Ghostty theme update" return 0 fi cat > "$ghostty_cfg" < "${aux}" cp "${aux}" "${com}" else cp "${col}" "${com}" fi echo "" done echo "[[ common ]]" cat "${com}" rm "${com}" "${aux}" } color_check_exists() { local colors="$1" local mode="$2" if ! tmux_color_check_exists "$colors" "$mode"; then >&2 echo "ERROR: tmux colors '$colors' not found for mode '$mode'" return 1 fi if ! alacritty_color_check_exists "$colors" "$mode"; then >&2 echo "ERROR: alacritty colors '$colors' not found for mode '$mode'" return 1 fi # neovim and bat have fallback mechanisms, so don't require strict checking return 0 } set_colors() { local mode="$1" local colors="$2" if [[ -z "$mode" ]]; then mode="$(get_mode)" fi if [[ -z "$colors" ]]; then colors="$(get_colors "$mode")" fi color_check_exists "$colors" "$mode" bat_colors_set "$colors" "$mode" tmux_colors_set "$colors" "$mode" neovim_colors_set "$colors" "$mode" alacritty_colors_set "$colors" "$mode" ghostty_colors_set "$colors" "$mode" local colors_file colors_file="$(get_default_colors_file "$mode")" mkdir -p "$(dirname "$colors_file")" echo "$colors" > "$colors_file" 2>/dev/null || true } set_theme() { local mode=$1 local colors=$2 set_mode "$mode" set_colors "$mode" "$colors" echo "Set theme to $(get_mode "$mode") $(get_colors "$mode")" } print_help() { local s_name s_name="$(basename $0)" cat <&2 echo "ERROR: Unexpected state!" exit 1