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
Current section
Files
lib/raxol/terminal/buffer/scroller.ex
defmodule Raxol.Terminal.Buffer.Scroller do
@moduledoc """
Handles scrolling operations for the terminal buffer.
"""
require Raxol.Core.Runtime.Log
alias Raxol.Terminal.ScreenBuffer
@doc """
Scrolls the buffer up by the specified number of lines.
"""
def scroll_up(buffer, count) do
do_scroll_up(buffer, count)
end
@doc """
Scrolls the buffer down by the specified number of lines.
"""
def scroll_down(buffer, count) do
do_scroll_down(buffer, count)
end
@doc """
Gets the scroll top position.
"""
def get_scroll_top(_buffer, scroll_margins) do
scroll_margins.top
end
@doc """
Gets the scroll bottom position.
"""
def get_scroll_bottom(_buffer, scroll_margins) do
scroll_margins.bottom
end
@doc """
Scrolls the entire buffer up by the specified number of lines.
## Parameters
* `buffer` - The screen buffer to scroll
* `count` - The number of lines to scroll up
## Returns
A tuple containing :ok and the updated buffer.
## Examples
iex> buffer = ScreenBuffer.new(80, 24)
iex> {:ok, new_buffer} = Scroller.scroll_entire_buffer_up(buffer, 1)
iex> length(new_buffer.content)
24
"""
@spec scroll_entire_buffer_up(ScreenBuffer.t(), non_neg_integer()) ::
{:ok, ScreenBuffer.t()}
def scroll_entire_buffer_up(buffer, count) do
{new_buffer, _removed_lines} = ScreenBuffer.pop_bottom_lines(buffer, count)
empty_lines = List.duplicate(List.duplicate(%{}, buffer.width), count)
new_cells = empty_lines ++ new_buffer.cells
{:ok, %{new_buffer | cells: new_cells}}
end
@doc """
Scrolls the entire buffer down by the specified number of lines.
## Parameters
* `buffer` - The screen buffer to scroll
* `count` - The number of lines to scroll down
## Returns
A tuple containing :ok and the updated buffer.
## Examples
iex> buffer = ScreenBuffer.new(80, 24)
iex> {:ok, new_buffer} = Scroller.scroll_entire_buffer_down(buffer, 1)
iex> length(new_buffer.content)
24
"""
@spec scroll_entire_buffer_down(ScreenBuffer.t(), non_neg_integer()) ::
{:ok, ScreenBuffer.t()}
def scroll_entire_buffer_down(buffer, count) do
{new_buffer, _removed_lines} = ScreenBuffer.pop_bottom_lines(buffer, count)
empty_lines = List.duplicate(List.duplicate(%{}, buffer.width), count)
new_cells = new_buffer.cells ++ empty_lines
{:ok, %{new_buffer | cells: new_cells}}
end
@doc """
Scrolls a specific region of the buffer up by the specified number of lines.
## Parameters
* `buffer` - The screen buffer to scroll
* `count` - The number of lines to scroll up
* `top` - The top boundary of the scroll region
* `bottom` - The bottom boundary of the scroll region
## Returns
A tuple containing :ok and the updated buffer.
## Examples
iex> buffer = ScreenBuffer.new(80, 24)
iex> {:ok, new_buffer} = Scroller.scroll_region_up(buffer, 1, 5, 15)
iex> length(new_buffer.content)
24
"""
@spec scroll_region_up(
ScreenBuffer.t(),
non_neg_integer(),
non_neg_integer(),
non_neg_integer()
) :: {:ok, ScreenBuffer.t()}
def scroll_region_up(buffer, count, top, bottom) do
region_lines = Enum.slice(buffer.cells, top..bottom)
{_to_scroll, remaining} = Enum.split(region_lines, count)
empty_lines = List.duplicate(List.duplicate(%{}, buffer.width), count)
new_region = remaining ++ empty_lines
new_cells = List.replace_at(buffer.cells, top, new_region)
{:ok, %{buffer | cells: new_cells}}
end
@doc """
Scrolls a specific region of the buffer down by the specified number of lines.
## Parameters
* `buffer` - The screen buffer to scroll
* `count` - The number of lines to scroll down
* `top` - The top boundary of the scroll region
* `bottom` - The bottom boundary of the scroll region
## Returns
A tuple containing :ok and the updated buffer.
## Examples
iex> buffer = ScreenBuffer.new(80, 24)
iex> {:ok, new_buffer} = Scroller.scroll_region_down(buffer, 1, 5, 15)
iex> length(new_buffer.content)
24
"""
@spec scroll_region_down(
ScreenBuffer.t(),
non_neg_integer(),
non_neg_integer(),
non_neg_integer()
) :: {:ok, ScreenBuffer.t()}
def scroll_region_down(buffer, count, top, bottom) do
region_lines = Enum.slice(buffer.cells, top..bottom)
{remaining, _to_scroll} = Enum.split(region_lines, -count)
empty_lines = List.duplicate(List.duplicate(%{}, buffer.width), count)
new_region = empty_lines ++ remaining
new_cells = List.replace_at(buffer.cells, top, new_region)
{:ok, %{buffer | cells: new_cells}}
end
# Private helper functions
defp do_scroll_up(buffer, count) do
case buffer.scroll_region do
nil ->
{:ok, new_buffer} = scroll_entire_buffer_up(buffer, count)
new_buffer
region ->
{:ok, new_buffer} =
scroll_region_up(buffer, count, region.top, region.bottom)
new_buffer
end
end
defp do_scroll_down(buffer, count) do
case buffer.scroll_region do
nil ->
{:ok, new_buffer} = scroll_entire_buffer_down(buffer, count)
new_buffer
region ->
{:ok, new_buffer} =
scroll_region_down(buffer, count, region.top, region.bottom)
new_buffer
end
end
end