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
raxol lib raxol terminal screen_buffer erase_operations.ex
Raw

lib/raxol/terminal/screen_buffer/erase_operations.ex

defmodule Raxol.Terminal.ScreenBuffer.EraseOperations do
@moduledoc """
Handles all erase operations for the terminal screen buffer.
This module provides focused functionality for erasing content from the buffer,
including line erasing, display erasing, and region clearing operations.
"""
alias Raxol.Terminal.Cell
alias Raxol.Terminal.ScreenBuffer
@doc """
Erases from cursor to end of display.
"""
@spec erase_from_cursor_to_end(
ScreenBuffer.t(),
non_neg_integer(),
non_neg_integer(),
non_neg_integer(),
non_neg_integer()
) :: ScreenBuffer.t()
def erase_from_cursor_to_end(buffer, x, y, _top, bottom) do
# Log.info("[DEBUG] erase_from_cursor_to_end called with x=#{x}, y=#{y}, bottom=#{bottom}")
empty_cell = Cell.new()
# Process each line
new_cells =
Enum.with_index(buffer.cells)
|> Enum.map(fn {line, line_idx} ->
cond do
# Lines before cursor row remain unchanged
line_idx < y ->
line
# Cursor row: clear from cursor position to end
line_idx == y ->
preserved_cells = Enum.take(line, x)
cleared_cells = List.duplicate(empty_cell, buffer.width - x)
preserved_cells ++ cleared_cells
# Lines after cursor row: clear entirely if within bottom
line_idx <= bottom ->
List.duplicate(empty_cell, buffer.width)
# Lines beyond bottom remain unchanged
true ->
line
end
end)
%{buffer | cells: new_cells}
end
@doc """
Erases from start to cursor position.
"""
@spec erase_from_start_to_cursor(
ScreenBuffer.t(),
non_neg_integer(),
non_neg_integer(),
non_neg_integer(),
non_neg_integer()
) :: ScreenBuffer.t()
def erase_from_start_to_cursor(buffer, x, y, top, _bottom) do
empty_cell = Cell.new()
# Process each line
new_cells =
Enum.with_index(buffer.cells)
|> Enum.map(fn {line, line_idx} ->
cond do
# Lines before top remain unchanged
line_idx < top ->
line
# Lines from top to before cursor row: clear entirely
line_idx < y ->
List.duplicate(empty_cell, buffer.width)
# Cursor row: clear from start to cursor position (inclusive)
line_idx == y ->
cleared_cells = List.duplicate(empty_cell, x + 1)
preserved_cells = Enum.drop(line, x + 1)
cleared_cells ++ preserved_cells
# Lines after cursor row remain unchanged
true ->
line
end
end)
%{buffer | cells: new_cells}
end
@doc """
Erases the entire buffer.
"""
@spec erase_all(ScreenBuffer.t()) :: ScreenBuffer.t()
def erase_all(buffer) do
empty_cell = Cell.new()
%{
buffer
| cells:
List.duplicate(
List.duplicate(empty_cell, buffer.width),
buffer.height
),
scrollback: []
}
end
@doc """
Clears a specific region of the buffer.
"""
@spec clear_region(
ScreenBuffer.t(),
non_neg_integer(),
non_neg_integer(),
non_neg_integer(),
non_neg_integer()
) :: ScreenBuffer.t()
def clear_region(buffer, x, y, width, height) do
# Clear the specified region by filling it with empty cells
empty_cell = Cell.new()
new_cells =
buffer.cells
|> Enum.with_index()
|> Enum.map(fn {row, row_idx} ->
cond do
row_idx < y or row_idx >= y + height ->
row
true ->
# Clear columns x to x+width-1 in this row
row
|> Enum.with_index()
|> Enum.map(fn {cell, col_idx} ->
if col_idx >= x and col_idx < x + width do
empty_cell
else
cell
end
end)
end
end)
%{buffer | cells: new_cells}
end
@doc """
Erases part or all of the current line based on the cursor position and type.
Type can be :to_end, :to_beginning, or :all.
"""
@spec erase_in_line(
ScreenBuffer.t(),
{non_neg_integer(), non_neg_integer()},
atom()
) :: ScreenBuffer.t()
def erase_in_line(buffer, {x, y}, type) do
case type do
:to_end -> erase_line_to_end(buffer, x, y)
:to_beginning -> erase_line_to_beginning(buffer, x, y)
:all -> erase_entire_line(buffer, y)
_ -> erase_line_to_end(buffer, x, y)
end
end
@doc """
Erases part or all of the display based on the cursor position and type.
Type can be :to_end, :to_beginning, or :all.
"""
@spec erase_in_display(
ScreenBuffer.t(),
{non_neg_integer(), non_neg_integer()},
atom()
) :: ScreenBuffer.t()
def erase_in_display(buffer, {x, y}, type) do
case type do
:to_end ->
# Erase from cursor to end of display
erase_from_cursor_to_end(buffer, x, y, 0, buffer.height)
:to_beginning ->
# Erase from start of display to cursor
erase_from_start_to_cursor(buffer, x, y, 0, buffer.height)
:all ->
# Erase entire display
erase_all(buffer)
_ ->
# Default to :to_end
erase_in_display(buffer, {x, y}, :to_end)
end
end
@doc """
Erases from the cursor to the end of the screen using the current cursor position.
"""
@spec erase_from_cursor_to_end(ScreenBuffer.t()) :: ScreenBuffer.t()
def erase_from_cursor_to_end(buffer) do
{x, y} = buffer.cursor_position || {0, 0}
height = buffer.height || 24
erase_from_cursor_to_end(buffer, x, y, 0, height)
end
# Private helper functions
@spec erase_line_to_end(
ScreenBuffer.t(),
non_neg_integer(),
non_neg_integer()
) :: ScreenBuffer.t()
defp erase_line_to_end(buffer, x, y) do
line = Enum.at(buffer.cells, y, [])
empty_cell = Cell.new()
# Preserve cells before cursor, clear from cursor to end
preserved_cells = Enum.take(line, x)
cleared_cells = List.duplicate(empty_cell, buffer.width - x)
cleared_line = preserved_cells ++ cleared_cells
new_cells = List.replace_at(buffer.cells, y, cleared_line)
%{buffer | cells: new_cells}
end
@spec erase_line_to_beginning(
ScreenBuffer.t(),
non_neg_integer(),
non_neg_integer()
) :: ScreenBuffer.t()
defp erase_line_to_beginning(buffer, x, y) do
line = Enum.at(buffer.cells, y, [])
empty_cell = Cell.new()
cleared_line = List.duplicate(empty_cell, x + 1) ++ Enum.drop(line, x + 1)
new_cells = List.replace_at(buffer.cells, y, cleared_line)
%{buffer | cells: new_cells}
end
@spec erase_entire_line(ScreenBuffer.t(), non_neg_integer()) ::
ScreenBuffer.t()
defp erase_entire_line(buffer, y) do
empty_cell = Cell.new()
new_cells =
List.replace_at(buffer.cells, y, List.duplicate(empty_cell, buffer.width))
%{buffer | cells: new_cells}
end
end