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
Current section
Files
lib/raxol/terminal/screen_buffer/shared_operations.ex
defmodule Raxol.Terminal.ScreenBuffer.SharedOperations do
@moduledoc """
Shared operations for screen buffer modules to eliminate code duplication.
This module contains common functionality used across different screen buffer implementations.
"""
@default_width Raxol.Core.Defaults.terminal_width()
alias Raxol.Terminal.Cell
@doc """
Erases a specified number of characters at a given position.
Replaces characters with empty cells using the buffer's default style.
## Parameters
- buffer: The buffer to modify
- x: Starting column position
- y: Row position
- count: Number of characters to erase
## Returns
Updated buffer with erased characters
"""
@spec erase_chars_at_position(
map(),
non_neg_integer(),
non_neg_integer(),
non_neg_integer()
) :: map()
def erase_chars_at_position(buffer, x, y, count)
when x >= 0 and y >= 0 and count > 0 do
cells = buffer.cells || []
row = Enum.at(cells, y, [])
new_row =
row
|> Enum.with_index()
|> Enum.map(fn {cell, idx} ->
if idx >= x and idx < x + count do
%Cell{char: " ", style: buffer.default_style}
else
cell
end
end)
new_cells = List.replace_at(cells, y, new_row)
%{buffer | cells: new_cells}
end
def erase_chars_at_position(buffer, _, _, _), do: buffer
@doc """
Inserts a character at the specified position, shifting content right.
Core logic for character insertion without damage tracking.
## Parameters
- buffer: The buffer to modify
- x: Column position
- y: Row position
- char: Character to insert
- style: Style to apply (uses buffer default if nil)
## Returns
Buffer with updated cells (damage tracking handled by caller)
"""
@spec insert_char_core_logic(
map(),
non_neg_integer(),
non_neg_integer(),
String.t(),
map() | nil
) :: map()
def insert_char_core_logic(buffer, x, y, char, style) do
if within_bounds?(buffer, x, y) do
style = style || buffer.default_style
new_cell = %Cell{char: char, style: style}
new_cells =
List.update_at(buffer.cells, y, fn row ->
{before, after_} = Enum.split(row, x)
before ++ [new_cell] ++ Enum.take(after_, buffer.width - x - 1)
end)
%{buffer | cells: new_cells}
else
buffer
end
end
@doc """
Core logic for deleting lines from a buffer.
Removes specified lines and adds empty lines at the bottom.
## Parameters
- buffer: The buffer to modify
- y: Starting line position
- count: Number of lines to delete
## Returns
Updated buffer with deleted lines
"""
@spec delete_lines_core_logic(map(), non_neg_integer(), non_neg_integer()) ::
map()
def delete_lines_core_logic(buffer, y, count) when count > 0 and y >= 0 do
cells = buffer.cells || []
{before, at_and_after} = Enum.split(cells, y)
remaining = Enum.drop(at_and_after, count)
empty_lines = List.duplicate(create_empty_line(buffer), count)
new_cells =
(before ++ remaining ++ empty_lines)
|> Enum.take(buffer.height)
%{buffer | cells: new_cells}
end
def delete_lines_core_logic(buffer, _, _), do: buffer
# Helper function to create an empty line
defp create_empty_line(buffer) do
width = Map.get(buffer, :width, @default_width)
Enum.map(0..(width - 1), fn _ ->
%Cell{char: " ", style: buffer.default_style}
end)
end
# Helper function to check if coordinates are within buffer bounds
defp within_bounds?(buffer, x, y) do
x >= 0 and y >= 0 and x < buffer.width and y < buffer.height
end
@doc """
Normalizes selection coordinates so that start is always before end.
Returns {start_x, start_y, end_x, end_y} in proper order.
## Parameters
- x1, y1: First selection point
- x2, y2: Second selection point
## Returns
Tuple with normalized coordinates {start_x, start_y, end_x, end_y}
"""
@spec normalize_selection(integer(), integer(), integer(), integer()) ::
{integer(), integer(), integer(), integer()}
def normalize_selection(x1, y1, x2, y2) do
if y1 < y2 or (y1 == y2 and x1 <= x2) do
{x1, y1, x2, y2}
else
{x2, y2, x1, y1}
end
end
@doc """
Checks if a position (x, y) is within the selection boundaries.
## Parameters
- x, y: Position to check
- start_x, start_y: Selection start coordinates
- end_x, end_y: Selection end coordinates
## Returns
Boolean indicating if position is within selection
"""
@spec position_in_selection?(
integer(),
integer(),
integer(),
integer(),
integer(),
integer()
) :: boolean()
def position_in_selection?(x, y, start_x, start_y, end_x, end_y) do
cond do
y < start_y or y > end_y -> false
y > start_y and y < end_y -> true
y == start_y and y == end_y -> x >= start_x and x <= end_x
y == start_y -> x >= start_x
y == end_y -> x <= end_x
true -> false
end
end
end