Packages

Terminal emulation and driver infrastructure for Raxol. ANSI parsing, screen buffers, command processing, cursor management, input handling, session management, and termbox2 NIF integration.

Current section

Files

Jump to
raxol_terminal lib raxol terminal style_buffer.ex
Raw

lib/raxol/terminal/style_buffer.ex

defmodule Raxol.Terminal.StyleBuffer do
@moduledoc """
Manages terminal style state and operations.
This module handles text attributes, colors, and formatting for terminal output.
"""
@default_width Raxol.Core.Defaults.terminal_width()
@default_height Raxol.Core.Defaults.terminal_height()
@type style :: %{
foreground: String.t() | nil,
background: String.t() | nil,
bold: boolean(),
italic: boolean(),
underline: boolean(),
attributes: [atom()]
}
@type position :: {non_neg_integer(), non_neg_integer()}
@type region :: {position(), position()}
@type t :: %__MODULE__{
current_style: style(),
default_style: style(),
style_map: %{position() => style()},
width: non_neg_integer(),
height: non_neg_integer()
}
defstruct current_style: %{
foreground: nil,
background: nil,
bold: false,
italic: false,
underline: false,
attributes: []
},
default_style: %{
foreground: nil,
background: nil,
bold: false,
italic: false,
underline: false,
attributes: []
},
style_map: %{},
width: @default_width,
height: @default_height
@doc """
Creates a new style buffer with the given dimensions.
"""
@spec new(non_neg_integer(), non_neg_integer()) :: t()
def new(width, height) do
default_style = %{
foreground: nil,
background: nil,
bold: false,
italic: false,
underline: false,
attributes: []
}
%__MODULE__{
current_style: default_style,
default_style: default_style,
style_map: %{},
width: width,
height: height
}
end
@doc """
Sets the foreground color.
"""
@spec set_foreground(t(), String.t()) :: t()
def set_foreground(buffer, color) do
%{buffer | current_style: Map.put(buffer.current_style, :foreground, color)}
end
@doc """
Sets the background color.
"""
@spec set_background(t(), String.t()) :: t()
def set_background(buffer, color) do
%{buffer | current_style: Map.put(buffer.current_style, :background, color)}
end
@doc """
Sets text attributes (list of atoms).
"""
@spec set_attributes(t(), [atom()]) :: t()
def set_attributes(buffer, attrs) when is_list(attrs) do
%{buffer | current_style: Map.put(buffer.current_style, :attributes, attrs)}
end
@doc """
Gets the current style.
"""
@spec get_style(t()) :: style()
def get_style(%__MODULE__{current_style: style}), do: style
@doc """
Resets the style to default.
"""
@spec reset_style(t()) :: t()
def reset_style(buffer) do
%{buffer | current_style: buffer.default_style}
end
@doc """
Gets the style at a specific position (x, y).
Returns the style at the position or the current style if not set.
"""
@spec get_style_at(t(), non_neg_integer(), non_neg_integer()) :: style()
def get_style_at(%__MODULE__{style_map: map, current_style: style}, x, y) do
Map.get(map, {x, y}, style)
end
@doc """
Applies a style to a rectangular region.
"""
@spec apply_style_to_region(
t(),
style(),
{non_neg_integer(), non_neg_integer()},
{non_neg_integer(), non_neg_integer()}
) :: t()
def apply_style_to_region(buffer, style, {x1, y1}, {x2, y2}) do
coords = for x <- x1..x2, y <- y1..y2, do: {x, y}
new_map =
Enum.reduce(coords, buffer.style_map, fn pos, acc ->
Map.put(acc, pos, style)
end)
%{buffer | style_map: new_map}
end
@doc """
Sets the default style.
"""
@spec set_default_style(t(), style()) :: t()
def set_default_style(buffer, style) do
%{buffer | default_style: style}
end
@doc """
Gets the default style.
"""
@spec get_default_style(t()) :: style()
def get_default_style(%__MODULE__{default_style: style}), do: style
@doc """
Merges two styles.
"""
@spec merge_styles(style(), style()) :: style()
def merge_styles(style1, style2) do
Map.merge(style1, style2)
end
@doc """
Validates a style map.
"""
@spec validate_style(style()) :: :ok | {:error, String.t()}
def validate_style(style) when is_map(style) do
# Basic validation - check for known style keys
valid_keys = [
:foreground,
:background,
:bold,
:italic,
:underline,
:blink,
:reverse,
:hidden,
:strikethrough
]
invalid_keys = Map.keys(style) -- valid_keys
validate_key_result(invalid_keys == [], invalid_keys)
end
def validate_style(_), do: {:error, "Style must be a map"}
defp validate_key_result(true, _invalid_keys), do: :ok
defp validate_key_result(false, invalid_keys),
do: {:error, "Invalid style keys: #{inspect(invalid_keys)}"}
end