Packages
Core behaviours, utilities, events, config, accessibility, and plugin infrastructure for Raxol. Zero external runtime dependencies. Provides BaseManager, event system, plugin lifecycle, keyboard/focus management, and accessibility primitives.
Current section
Files
Jump to
Current section
Files
lib/raxol/core/renderer.ex
defmodule Raxol.Core.Renderer do
@moduledoc """
Pure functional renderer for Raxol buffers.
This module provides efficient rendering and diffing capabilities
without requiring GenServers or stateful components.
## Performance Targets
- `render_to_string/1` completes in < 1ms for 80x24 buffer
- `render_diff/2` completes in < 2ms for 80x24 buffer
- Memory efficient (minimal allocations)
## Examples
# Render buffer to ASCII string
buffer = Raxol.Core.Buffer.create_blank_buffer(80, 24)
output = Raxol.Core.Renderer.render_to_string(buffer)
# Calculate diff between two buffers
old_buffer = Raxol.Core.Buffer.create_blank_buffer(80, 24)
new_buffer = Raxol.Core.Buffer.write_at(old_buffer, 0, 0, "Changed")
diff = Raxol.Core.Renderer.render_diff(old_buffer, new_buffer)
"""
alias Raxol.Core.Buffer
@doc """
Renders a buffer to an ASCII string representation.
Primarily used for testing and debugging. For production rendering,
use more efficient methods.
## Parameters
- `buffer` - The buffer to render
## Returns
A string representation of the buffer suitable for terminal display.
"""
@spec render_to_string(Buffer.t()) :: String.t()
def render_to_string(buffer) do
Buffer.to_string(buffer)
end
@doc """
Calculates the minimal diff between two buffers.
This is useful for efficient terminal updates where only changed
cells need to be redrawn.
## Parameters
- `old_buffer` - The previous buffer state
- `new_buffer` - The new buffer state
## Returns
A list of changes representing the minimal set of updates needed
to transform old_buffer into new_buffer.
Each change is a map with:
- `:x` - Column coordinate
- `:y` - Row coordinate
- `:char` - New character
- `:style` - New style
"""
@spec render_diff(Buffer.t(), Buffer.t()) :: list(map())
def render_diff(
%{lines: old_lines, width: old_width, height: old_height},
%{lines: new_lines, width: new_width, height: new_height}
) do
# If dimensions differ, treat as complete change
cond do
old_width != new_width or old_height != new_height ->
collect_all_cells(new_lines, new_width, new_height)
true ->
collect_changed_cells(old_lines, new_lines, new_width, new_height)
end
end
# Private helpers
@spec collect_all_cells(list(map()), non_neg_integer(), non_neg_integer()) ::
list(map())
defp collect_all_cells(lines, width, height) do
for y <- 0..(height - 1),
x <- 0..(width - 1) do
cell =
lines
|> Enum.at(y)
|> Map.get(:cells)
|> Enum.at(x)
%{
x: x,
y: y,
char: cell.char,
style: cell.style
}
end
end
@spec collect_changed_cells(
list(map()),
list(map()),
non_neg_integer(),
non_neg_integer()
) :: list(map())
defp collect_changed_cells(old_lines, new_lines, _width, _height) do
old_lines
|> Enum.zip(new_lines)
|> Enum.with_index()
|> Enum.flat_map(fn {{old_line, new_line}, y} ->
old_line.cells
|> Enum.zip(new_line.cells)
|> Enum.with_index()
|> Enum.filter(fn {{old_cell, new_cell}, _x} ->
old_cell.char != new_cell.char or old_cell.style != new_cell.style
end)
|> Enum.map(fn {{_old_cell, new_cell}, x} ->
%{
x: x,
y: y,
char: new_cell.char,
style: new_cell.style
}
end)
end)
end
end