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/escape_sequence.ex
defmodule Raxol.Terminal.EscapeSequence do
@moduledoc """
Handles parsing of ANSI escape sequences and other control sequences.
This module provides functions for parsing ANSI escape sequences
into structured data representing terminal commands.
"""
require Raxol.Core.Runtime.Log
alias Raxol.Terminal.Escape.Parsers.{BaseParser, CSIParser, SCSParser}
# --- Public API ---
@doc """
Parses an input string, potentially containing an escape sequence.
Returns:
* `{:ok, command_data, remaining_input}` if a complete sequence is parsed.
* `{:incomplete, remaining_input}` if the input is potentially part of a sequence but incomplete.
* `{:error, :invalid_sequence, remaining_input}` if the sequence is malformed.
* `{:error, :not_escape_sequence, input}` if the input doesn't start with ESC.
`command_data` is a tuple representing the parsed command, e.g.:
* `{:cursor_position, {row, col}}`
* `{:cursor_move, :up, count}`
* `{:set_mode, :dec_private, mode_code, boolean_value}`
* `{:set_mode, :standard, mode_code, boolean_value}`
* `{:designate_charset, target_g_set, charset_atom}`
* `{:invoke_charset, target_g_set}`
* etc.
"""
@spec parse(String.t()) ::
{:ok, term(), String.t()}
| {:incomplete, String.t()}
| {:error, atom(), String.t()}
def parse(<<"\e", rest::binary>>) do
parse_after_esc(rest)
end
def parse(input) do
{:error, :not_escape_sequence, input}
end
# --- Private Parsing Logic ---
# After initial ESC
defp parse_after_esc(<<"[", rest::binary>>) do
# Control Sequence Introducer
CSIParser.parse(rest)
end
defp parse_after_esc(<<char, rest::binary>>) when char in [?(, ?), ?*, ?+] do
# Select Character Set (Designate G0-G3)
SCSParser.parse(char, rest)
end
defp parse_after_esc(<<"~", rest::binary>>) do
# LS1R - Invoke G1 into GR
{:ok, {:invoke_charset_gr, :g1}, rest}
end
defp parse_after_esc(<<"}", rest::binary>>) do
# LS2R - Invoke G2 into GR
{:ok, {:invoke_charset_gr, :g2}, rest}
end
defp parse_after_esc(<<"|", rest::binary>>) do
# LS3R - Invoke G3 into GR
{:ok, {:invoke_charset_gr, :g3}, rest}
end
defp parse_after_esc(<<"n", rest::binary>>) do
# LS2 - Invoke G2 into GL
{:ok, {:invoke_charset_gl, :g2}, rest}
end
defp parse_after_esc(<<"o", rest::binary>>) do
# LS3 - Invoke G3 into GL
{:ok, {:invoke_charset_gl, :g3}, rest}
end
defp parse_after_esc(<<_c, _rest::binary>> = unknown) do
# Consider single char ESC sequences like ESC D, E, M, 7, 8 etc.
BaseParser.log_unknown_sequence("ESC", unknown)
{:error, :unknown_sequence, unknown}
end
defp parse_after_esc("") do
{:incomplete, ""}
end
end