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/history_buffer.ex
defmodule Raxol.Terminal.HistoryBuffer do
@moduledoc """
Manages terminal command history buffer operations.
This module handles the storage and retrieval of command history.
"""
@type t :: %__MODULE__{
commands: list(String.t()),
position: integer(),
max_size: non_neg_integer()
}
defstruct commands: [], position: 0, max_size: 1000
@doc """
Creates a new history buffer with the specified maximum size.
"""
@spec new(non_neg_integer()) :: t()
def new(max_size \\ 1000) do
%__MODULE__{max_size: max_size}
end
@doc """
Adds a command to the history buffer.
"""
@spec add_command(t(), String.t()) :: t()
def add_command(buffer, command) when is_binary(command) and command != "" do
commands = [command | buffer.commands]
commands = Enum.take(commands, buffer.max_size)
%{buffer | commands: commands, position: 0}
end
def add_command(buffer, _command), do: buffer
@doc """
Gets the command at the specified index.
"""
@spec get_command_at(t(), integer()) ::
{:ok, String.t()} | {:error, String.t()}
def get_command_at(buffer, index)
when index >= 0 and index < length(buffer.commands) do
case Enum.at(buffer.commands, index) do
nil -> {:error, "Index out of bounds"}
command -> {:ok, command}
end
end
def get_command_at(_buffer, _index), do: {:error, "Invalid index"}
@doc """
Gets the current history position.
"""
@spec get_position(t()) :: integer()
def get_position(buffer), do: buffer.position
@doc """
Sets the history position.
"""
@spec set_position(t(), integer()) :: t()
def set_position(buffer, position)
when position >= 0 and position <= length(buffer.commands) do
%{buffer | position: position}
end
def set_position(buffer, _position), do: buffer
@doc """
Moves to the next command in history.
"""
@spec next_command(t()) :: {:ok, t(), String.t()} | {:error, String.t()}
def next_command(buffer) do
case buffer.position > 0 do
true ->
new_position = buffer.position - 1
case get_command_at(buffer, new_position) do
{:ok, command} -> {:ok, set_position(buffer, new_position), command}
{:error, reason} -> {:error, reason}
end
false ->
{:error, "Already at the most recent command"}
end
end
@doc """
Moves to the previous command in history.
"""
@spec previous_command(t()) :: {:ok, t(), String.t()} | {:error, String.t()}
def previous_command(buffer) do
case buffer.position < length(buffer.commands) do
true ->
new_position = buffer.position + 1
case get_command_at(buffer, new_position - 1) do
{:ok, command} -> {:ok, set_position(buffer, new_position), command}
{:error, reason} -> {:error, reason}
end
false ->
{:error, "Already at the oldest command"}
end
end
@doc """
Clears the command history.
"""
@spec clear(t()) :: t()
def clear(buffer) do
%{buffer | commands: [], position: 0}
end
@doc """
Gets the history size.
"""
@spec get_size(t()) :: non_neg_integer()
def get_size(buffer), do: length(buffer.commands)
@doc """
Gets the maximum history size.
"""
@spec get_max_size(t()) :: non_neg_integer()
def get_max_size(buffer), do: buffer.max_size
@doc """
Sets the maximum history size.
"""
@spec set_max_size(t(), non_neg_integer()) :: t()
def set_max_size(buffer, max_size) when max_size > 0 do
commands = Enum.take(buffer.commands, max_size)
%{buffer | commands: commands, max_size: max_size}
end
def set_max_size(buffer, _max_size), do: buffer
@doc """
Gets all commands in history.
"""
@spec get_all_commands(t()) :: list(String.t())
def get_all_commands(buffer), do: buffer.commands
@doc """
Saves the history to a file.
"""
@spec save_to_file(t(), String.t()) :: :ok | {:error, String.t()}
def save_to_file(buffer, file_path) do
case Raxol.Core.ErrorHandling.safe_call(fn ->
File.write(file_path, Enum.join(buffer.commands, "\n"))
end) do
{:ok, result} -> result
{:error, reason} -> {:error, "Failed to save history: #{inspect(reason)}"}
end
end
@doc """
Loads history from a file.
"""
@spec load_from_file(t(), String.t()) :: {:ok, t()} | {:error, String.t()}
def load_from_file(buffer, file_path) do
case Raxol.Core.ErrorHandling.safe_call(fn ->
case File.read(file_path) do
{:ok, content} ->
commands = String.split(content, "\n", trim: true)
commands = Enum.take(commands, buffer.max_size)
{:ok, %{buffer | commands: commands, position: 0}}
{:error, reason} ->
{:error, "Failed to read file: #{inspect(reason)}"}
end
end) do
{:ok, result} -> result
{:error, reason} -> {:error, "Failed to load history: #{inspect(reason)}"}
end
end
end