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/attributes.ex
defmodule Raxol.Terminal.ScreenBuffer.Attributes do
@moduledoc """
Manages buffer attributes including formatting, charset, and cursor state.
Consolidates: Formatting, TextFormatting, Charset, Cursor functionality.
"""
alias Raxol.Terminal.ANSI.TextFormatting
alias Raxol.Terminal.ScreenBuffer.SharedOperations
# Cursor operations
@doc """
Sets the cursor position.
"""
def set_cursor_position(buffer, x, y) do
x = max(0, min(x, buffer.width - 1))
y = max(0, min(y, buffer.height - 1))
%{buffer | cursor_position: {x, y}}
end
@doc """
Gets the cursor position.
"""
def get_cursor_position(buffer) do
buffer.cursor_position
end
@doc """
Moves the cursor relative to its current position.
"""
def move_cursor(buffer, dx, dy) do
{x, y} = buffer.cursor_position
set_cursor_position(buffer, x + dx, y + dy)
end
@doc """
Sets cursor visibility.
"""
def set_cursor_visible(buffer, visible) do
%{buffer | cursor_visible: visible}
end
@doc """
Sets cursor style.
"""
def set_cursor_style(buffer, style)
when style in [:block, :underline, :bar] do
%{buffer | cursor_style: style}
end
def set_cursor_style(buffer, _), do: buffer
@doc """
Sets cursor blink state.
"""
def set_cursor_blink(buffer, blink) do
%{buffer | cursor_blink: blink}
end
@doc """
Saves the current cursor position.
"""
def save_cursor(buffer) do
Map.put(buffer, :saved_cursor_position, buffer.cursor_position)
end
@doc """
Restores the saved cursor position.
"""
def restore_cursor(buffer) do
case Map.get(buffer, :saved_cursor_position) do
{x, y} -> set_cursor_position(buffer, x, y)
nil -> buffer
end
end
# Formatting operations
@doc """
Sets the default text style for the buffer.
"""
def set_default_style(buffer, style) do
%{buffer | default_style: style}
end
@doc """
Gets the default text style.
"""
def get_default_style(buffer) do
buffer.default_style || TextFormatting.new()
end
@doc """
Creates a text style from SGR parameters.
"""
def create_style(params) do
# Create a default style and apply SGR parameters
initial_style = TextFormatting.new()
Enum.reduce(params, initial_style, fn param, style ->
TextFormatting.parse_sgr_param(param, style)
end)
end
@doc """
Merges two styles, with the second taking precedence.
"""
def merge_styles(base, override) do
Map.merge(base, override)
end
# Charset operations
@doc """
Sets the active charset (G0, G1, G2, G3).
"""
def set_charset(buffer, slot, charset) when slot in [:g0, :g1, :g2, :g3] do
charsets =
Map.get(buffer, :charsets, %{
g0: :ascii,
g1: :ascii,
g2: :ascii,
g3: :ascii
})
new_charsets = Map.put(charsets, slot, charset)
Map.put(buffer, :charsets, new_charsets)
end
def set_charset(buffer, _, _), do: buffer
@doc """
Gets the active charset for a slot.
"""
def get_charset(buffer, slot) when slot in [:g0, :g1, :g2, :g3] do
charsets =
Map.get(buffer, :charsets, %{
g0: :ascii,
g1: :ascii,
g2: :ascii,
g3: :ascii
})
Map.get(charsets, slot, :ascii)
end
def get_charset(_buffer, _), do: :ascii
@doc """
Selects which charset slot is active.
"""
def select_charset(buffer, slot) when slot in [:g0, :g1, :g2, :g3] do
Map.put(buffer, :active_charset, slot)
end
def select_charset(buffer, _), do: buffer
@doc """
Gets the currently active charset.
"""
def get_active_charset(buffer) do
slot = Map.get(buffer, :active_charset, :g0)
get_charset(buffer, slot)
end
@doc """
Translates a character according to the active charset.
"""
def translate_char(buffer, char) do
charset = get_active_charset(buffer)
translate_with_charset(char, charset)
end
# Screen mode operations
@doc """
Switches between main and alternate screen buffers.
"""
def set_alternate_screen(buffer, use_alternate) do
%{buffer | alternate_screen: use_alternate}
end
@doc """
Checks if using alternate screen.
"""
def using_alternate_screen?(buffer) do
buffer.alternate_screen
end
# Tab stop operations
@doc """
Sets a tab stop at the current cursor position.
"""
def set_tab_stop(buffer) do
{x, _y} = buffer.cursor_position
tab_stops = Map.get(buffer, :tab_stops, default_tab_stops(buffer.width))
new_tab_stops = MapSet.put(tab_stops, x)
Map.put(buffer, :tab_stops, new_tab_stops)
end
@doc """
Clears a tab stop at the current cursor position.
"""
def clear_tab_stop(buffer) do
{x, _y} = buffer.cursor_position
tab_stops = Map.get(buffer, :tab_stops, default_tab_stops(buffer.width))
new_tab_stops = MapSet.delete(tab_stops, x)
Map.put(buffer, :tab_stops, new_tab_stops)
end
@doc """
Clears all tab stops.
"""
def clear_all_tab_stops(buffer) do
Map.put(buffer, :tab_stops, MapSet.new())
end
@doc """
Resets tab stops to default (every 8 columns).
"""
def reset_tab_stops(buffer) do
Map.put(buffer, :tab_stops, default_tab_stops(buffer.width))
end
@doc """
Finds the next tab stop position from the current cursor.
"""
def next_tab_stop(buffer) do
{x, _y} = buffer.cursor_position
tab_stops = Map.get(buffer, :tab_stops, default_tab_stops(buffer.width))
# Find next tab stop after current position
tab_stops
|> Enum.filter(fn stop -> stop > x end)
|> Enum.min(fn -> buffer.width - 1 end)
end
# Private helper functions
defp translate_with_charset(char, :ascii), do: char
defp translate_with_charset(char, :dec_special) do
# DEC Special Graphics character set mapping
case char do
"`" -> "◆"
"a" -> "▒"
"b" -> "␉"
"c" -> "␌"
"d" -> "␍"
"e" -> "␊"
"f" -> "°"
"g" -> "±"
"h" -> ""
"i" -> "␋"
"j" -> "┘"
"k" -> "┐"
"l" -> "┌"
"m" -> "└"
"n" -> "┼"
"o" -> "⎺"
"p" -> "⎻"
"q" -> "─"
"r" -> "⎼"
"s" -> "⎽"
"t" -> "├"
"u" -> "┤"
"v" -> "┴"
"w" -> "┬"
"x" -> "│"
"y" -> "≤"
"z" -> "≥"
"{" -> "π"
"|" -> "≠"
"}" -> "£"
"~" -> "·"
_ -> char
end
end
defp translate_with_charset(char, _), do: char
defp default_tab_stops(width) do
# Default tab stops every 8 columns
0..(width - 1)
|> Enum.filter(fn x -> rem(x, 8) == 0 end)
|> MapSet.new()
end
# === Stub Implementations for Test Compatibility ===
# These functions are referenced by delegations but not critical for core functionality
@doc """
Checks if cursor is visible (stub).
"""
def cursor_visible?(buffer), do: buffer.cursor_visible
@doc """
Checks if cursor is blinking (stub).
"""
def cursor_blinking?(buffer), do: Map.get(buffer, :cursor_blink, true)
@doc """
Gets cursor style (stub).
"""
def get_cursor_style(buffer), do: buffer.cursor_style
@doc """
Gets text style (stub).
"""
def get_style(buffer), do: buffer.default_style
@doc """
Updates text style (stub).
"""
def update_style(buffer, style), do: %{buffer | default_style: style}
@doc """
Gets foreground color (stub).
"""
def get_foreground(buffer), do: buffer.default_style.foreground
@doc """
Gets background color (stub).
"""
def get_background(buffer), do: buffer.default_style.background
@doc """
Starts selection (stub).
"""
def start_selection(buffer, x, y), do: %{buffer | selection: {x, y, nil, nil}}
@doc """
Updates selection (stub).
"""
def update_selection(buffer, x, y) do
case buffer.selection do
{sx, sy, _, _} -> %{buffer | selection: {sx, sy, x, y}}
nil -> buffer
end
end
@doc """
Clears selection (stub).
"""
def clear_selection(buffer), do: %{buffer | selection: nil}
@doc """
Gets selection (stub).
"""
def get_selection(buffer), do: buffer.selection
@doc """
Gets selection start (stub).
"""
def get_selection_start(buffer) do
case buffer.selection do
{sx, sy, _, _} -> {sx, sy}
nil -> nil
end
end
@doc """
Gets selection end (stub).
"""
def get_selection_end(buffer) do
case buffer.selection do
{_, _, nil, nil} -> nil
{_, _, ex, ey} -> {ex, ey}
nil -> nil
end
end
@doc """
Gets selection boundaries (stub).
"""
def get_selection_boundaries(buffer), do: buffer.selection
@doc """
Checks if position is in selection (stub).
"""
def in_selection?(buffer, x, y) do
case buffer.selection do
{sx, sy, ex, ey} when ex != nil and ey != nil ->
{start_x, start_y, end_x, end_y} =
SharedOperations.normalize_selection(sx, sy, ex, ey)
SharedOperations.position_in_selection?(
x,
y,
start_x,
start_y,
end_x,
end_y
)
_ ->
false
end
end
@doc """
Gets text in region.
"""
def get_text_in_region(buffer, x1, y1, x2, y2) do
case buffer.cells do
cells when is_list(cells) ->
start_y = max(0, min(y1, y2))
end_y = min(length(cells) - 1, max(y1, y2))
col_range = {max(0, min(x1, x2)), max(x1, x2)}
if start_y > end_y do
""
else
cells
|> Enum.slice(start_y..end_y)
|> Enum.map(&extract_line_text(&1, col_range))
|> Enum.reject(&(&1 == ""))
|> Enum.join("\n")
end
_ ->
""
end
end
defp extract_line_text(line, {start_x, max_x}) when is_list(line) do
end_x = min(length(line) - 1, max_x)
if start_x > end_x do
""
else
line
|> Enum.slice(start_x..end_x)
|> Enum.map_join("", fn
%{char: char} -> char
_ -> " "
end)
|> String.trim_trailing()
end
end
defp extract_line_text(_line, _col_range), do: ""
@doc """
Checks if attribute is set (stub).
"""
def attribute_set?(_buffer, _attr), do: false
@doc """
Gets set attributes (stub).
"""
def get_set_attributes(_buffer), do: []
@doc """
Applies single shift (stub).
"""
def apply_single_shift(buffer, _set), do: buffer
@doc """
Gets single shift state (stub).
"""
def get_single_shift(_buffer), do: nil
@doc """
Gets current G set (stub).
"""
def get_current_g_set(_buffer), do: 0
@doc """
Designates charset (stub).
"""
def designate_charset(buffer, _set, _charset), do: buffer
@doc """
Gets designated charset (stub).
"""
def get_designated_charset(_buffer, _set), do: :us
@doc """
Invokes G set (stub).
"""
def invoke_g_set(buffer, _set), do: buffer
@doc """
Resets all attributes to defaults (stub).
"""
def reset_all_attributes(buffer) do
%{buffer | default_style: TextFormatting.default_style()}
end
@doc """
Resets specific attribute (stub).
"""
def reset_attribute(buffer, _attr), do: buffer
@doc """
Resets charset state (stub).
"""
def reset_charset_state(buffer), do: buffer
@doc """
Checks if selection is active (stub).
"""
def selection_active?(buffer), do: buffer.selection != nil
@doc """
Sets specific attribute (stub).
"""
def set_attribute(buffer, _attr), do: buffer
@doc """
Sets background color (stub).
"""
def set_background(buffer, color) do
style = Map.put(buffer.default_style, :background, color)
%{buffer | default_style: style}
end
@doc """
Sets cursor visibility (stub).
"""
def set_cursor_visibility(buffer, visible) do
%{buffer | cursor_visible: visible}
end
@doc """
Sets foreground color (stub).
"""
def set_foreground(buffer, color) do
style = Map.put(buffer.default_style, :foreground, color)
%{buffer | default_style: style}
end
end