defmodule Raxol.Terminal.Scrollback.Manager do @moduledoc """ Manages terminal scrollback buffer operations. """ @default_scrollback Raxol.Core.Defaults.scrollback_limit() defstruct scrollback_buffer: [], scrollback_limit: @default_scrollback, current_position: 0 @type scrollback_line :: String.t() @type scrollback_buffer :: [scrollback_line()] @type t :: %__MODULE__{ scrollback_buffer: scrollback_buffer(), scrollback_limit: pos_integer(), current_position: non_neg_integer() } @doc """ Creates a new scrollback manager instance. """ def new(opts \\ []) do %__MODULE__{ scrollback_limit: Keyword.get(opts, :scrollback_limit, @default_scrollback) } end @doc """ Gets the current scrollback buffer. """ def get_scrollback_buffer(%__MODULE__{} = state) do state.scrollback_buffer end @doc """ Adds a line to the scrollback buffer. """ def add_to_scrollback(%__MODULE__{} = state, line) when is_binary(line) do new_buffer = [line | state.scrollback_buffer] trimmed_buffer = case length(new_buffer) > state.scrollback_limit do true -> Enum.take(new_buffer, state.scrollback_limit) false -> new_buffer end %{state | scrollback_buffer: trimmed_buffer} end @doc """ Clears the scrollback buffer. """ def clear_scrollback(%__MODULE__{} = state) do %{state | scrollback_buffer: [], current_position: 0} end @doc """ Gets the scrollback limit. """ def get_scrollback_limit(%__MODULE__{} = state) do state.scrollback_limit end @doc """ Sets the scrollback limit. """ def set_scrollback_limit(%__MODULE__{} = state, limit) when is_integer(limit) and limit > 0 do new_state = %{state | scrollback_limit: limit} case length(new_state.scrollback_buffer) > limit do true -> %{ new_state | scrollback_buffer: Enum.take(new_state.scrollback_buffer, limit) } false -> new_state end end @doc """ Gets a range of lines from the scrollback buffer. """ def get_scrollback_range(%__MODULE__{} = state, start_line, end_line) when is_integer(start_line) and is_integer(end_line) and start_line >= 0 and end_line >= start_line do case Enum.slice(state.scrollback_buffer, start_line..end_line) do [] -> {:error, :invalid_range} lines -> {:ok, lines} end end @doc """ Gets the current size of the scrollback buffer. """ def get_scrollback_size(%__MODULE__{} = state) do length(state.scrollback_buffer) end @doc """ Checks if the scrollback buffer is empty. """ def scrollback_empty?(%__MODULE__{} = state) do state.scrollback_buffer == [] end @doc """ Gets the current scrollback position. """ def get_current_position(%__MODULE__{} = state) do state.current_position end @doc """ Sets the current scrollback position. """ def set_current_position(%__MODULE__{} = state, position) when is_integer(position) and position >= 0 do max_position = length(state.scrollback_buffer) - 1 new_position = min(position, max_position) %{state | current_position: new_position} end @doc """ Scrolls up in the scrollback buffer. """ def scroll_up(%__MODULE__{} = state, lines \\ 1) when is_integer(lines) and lines > 0 do new_position = min(state.current_position + lines, length(state.scrollback_buffer) - 1) %{state | current_position: new_position} end @doc """ Scrolls down in the scrollback buffer. """ def scroll_down(%__MODULE__{} = state, lines \\ 1) when is_integer(lines) and lines > 0 do new_position = max(state.current_position - lines, 0) %{state | current_position: new_position} end @doc """ Gets the current line from the scrollback buffer. """ def get_current_line(%__MODULE__{} = state) do case Enum.at(state.scrollback_buffer, state.current_position) do nil -> {:error, :invalid_position} line -> {:ok, line} end end end