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(<>) 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