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 screen_buffer data_adapter.ex
Raw

lib/raxol/terminal/screen_buffer/data_adapter.ex

defmodule Raxol.Terminal.ScreenBuffer.DataAdapter do
@moduledoc """
Data structure adapter for ScreenBuffer operations.
Provides bidirectional conversion between the two buffer formats:
- ScreenBuffer.Core format: `:cells` (list of lists of Cell structs)
- LineOperations format: `:lines` (map with integer keys to line lists)
This adapter allows seamless interoperability between different buffer
operation layers without requiring architectural changes.
"""
@default_width Raxol.Core.Defaults.terminal_width()
@default_height Raxol.Core.Defaults.terminal_height()
alias Raxol.Terminal.ANSI.TextFormatting
alias Raxol.Terminal.Cell
@doc """
Convert buffer from cells format to lines format.
Transforms `buffer.cells` (list of lists) into a `:lines` map
where keys are row indices and values are cell lists.
"""
def cells_to_lines(buffer) when is_map(buffer) do
case Map.get(buffer, :cells) do
nil ->
# Already has lines format or invalid buffer
buffer
cells when is_list(cells) ->
lines_map =
cells
|> Enum.with_index()
|> Enum.into(%{}, fn {line_cells, index} -> {index, line_cells} end)
Map.put(buffer, :lines, lines_map)
_ ->
# Invalid cells format
buffer
end
end
@doc """
Convert buffer from lines format back to cells format.
Transforms `:lines` map back into `buffer.cells` (list of lists).
"""
def lines_to_cells(buffer, lines_map)
when is_map(buffer) and is_map(lines_map) do
height = Map.get(buffer, :height, @default_height)
cells =
0..(height - 1)
|> Enum.map(fn row_index ->
Map.get(lines_map, row_index, [])
end)
buffer
|> Map.put(:cells, cells)
# Remove temporary lines map
|> Map.delete(:lines)
end
@doc """
Execute an operation function with the buffer temporarily in lines format.
This is the key function that allows LineOperations to work with
ScreenBuffer.Core structures without permanent conversion.
The operation function can return either:
- A modified buffer map
- A tuple where the second element is the modified buffer map
"""
def with_lines_format(buffer, operation_fn)
when is_map(buffer) and is_function(operation_fn, 1) do
# Convert to lines format
buffer_with_lines = cells_to_lines(buffer)
# Execute operation
result = operation_fn.(buffer_with_lines)
# Handle different return types
case result do
{data, result_buffer} when is_map(result_buffer) ->
# Operation returned a tuple with buffer as second element
case Map.get(result_buffer, :lines) do
lines_map when is_map(lines_map) ->
converted_buffer = lines_to_cells(result_buffer, lines_map)
{data, converted_buffer}
_ ->
{data, result_buffer}
end
result_buffer when is_map(result_buffer) ->
# Operation returned just a buffer
case Map.get(result_buffer, :lines) do
lines_map when is_map(lines_map) ->
lines_to_cells(result_buffer, lines_map)
_ ->
# Operation didn't modify lines, return as-is
result_buffer
end
other ->
# Return as-is for any other case
other
end
end
@doc """
Check if buffer uses cells format (list of lists).
"""
def has_cells_format?(buffer) when is_map(buffer) do
case Map.get(buffer, :cells) do
cells when is_list(cells) -> true
_ -> false
end
end
@doc """
Check if buffer uses lines format (map).
"""
def has_lines_format?(buffer) when is_map(buffer) do
case Map.get(buffer, :lines) do
lines when is_map(lines) -> true
_ -> false
end
end
@doc """
Get a line from buffer regardless of format.
"""
def get_line(buffer, y, default) when is_map(buffer) and is_integer(y) do
cond do
has_lines_format?(buffer) ->
Map.get(buffer.lines, y, default)
has_cells_format?(buffer) ->
if y >= 0 and y < length(buffer.cells) do
Enum.at(buffer.cells, y, default)
else
default
end
true ->
default
end
end
@doc """
Set a line in buffer regardless of format.
"""
def set_line(buffer, y, line)
when is_map(buffer) and is_integer(y) and is_list(line) do
cond do
has_lines_format?(buffer) ->
%{buffer | lines: Map.put(buffer.lines, y, line)}
has_cells_format?(buffer) ->
replace_cell_line(buffer, y, line)
true ->
initialize_and_set_line(buffer, y, line)
end
end
defp replace_cell_line(buffer, y, line) do
if y >= 0 and y < length(buffer.cells) do
%{buffer | cells: List.replace_at(buffer.cells, y, line)}
else
buffer
end
end
defp initialize_and_set_line(buffer, y, line) do
height = Map.get(buffer, :height, @default_height)
width = Map.get(buffer, :width, @default_width)
default_style = Map.get(buffer, :default_style, %TextFormatting{})
cells = create_empty_cells(width, height, default_style)
updated_cells =
if y >= 0 and y < height do
List.replace_at(cells, y, line)
else
cells
end
Map.put(buffer, :cells, updated_cells)
end
@doc """
Create empty cells structure for initialization.
"""
def create_empty_cells(width, height, default_style) do
empty_line = create_empty_line(width, default_style)
List.duplicate(empty_line, height)
end
@doc """
Create an empty line with the specified width and style.
"""
def create_empty_line(width, default_style) do
blank_cell = %Cell{
char: " ",
style: default_style || %TextFormatting{},
dirty: false,
wide_placeholder: false,
sixel: false
}
List.duplicate(blank_cell, width)
end
end