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

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