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/scroll/scroll_manager.ex
defmodule Raxol.Terminal.Scroll.Manager do
@moduledoc """
Manages terminal scrolling operations with advanced features.
Features:
- Predictive scrolling for smooth performance
- Scroll caching for efficient memory usage
- Scroll optimization for better performance
- Scroll synchronization across splits
"""
alias Raxol.Terminal.Scroll.Optimizer
alias Raxol.Terminal.Scroll.Predictor
alias Raxol.Terminal.Scroll.Sync
@type t :: %__MODULE__{
predictor: Predictor.t(),
optimizer: Optimizer.t(),
sync: Sync.t(),
metrics: %{
scrolls: non_neg_integer(),
predictions: non_neg_integer(),
cache_hits: non_neg_integer(),
cache_misses: non_neg_integer(),
optimizations: non_neg_integer()
}
}
defstruct [
:predictor,
:optimizer,
:sync,
:metrics
]
@doc """
Creates a new scroll manager.
## Options
* `:prediction_enabled` - Whether to enable predictive scrolling (default: true)
* `:optimization_enabled` - Whether to enable scroll optimization (default: true)
* `:sync_enabled` - Whether to enable scroll synchronization (default: true)
"""
@spec new(keyword()) :: t()
def new(opts \\ []) do
prediction_enabled = Keyword.get(opts, :prediction_enabled, true)
optimization_enabled = Keyword.get(opts, :optimization_enabled, true)
sync_enabled = Keyword.get(opts, :sync_enabled, true)
%__MODULE__{
predictor: create_predictor(prediction_enabled),
optimizer: create_optimizer(optimization_enabled),
sync: create_sync(sync_enabled),
metrics: %{
scrolls: 0,
predictions: 0,
cache_hits: 0,
cache_misses: 0,
optimizations: 0
}
}
end
@doc """
Scrolls the terminal content.
## Parameters
* `manager` - The scroll manager
* `direction` - Scroll direction (:up or :down)
* `lines` - Number of lines to scroll
* `opts` - Scroll options
* `:predict` - Whether to use prediction (default: true)
* `:optimize` - Whether to optimize the scroll (default: true)
* `:sync` - Whether to sync across splits (default: true)
"""
@spec scroll(t(), :up | :down, non_neg_integer(), keyword()) ::
{:ok, t()} | {:error, term()}
def scroll(manager, direction, amount, opts \\ []) do
case get_cached_scroll(manager, direction, amount) do
{:hit, cached_result} ->
_manager = update_metrics(manager, :cache_hit)
cached_result
{:miss, _} ->
_manager = update_metrics(manager, :cache_miss)
perform_scroll(manager, direction, amount, opts)
end
end
@doc """
Gets the scroll history.
## Parameters
* `manager` - The scroll manager
* `opts` - History options
* `:limit` - Maximum number of entries to return (default: all)
* `:direction` - Filter by direction (:up or :down)
"""
@spec get_history(t(), keyword()) :: {:ok, [map()], t()}
def get_history(manager, opts \\ []) do
limit = Keyword.get(opts, :limit)
direction = Keyword.get(opts, :direction)
case Raxol.Terminal.Cache.System.get(:history, namespace: :scroll) do
{:ok, history} ->
result =
history
|> filter_by_direction(direction)
|> limit_results(limit)
{:ok, result, manager}
{:error, _} ->
{:ok, [], manager}
end
end
@doc """
Gets the current scroll metrics.
"""
@spec get_metrics(t()) :: map()
def get_metrics(manager) do
manager.metrics
end
@doc """
Optimizes the scroll manager based on current metrics.
"""
@spec optimize(t()) :: t()
def optimize(manager) do
case Raxol.Terminal.Cache.System.stats(namespace: :scroll) do
{:ok, stats} ->
handle_cache_stats(stats)
_ ->
:ok
end
manager
end
defp get_cached_scroll(_manager, direction, amount) do
cache_key = {direction, amount}
case Raxol.Terminal.Cache.System.get(cache_key, namespace: :scroll) do
{:ok, result} -> {:hit, result}
{:error, _} -> {:miss, nil}
end
end
defp perform_scroll(manager, direction, amount, opts) do
predict = Keyword.get(opts, :predict, true)
optimize = Keyword.get(opts, :optimize, true)
sync = Keyword.get(opts, :sync, true)
manager = update_metrics(manager, :scroll)
manager = apply_prediction(manager, predict, direction, amount)
manager = apply_optimization(manager, optimize, direction, amount)
manager = apply_sync(manager, sync, direction, amount)
scroll_entry = %{
direction: direction,
amount: amount,
timestamp: System.monotonic_time(),
options: opts
}
case Raxol.Terminal.Cache.System.get(:history, namespace: :scroll) do
{:ok, history} ->
updated_history = [scroll_entry | history]
Raxol.Terminal.Cache.System.put(:history, updated_history, namespace: :scroll)
{:error, _} ->
Raxol.Terminal.Cache.System.put(:history, [scroll_entry], namespace: :scroll)
end
{:ok, manager}
end
defp update_metrics(manager, :cache_hit) do
%{
manager
| metrics: %{manager.metrics | cache_hits: manager.metrics.cache_hits + 1}
}
end
defp update_metrics(manager, :cache_miss) do
%{
manager
| metrics: %{
manager.metrics
| cache_misses: manager.metrics.cache_misses + 1
}
}
end
defp update_metrics(manager, :scroll) do
%{
manager
| metrics: %{manager.metrics | scrolls: manager.metrics.scrolls + 1}
}
end
defp update_metrics(manager, :prediction) do
%{
manager
| metrics: %{
manager.metrics
| predictions: manager.metrics.predictions + 1
}
}
end
defp update_metrics(manager, :optimization) do
%{
manager
| metrics: %{
manager.metrics
| optimizations: manager.metrics.optimizations + 1
}
}
end
defp filter_by_direction(history, nil), do: history
defp filter_by_direction(history, direction) do
Enum.filter(history, fn entry -> entry.direction == direction end)
end
defp limit_results(history, nil), do: history
defp limit_results(history, limit), do: Enum.take(history, limit)
@doc """
Clears the scroll history.
"""
@spec clear_history(t()) :: {:ok, t()}
def clear_history(manager) do
case Raxol.Terminal.Cache.System.clear(namespace: :scroll) do
:ok -> {:ok, manager}
{:error, _} -> {:ok, manager}
end
end
defp create_predictor(true), do: Predictor.new()
defp create_predictor(false), do: nil
defp create_optimizer(true), do: Optimizer.new()
defp create_optimizer(false), do: nil
defp create_sync(true), do: Sync.new()
defp create_sync(false), do: nil
defp apply_prediction(manager, predict, direction, amount) do
case {predict, manager.predictor} do
{true, predictor} when not is_nil(predictor) ->
%{
manager
| predictor: Predictor.predict(predictor, direction, amount)
}
|> update_metrics(:prediction)
_ ->
manager
end
end
defp apply_optimization(manager, optimize, direction, amount) do
case {optimize, manager.optimizer} do
{true, optimizer} when not is_nil(optimizer) ->
%{
manager
| optimizer: Optimizer.optimize(optimizer, direction, amount)
}
|> update_metrics(:optimization)
_ ->
manager
end
end
defp apply_sync(manager, sync, direction, amount) do
case {sync, manager.sync} do
{true, sync_module} when not is_nil(sync_module) ->
%{manager | sync: Sync.sync(sync_module, direction, amount)}
_ ->
manager
end
end
defp handle_cache_stats(stats) do
case stats.size > stats.max_size * 0.8 do
true -> Raxol.Terminal.Cache.System.clear(namespace: :scroll)
false -> :ok
end
end
end