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/buffer/cell.ex
defmodule Raxol.Terminal.Buffer.Cell do
@moduledoc """
Manages terminal cell operations and attributes.
"""
defstruct [
:char,
:foreground,
:background,
:attributes,
:hyperlink,
:width,
# Virtual field for backward compatibility
:fg,
# Virtual field for backward compatibility
:bg
]
@type t :: %__MODULE__{
char: String.t(),
foreground: integer() | atom() | String.t(),
background: integer() | atom() | String.t(),
attributes: map(),
hyperlink: String.t() | nil,
width: integer()
}
@doc """
Creates a new cell with default settings.
"""
def new(opts \\ [])
def new(opts) when is_list(opts) do
case opts do
[] -> %__MODULE__{}
_ -> new_from_keyword(opts)
end
end
def new(arg) when is_binary(arg) do
%__MODULE__{char: arg, fg: nil, bg: nil}
end
def new(opts) when is_map(opts) do
new_from_map(opts)
end
def new(_), do: %__MODULE__{}
@doc """
Creates a new cell with the specified character and style.
"""
def new(char, style) when is_binary(char) and is_map(style) do
# Log.info("DEBUG: Cell.new/2 - creating cell with char: #{inspect(char)}, style: #{inspect(style)}")
foreground = extract_foreground(style)
background = extract_background(style)
cell = %__MODULE__{
char: char,
foreground: foreground,
background: background,
attributes: extract_attributes(style),
hyperlink: extract_hyperlink(style),
width: extract_width(style),
# Virtual field for backward compatibility
fg: foreground,
# Virtual field for backward compatibility
bg: background
}
# Log.info("DEBUG: Cell.new/2 - created cell: #{inspect(cell)}")
cell
end
@doc """
Gets the cell's character.
"""
def get_char(%__MODULE__{} = cell) do
cell.char
end
@doc """
Sets the cell's character.
"""
def set_char(%__MODULE__{} = cell, char) when is_binary(char) do
%{cell | char: char}
end
@doc """
Gets the cell's foreground color.
"""
def get_foreground(%__MODULE__{} = cell) do
cell.foreground
end
@doc """
Sets the cell's foreground color.
"""
def set_foreground(%__MODULE__{} = cell, color) when is_integer(color) do
%{cell | foreground: color}
end
@doc """
Gets the cell's background color.
"""
def get_background(%__MODULE__{} = cell) do
cell.background
end
@doc """
Sets the cell's background color.
"""
def set_background(%__MODULE__{} = cell, color) when is_integer(color) do
%{cell | background: color}
end
@doc """
Gets the cell's attributes.
"""
def get_attributes(%__MODULE__{} = cell) do
cell.attributes
end
@doc """
Sets the cell's attributes.
"""
def set_attributes(%__MODULE__{} = cell, attributes)
when is_map(attributes) do
%{cell | attributes: attributes}
end
@doc """
Gets the cell's hyperlink.
"""
def get_hyperlink(%__MODULE__{} = cell) do
cell.hyperlink
end
@doc """
Sets the cell's hyperlink.
"""
def set_hyperlink(%__MODULE__{} = cell, hyperlink)
when is_binary(hyperlink) or is_nil(hyperlink) do
%{cell | hyperlink: hyperlink}
end
@doc """
Gets the cell's width.
"""
def get_width(%__MODULE__{} = cell) do
cell.width
end
@doc """
Sets the cell's width.
"""
def set_width(%__MODULE__{} = cell, width)
when is_integer(width) and width > 0 do
%{cell | width: width}
end
# Backward compatibility functions for old :fg and :bg field names
@doc """
Gets the cell's foreground color (backward compatibility).
"""
def fg(%__MODULE__{} = cell) do
cell.foreground
end
@doc """
Sets the cell's foreground color (backward compatibility).
"""
def fg(%__MODULE__{} = cell, color) do
%{cell | foreground: color}
end
@doc """
Gets the cell's background color (backward compatibility).
"""
def bg(%__MODULE__{} = cell) do
cell.background
end
@doc """
Sets the cell's background color (backward compatibility).
"""
def bg(%__MODULE__{} = cell, color) do
%{cell | background: color}
end
@doc """
Checks if the cell is empty.
"""
def empty?(%__MODULE__{} = cell) do
cell.char == " "
end
@doc """
Resets a cell to its default state.
"""
def reset(%__MODULE__{} = _cell) do
%__MODULE__{}
end
@doc """
Copies attributes from one cell to another.
"""
def copy_attributes(%__MODULE__{} = source, %__MODULE__{} = target) do
%{
target
| foreground: source.foreground,
background: source.background,
attributes: source.attributes,
hyperlink: source.hyperlink
}
end
@doc """
Validates a cell's data.
Returns true if the cell is valid, false otherwise.
"""
def valid?(%__MODULE__{} = cell) do
valid_char?(cell.char) and
valid_color?(cell.foreground) and
valid_color?(cell.background) and
valid_attributes?(cell.attributes)
end
def valid?(_), do: false
defp valid_char?(char) when is_binary(char) do
String.length(char) == 1
end
defp valid_char?(_), do: false
defp valid_color?(color) when is_atom(color) do
color in [
:default,
:black,
:red,
:green,
:yellow,
:blue,
:magenta,
:cyan,
:white
]
end
defp valid_color?(color) when is_binary(color) do
String.match?(color, ~r/^#[0-9A-Fa-f]{6}$/)
end
defp valid_color?(color) when is_integer(color) do
color >= 0 and color <= 255
end
defp valid_color?(_), do: false
defp valid_attributes?(attrs) when is_map(attrs) do
valid_attribute_keys = [
:bold,
:italic,
:underline,
:strikethrough,
:blink,
:reverse,
:faint,
:conceal,
:fraktur,
:double_underline,
:framed,
:encircled,
:overlined
]
Enum.all?(attrs, fn {key, value} ->
key in valid_attribute_keys and is_boolean(value)
end)
end
defp valid_attributes?(_), do: false
defp extract_foreground(style) do
case Map.get(style, :foreground) do
nil ->
# Default white
7
fg ->
fg
end
end
defp extract_background(style) do
case Map.get(style, :background) do
nil ->
# Default black
0
bg ->
bg
end
end
defp extract_attributes(style) do
case Map.get(style, :attributes) do
nil ->
# Extract attributes from TextFormatting struct fields
%{
bold: Map.get(style, :bold, false),
italic: Map.get(style, :italic, false),
underline: Map.get(style, :underline, false),
strikethrough: Map.get(style, :strikethrough, false),
blink: Map.get(style, :blink, false),
reverse: Map.get(style, :reverse, false),
faint: Map.get(style, :faint, false),
conceal: Map.get(style, :conceal, false),
fraktur: Map.get(style, :fraktur, false),
double_underline: Map.get(style, :double_underline, false),
framed: Map.get(style, :framed, false),
encircled: Map.get(style, :encircled, false),
overlined: Map.get(style, :overlined, false)
}
attrs ->
attrs
end
end
defp extract_hyperlink(style) do
case Map.get(style, :hyperlink) do
nil ->
case style do
%{hyperlink: link} -> link
_ -> nil
end
link ->
link
end
end
defp extract_width(style) do
case Map.get(style, :width) do
nil ->
case style do
%{width: w} when not is_nil(w) -> w
_ -> 1
end
w ->
w
end
end
defp new_from_map(opts) do
foreground = Map.get(opts, :foreground, 7)
background = Map.get(opts, :background, 0)
%__MODULE__{
char: Map.get(opts, :char, " "),
foreground: foreground,
background: background,
attributes: Map.get(opts, :attributes, %{}),
hyperlink: Map.get(opts, :hyperlink, nil),
width: Map.get(opts, :width, 1),
# Virtual field for backward compatibility
fg: foreground,
# Virtual field for backward compatibility
bg: background
}
end
defp new_from_keyword(opts) do
foreground = Keyword.get(opts, :foreground, 7)
background = Keyword.get(opts, :background, 0)
%__MODULE__{
char: Keyword.get(opts, :char, " "),
foreground: foreground,
background: background,
attributes: Keyword.get(opts, :attributes, %{}),
hyperlink: Keyword.get(opts, :hyperlink, nil),
width: Keyword.get(opts, :width, 1),
# Virtual field for backward compatibility
fg: foreground,
# Virtual field for backward compatibility
bg: background
}
end
end