Current section
Files
Jump to
Current section
Files
lib/esc/theme.ex
defmodule Esc.Theme do
@moduledoc """
Theme definitions for terminal styling.
Themes provide a consistent color palette including:
- 16 ANSI colors (ansi_0 through ansi_15)
- Background and foreground colors
- Semantic colors for common UI purposes
## Semantic Colors
Semantic colors provide meaningful names for common use cases:
- `:header` - Headers, titles (defaults to cyan/ansi_6)
- `:emphasis` - Important text (defaults to blue/ansi_4)
- `:warning` - Warning messages (defaults to yellow/ansi_3)
- `:error` - Error messages (defaults to red/ansi_1)
- `:success` - Success messages (defaults to green/ansi_2)
- `:muted` - Subdued text, borders (defaults to bright black/ansi_8)
## Usage
Esc.set_theme(:nord)
# Use semantic colors in styles
style() |> theme_foreground(:error) |> render("Error!")
"""
@type rgb :: {0..255, 0..255, 0..255}
@type t :: %__MODULE__{
name: atom(),
# Standard ANSI 16 colors
ansi_0: rgb(),
ansi_1: rgb(),
ansi_2: rgb(),
ansi_3: rgb(),
ansi_4: rgb(),
ansi_5: rgb(),
ansi_6: rgb(),
ansi_7: rgb(),
ansi_8: rgb(),
ansi_9: rgb(),
ansi_10: rgb(),
ansi_11: rgb(),
ansi_12: rgb(),
ansi_13: rgb(),
ansi_14: rgb(),
ansi_15: rgb(),
# Terminal colors
background: rgb(),
foreground: rgb(),
# Semantic colors (nil = derive from ANSI)
header: rgb() | nil,
emphasis: rgb() | nil,
warning: rgb() | nil,
error: rgb() | nil,
success: rgb() | nil,
muted: rgb() | nil
}
defstruct [
:name,
# Standard ANSI 16 colors
:ansi_0,
:ansi_1,
:ansi_2,
:ansi_3,
:ansi_4,
:ansi_5,
:ansi_6,
:ansi_7,
:ansi_8,
:ansi_9,
:ansi_10,
:ansi_11,
:ansi_12,
:ansi_13,
:ansi_14,
:ansi_15,
# Terminal colors
:background,
:foreground,
# Semantic colors
:header,
:emphasis,
:warning,
:error,
:success,
:muted
]
@doc """
Gets a color from a theme by name.
Handles both direct palette colors (ansi_0..ansi_15, background, foreground)
and semantic colors (header, emphasis, warning, error, success, muted).
Semantic colors are derived from the ANSI palette if not explicitly set.
## Examples
iex> theme = Esc.Theme.Palette.get(:nord)
iex> Esc.Theme.color(theme, :error)
{191, 97, 106}
iex> Esc.Theme.color(theme, :ansi_4)
{129, 161, 193}
"""
@spec color(t(), atom()) :: rgb() | nil
def color(%__MODULE__{} = theme, name) do
case Map.get(theme, name) do
nil -> derive_semantic_color(theme, name)
color -> color
end
end
# Map friendly color names to ANSI field names
@color_to_ansi %{
black: :ansi_0,
red: :ansi_1,
green: :ansi_2,
yellow: :ansi_3,
blue: :ansi_4,
magenta: :ansi_5,
cyan: :ansi_6,
white: :ansi_7,
bright_black: :ansi_8,
bright_red: :ansi_9,
bright_green: :ansi_10,
bright_yellow: :ansi_11,
bright_blue: :ansi_12,
bright_magenta: :ansi_13,
bright_cyan: :ansi_14,
bright_white: :ansi_15
}
# Default semantic color derivations from ANSI palette
defp derive_semantic_color(theme, :header), do: theme.ansi_6
defp derive_semantic_color(theme, :emphasis), do: theme.ansi_4
defp derive_semantic_color(theme, :warning), do: theme.ansi_3
defp derive_semantic_color(theme, :error), do: theme.ansi_1
defp derive_semantic_color(theme, :success), do: theme.ansi_2
defp derive_semantic_color(theme, :muted), do: theme.ansi_8
# Map friendly color names to ANSI slots
defp derive_semantic_color(theme, name) do
case Map.get(@color_to_ansi, name) do
nil -> nil
ansi_field -> Map.get(theme, ansi_field)
end
end
end