Raxol.Plugin behaviour (Raxol v2.0.1)

View Source

Behavior for Raxol plugins.

Plugins extend Raxol with custom functionality while maintaining a consistent interface for lifecycle management, input handling, and rendering.

Example

defmodule MyApp.HelloPlugin do
  @behaviour Raxol.Plugin

  alias Raxol.Core.Buffer

  @impl true
  def init(_opts) do
    {:ok, %{counter: 0}}
  end

  @impl true
  def handle_input(key, _modifiers, state) do
    case key do
      " " -> {:ok, %{state | counter: state.counter + 1}}
      "r" -> {:ok, %{counter: 0}}
      "q" -> {:exit, state}
      _ -> {:ok, state}
    end
  end

  @impl true
  def render(buffer, state) do
    buffer
    |> Buffer.write_at(0, 0, "Hello from plugin!", %{bold: true})
    |> Buffer.write_at(0, 1, "Counter: #{state.counter}")
    |> Buffer.write_at(0, 2, "Press SPACE to increment, R to reset, Q to quit")
  end

  @impl true
  def cleanup(_state), do: :ok
end

Plugin State

Plugin state is managed by the plugin itself and passed between callbacks. The state can be any Elixir term (map, struct, etc.).

Input Handling

The handle_input/3 callback receives:

  • key - The key pressed (string or atom for special keys)
  • modifiers - Map with :ctrl, :alt, :shift, :meta flags
  • state - Current plugin state

It should return:

  • {:ok, new_state} - Continue with updated state
  • {:exit, state} - Signal plugin should exit
  • {:error, reason} - Signal an error occurred

Rendering

The render/2 callback receives a buffer and current state, and should return the modified buffer. All rendering is done using the Raxol.Core.Buffer API for consistency.

Lifecycle

  1. init/1 - Called when plugin starts
  2. handle_input/3 - Called for each input event
  3. render/2 - Called to render current state
  4. cleanup/1 - Called when plugin exits

Summary

Callbacks

Clean up plugin resources.

Optional callback for handling timer/async events.

Handle keyboard or mouse input.

Initialize the plugin with given options.

Render the plugin's current state to a buffer.

Functions

Run a plugin with the given module and options.

Types

init_opts()

@type init_opts() :: keyword() | map()

key()

@type key() :: String.t() | atom()

modifiers()

@type modifiers() :: %{
  ctrl: boolean(),
  alt: boolean(),
  shift: boolean(),
  meta: boolean()
}

state()

@type state() :: any()

Callbacks

cleanup(state)

@callback cleanup(state()) :: :ok | {:error, term()}

Clean up plugin resources.

Called when the plugin is about to exit. Use this to close connections, save state, cancel timers, etc.

Examples

def cleanup(state) do
  if state.connection do
    Connection.close(state.connection)
  end
  :ok
end

handle_info(term, state)

(optional)
@callback handle_info(term(), state()) ::
  {:ok, state()} | {:exit, state()} | {:error, term()}

Optional callback for handling timer/async events.

If your plugin needs to handle periodic updates or async messages, implement this callback. It receives a message and current state.

Examples

def handle_info(:tick, state) do
  {:ok, %{state | last_update: System.monotonic_time()}}
end

def handle_info({:api_response, data}, state) do
  {:ok, %{state | data: data}}
end

handle_input(key, modifiers, state)

@callback handle_input(key(), modifiers(), state()) ::
  {:ok, state()} | {:exit, state()} | {:error, term()}

Handle keyboard or mouse input.

Called for each input event. Should return {:ok, new_state} to continue, {:exit, state} to signal plugin exit, or {:error, reason} on error.

Special Keys

Special keys are represented as atoms:

  • :enter, :escape, :tab, :backspace, :delete
  • :up, :down, :left, :right
  • :home, :end, :page_up, :page_down
  • :f1 through :f12

Examples

def handle_input(key, modifiers, state) do
  cond do
    key == "q" -> {:exit, state}
    key == :enter -> {:ok, process_input(state)}
    modifiers.ctrl and key == "c" -> {:exit, state}
    true -> {:ok, state}
  end
end

init(init_opts)

@callback init(init_opts()) :: {:ok, state()} | {:error, term()}

Initialize the plugin with given options.

Called once when the plugin is first loaded. Should return {:ok, initial_state} or {:error, reason} if initialization fails.

Examples

def init(opts) do
  api_key = Keyword.get(opts, :api_key)
  {:ok, %{api_key: api_key, data: []}}
end

render(t, state)

@callback render(Raxol.Core.Buffer.t(), state()) :: Raxol.Core.Buffer.t()

Render the plugin's current state to a buffer.

Called each frame to render the plugin's UI. Receives the buffer and current state, should return the modified buffer.

Examples

def render(buffer, state) do
  buffer
  |> Buffer.write_at(0, 0, "Status: #{state.status}")
  |> Buffer.write_at(0, 1, "Data: #{length(state.data)} items")
end

Functions

run(plugin_module, opts \\ [])

@spec run(
  module(),
  keyword()
) :: :ok

Run a plugin with the given module and options.

This is a convenience function for testing plugins or running them standalone without the full framework.

Examples

Raxol.Plugin.run(MyApp.HelloPlugin, buffer_width: 80, buffer_height: 24)