Packages
Multi-surface application runtime for Elixir. One TEA module renders to terminal, browser (LiveView), SSH, and MCP (agents). 30+ widgets, flexbox + CSS grid, AI agent runtime, distributed swarm with CRDTs, time-travel debugging, session recording, sandboxed REPL, and agentic commerce.
Current section
Files
Jump to
Current section
Files
lib/raxol/terminal/ansi/sgr_processor.ex
defmodule Raxol.Terminal.ANSI.SGRProcessor do
@moduledoc """
Processes SGR (Select Graphic Rendition) ANSI escape sequences.
SGR sequences control text formatting attributes like colors, bold, italic, etc.
This module handles parsing SGR parameters and updating terminal styles accordingly.
"""
@doc """
Handles SGR parameters and updates the style state.
## Parameters
- params: String of SGR parameters (e.g., "31", "1;4;31;48;5;196")
- style: Current style state (can be nil or a map)
## Returns
Updated style map
"""
@spec handle_sgr(String.t(), any()) :: map()
def handle_sgr(params, style) when is_binary(params) do
style = ensure_style_map(style)
params
|> parse_params()
|> process_params(style)
end
@doc """
Process SGR codes with parsed parameters.
## Parameters
- codes: List of integer SGR codes
- style: Current style map
## Returns
Updated style map
"""
@spec process_sgr_codes(list(integer()), map()) :: map()
def process_sgr_codes(codes, style) do
style = ensure_style_map(style)
process_params(codes, style)
end
# Private functions
defp ensure_style_map(nil), do: default_style()
defp ensure_style_map(style) when is_map(style), do: style
defp ensure_style_map(_), do: default_style()
defp default_style do
%{
foreground: nil,
background: nil,
bold: false,
italic: false,
underline: false,
blink: false,
reverse: false,
hidden: false,
strikethrough: false,
dim: false,
faint: false,
conceal: false,
fraktur: false,
double_underline: false,
framed: false,
encircled: false,
overlined: false,
# Extended attributes
underline_color: nil,
underline_style: :single,
overline: false
}
end
defp parse_params(""), do: [0]
defp parse_params(params) do
params
|> String.split(";")
|> Enum.map(&parse_int/1)
|> Enum.reject(&is_nil/1)
end
defp parse_int(str) do
case Integer.parse(str) do
{num, ""} -> num
_ -> nil
end
end
defp process_params([], style), do: style
defp process_params([code | rest], style) do
updated_style = apply_sgr_code(code, style, rest)
{_consumed, remaining} = get_consumed_params(code, rest)
process_params(remaining, updated_style)
end
defp apply_sgr_code(0, _style, _rest), do: default_style()
# Text attributes
defp apply_sgr_code(1, style, _rest), do: Map.put(style, :bold, true)
defp apply_sgr_code(2, style, _rest), do: %{style | dim: true, faint: true}
defp apply_sgr_code(3, style, _rest), do: Map.put(style, :italic, true)
defp apply_sgr_code(4, style, _rest), do: Map.put(style, :underline, true)
defp apply_sgr_code(5, style, _rest), do: Map.put(style, :blink, true)
# Rapid blink
defp apply_sgr_code(6, style, _rest), do: Map.put(style, :blink, true)
defp apply_sgr_code(7, style, _rest), do: Map.put(style, :reverse, true)
defp apply_sgr_code(8, style, _rest),
do: %{style | hidden: true, conceal: true}
defp apply_sgr_code(9, style, _rest), do: Map.put(style, :strikethrough, true)
# Extended text attributes
defp apply_sgr_code(20, style, _rest), do: Map.put(style, :fraktur, true)
defp apply_sgr_code(21, style, _rest),
do: %{style | double_underline: true, underline: false}
# Reset specific attributes
defp apply_sgr_code(22, style, _rest),
do: %{style | bold: false, dim: false, faint: false}
defp apply_sgr_code(23, style, _rest),
do: %{style | italic: false, fraktur: false}
defp apply_sgr_code(24, style, _rest),
do: %{style | underline: false, double_underline: false}
defp apply_sgr_code(25, style, _rest), do: Map.put(style, :blink, false)
defp apply_sgr_code(27, style, _rest), do: Map.put(style, :reverse, false)
defp apply_sgr_code(28, style, _rest),
do: %{style | hidden: false, conceal: false}
defp apply_sgr_code(29, style, _rest),
do: Map.put(style, :strikethrough, false)
# Framed, encircled, overlined attributes
defp apply_sgr_code(51, style, _rest), do: Map.put(style, :framed, true)
defp apply_sgr_code(52, style, _rest), do: Map.put(style, :encircled, true)
defp apply_sgr_code(53, style, _rest),
do: %{style | overlined: true, overline: true}
defp apply_sgr_code(54, style, _rest),
do: %{style | framed: false, encircled: false}
defp apply_sgr_code(55, style, _rest),
do: %{style | overlined: false, overline: false}
# Basic foreground colors (30-37)
defp apply_sgr_code(code, style, _rest) when code >= 30 and code <= 37 do
Map.put(style, :foreground, basic_color(code - 30))
end
# Extended foreground color (38)
defp apply_sgr_code(38, style, [5, color | _rest])
when color >= 0 and color <= 255 do
Map.put(style, :foreground, {:indexed, color})
end
defp apply_sgr_code(38, style, [2, r, g, b | _rest])
when r >= 0 and r <= 255 and g >= 0 and g <= 255 and b >= 0 and b <= 255 do
Map.put(style, :foreground, {:rgb, r, g, b})
end
# Default foreground (39)
defp apply_sgr_code(39, style, _rest), do: Map.put(style, :foreground, nil)
# Basic background colors (40-47)
defp apply_sgr_code(code, style, _rest) when code >= 40 and code <= 47 do
Map.put(style, :background, basic_color(code - 40))
end
# Extended background color (48)
defp apply_sgr_code(48, style, [5, color | _rest])
when color >= 0 and color <= 255 do
Map.put(style, :background, {:indexed, color})
end
defp apply_sgr_code(48, style, [2, r, g, b | _rest])
when r >= 0 and r <= 255 and g >= 0 and g <= 255 and b >= 0 and b <= 255 do
Map.put(style, :background, {:rgb, r, g, b})
end
# Default background (49)
defp apply_sgr_code(49, style, _rest), do: Map.put(style, :background, nil)
# Underline color (58)
defp apply_sgr_code(58, style, [5, color | _rest])
when color >= 0 and color <= 255 do
Map.put(style, :underline_color, {:indexed, color})
end
defp apply_sgr_code(58, style, [2, r, g, b | _rest])
when r >= 0 and r <= 255 and g >= 0 and g <= 255 and b >= 0 and b <= 255 do
Map.put(style, :underline_color, {:rgb, r, g, b})
end
# Default underline color (59)
defp apply_sgr_code(59, style, _rest),
do: Map.put(style, :underline_color, nil)
# Bright foreground colors (90-97) - set base color and bold
defp apply_sgr_code(code, style, _rest) when code >= 90 and code <= 97 do
%{style | foreground: basic_color(code - 90), bold: true}
end
# Bright background colors (100-107) - just set base color, no bold
defp apply_sgr_code(code, style, _rest) when code >= 100 and code <= 107 do
Map.put(style, :background, basic_color(code - 100))
end
# Unhandled codes - return style unchanged
defp apply_sgr_code(_code, style, _rest), do: style
defp get_consumed_params(38, [5, _ | rest]), do: {2, rest}
defp get_consumed_params(38, [2, _, _, _ | rest]), do: {4, rest}
defp get_consumed_params(48, [5, _ | rest]), do: {2, rest}
defp get_consumed_params(48, [2, _, _, _ | rest]), do: {4, rest}
defp get_consumed_params(58, [5, _ | rest]), do: {2, rest}
defp get_consumed_params(58, [2, _, _, _ | rest]), do: {4, rest}
defp get_consumed_params(_, rest), do: {0, rest}
defp basic_color(0), do: :black
defp basic_color(1), do: :red
defp basic_color(2), do: :green
defp basic_color(3), do: :yellow
defp basic_color(4), do: :blue
defp basic_color(5), do: :magenta
defp basic_color(6), do: :cyan
defp basic_color(7), do: :white
defp basic_color(_), do: nil
end