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 core.ex
Raw

lib/raxol/terminal/screen_buffer/core.ex

defmodule Raxol.Terminal.ScreenBuffer.Core do
@moduledoc """
Core functionality for screen buffer creation, initialization, and basic queries.
Consolidates: Initializer, Common, Helpers, and basic state management.
"""
@default_width Raxol.Core.Defaults.terminal_width()
@default_height Raxol.Core.Defaults.terminal_height()
@default_scrollback Raxol.Core.Defaults.scrollback_limit()
alias Raxol.Terminal.ANSI.TextFormatting
alias Raxol.Terminal.Cell
# Minimal, focused struct definition
defstruct [
:cells,
:scrollback,
:scrollback_limit,
:selection,
:scroll_region,
:scroll_position,
:width,
:height,
:damage_regions,
:default_style,
cursor_position: {0, 0},
cursor_style: :block,
cursor_visible: true,
cursor_blink: true,
alternate_screen: false
]
@type t :: %__MODULE__{
cells: list(list(Cell.t())),
scrollback: list(list(Cell.t())),
scrollback_limit: non_neg_integer(),
selection:
nil
| {non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer()},
scroll_region: nil | {non_neg_integer(), non_neg_integer()},
scroll_position: non_neg_integer(),
width: non_neg_integer(),
height: non_neg_integer(),
damage_regions: list(tuple()),
default_style: map(),
cursor_position: {non_neg_integer(), non_neg_integer()},
cursor_style: atom(),
cursor_visible: boolean(),
cursor_blink: boolean(),
alternate_screen: boolean()
}
@doc """
Creates a new screen buffer with the specified dimensions.
"""
def new(width, height, scrollback_limit \\ @default_scrollback) do
width = validate_dimension(width, @default_width)
height = validate_dimension(height, @default_height)
scrollback_limit = validate_dimension(scrollback_limit, @default_scrollback)
%__MODULE__{
cells: create_empty_grid(width, height),
scrollback: [],
scrollback_limit: scrollback_limit,
selection: nil,
scroll_region: nil,
scroll_position: 0,
width: width,
height: height,
damage_regions: [],
default_style: TextFormatting.new(),
cursor_position: {0, 0},
cursor_style: :block,
cursor_visible: true,
cursor_blink: true,
alternate_screen: false
}
end
@doc """
Resizes the buffer to new dimensions.
"""
def resize(buffer, new_width, new_height) do
new_width = validate_dimension(new_width, buffer.width)
new_height = validate_dimension(new_height, buffer.height)
new_cells =
resize_grid(
buffer.cells,
buffer.width,
buffer.height,
new_width,
new_height
)
%{
buffer
| cells: new_cells,
width: new_width,
height: new_height,
damage_regions: [{0, 0, new_width - 1, new_height - 1}]
}
|> adjust_cursor_after_resize()
end
@doc """
Gets the buffer dimensions.
"""
def get_dimensions(%{width: width, height: height}), do: {width, height}
@doc """
Gets the buffer width.
"""
def get_width(%{width: width}), do: width
@doc """
Gets the buffer height.
"""
def get_height(%{height: height}), do: height
@doc """
Checks if coordinates are within buffer bounds.
"""
def within_bounds?(%{width: width, height: height}, x, y) do
x >= 0 and x < width and y >= 0 and y < height
end
@doc """
Gets a cell at the specified coordinates.
"""
def get_cell(buffer, x, y) when x >= 0 and y >= 0 do
if within_bounds?(buffer, x, y) do
buffer.cells
|> Enum.at(y, [])
|> Enum.at(x)
else
nil
end
end
@doc """
Gets the character at the specified coordinates.
"""
def get_char(buffer, x, y) do
case get_cell(buffer, x, y) do
%Cell{char: char} -> char || " "
_ -> " "
end
end
@doc """
Gets a line of cells.
"""
def get_line(buffer, y) when y >= 0 and y < buffer.height do
Enum.at(buffer.cells, y, [])
end
def get_line(_buffer, _y), do: []
@doc """
Clears the entire buffer.
"""
def clear(buffer) do
%{
buffer
| cells: create_empty_grid(buffer.width, buffer.height),
damage_regions: [{0, 0, buffer.width - 1, buffer.height - 1}]
}
end
# Private helper functions
defp validate_dimension(dimension, _default)
when is_integer(dimension) and dimension > 0 do
dimension
end
defp validate_dimension(_dimension, default), do: default
defp create_empty_grid(width, height) when width > 0 and height > 0 do
for _y <- 0..(height - 1) do
for _x <- 0..(width - 1) do
Cell.empty()
end
end
end
defp create_empty_grid(_width, _height), do: []
defp resize_grid(cells, old_width, old_height, new_width, new_height) do
# Crop or extend height
cells =
if new_height < old_height do
Enum.take(cells, new_height)
else
cells ++ create_empty_grid(new_width, new_height - old_height)
end
# Crop or extend width for each row
Enum.map(cells, fn row ->
if new_width < old_width do
Enum.take(row, new_width)
else
row ++ List.duplicate(Cell.empty(), new_width - length(row))
end
end)
end
defp adjust_cursor_after_resize(buffer) do
{x, y} = buffer.cursor_position
new_x = min(x, buffer.width - 1)
new_y = min(y, buffer.height - 1)
%{buffer | cursor_position: {new_x, new_y}}
end
@doc """
Converts buffer to legacy cell grid format for backward compatibility.
"""
def to_cell_grid(buffer) do
buffer.cells
end
end