defmodule Raxol.Terminal.Commands.CommandServer do @moduledoc """ Unified command handler that consolidates all terminal command processing. Routes commands to specialized handler modules: - `CommandServer.CursorOps` -- cursor movement and positioning - `CommandServer.EraseOps` -- screen/line/character erase - `CommandServer.DeviceOps` -- DA/DSR device responses - `CommandServer.ModeOps` -- ANSI/DEC mode set/reset - `CommandServer.SGROps` -- SGR text formatting - `CommandServer.BufferLineOps` -- insert/delete lines with scroll regions """ require Raxol.Core.Runtime.Log alias Raxol.Terminal.Commands.CommandServer.{ BufferLineOps, CursorOps, DeviceOps, EraseOps, ModeOps, SGROps } alias Raxol.Terminal.Emulator alias Raxol.Terminal.ScreenBuffer @type command_result :: {:ok, Emulator.t()} | {:error, atom(), Emulator.t()} @type command_type :: :csi | :osc | :dcs | :escape | :control @type command_params :: %{ type: command_type(), command: String.t(), params: list(integer()), intermediates: String.t(), private_markers: String.t() } ## Public API @doc """ Processes any terminal command with unified handling. """ @spec handle_command(Emulator.t(), command_params()) :: command_result() def handle_command(emulator, %{type: type, command: command} = cmd_params) do Raxol.Core.Runtime.Log.debug("Processing #{type} command: #{command}") case route_command(type, command, cmd_params) do {:ok, handler_func} -> execute_command(emulator, handler_func, cmd_params) {:error, :unknown_command} -> Raxol.Core.Runtime.Log.warning("Unknown #{type} command: #{command}") {:ok, emulator} end end @doc """ Handles CSI (Control Sequence Introducer) commands. """ @spec handle_csi(Emulator.t(), String.t(), list(integer()), String.t()) :: command_result() def handle_csi(emulator, command, params \\ [], intermediates \\ "") do cmd_params = %{ type: :csi, command: command, params: params, intermediates: intermediates, private_markers: "" } handle_command(emulator, cmd_params) end @doc """ Handles OSC (Operating System Command) sequences. """ @dialyzer {:nowarn_function, handle_osc: 3} @spec handle_osc(Emulator.t(), String.t() | integer(), String.t()) :: command_result() def handle_osc(emulator, command, data) do cmd_params = %{ type: :osc, command: command, params: [data], intermediates: "", private_markers: "" } handle_command(emulator, cmd_params) end ## Command Routing defp route_command(:csi, command, _params) do case categorize_csi_command(command) do {:cursor, handler} -> {:ok, handler} {:erase, handler} -> {:ok, handler} {:device, handler} -> {:ok, handler} {:mode, handler} -> {:ok, handler} {:text, handler} -> {:ok, handler} {:scroll, handler} -> {:ok, handler} {:buffer, handler} -> {:ok, handler} {:tab, handler} -> {:ok, handler} :unknown -> {:error, :unknown_command} end end defp route_command(:osc, command, _params) do case command do "0" -> {:ok, &handle_window_title/3} "1" -> {:ok, &handle_window_icon/3} "2" -> {:ok, &handle_window_title/3} "4" -> {:ok, &handle_color_palette/3} "10" -> {:ok, &handle_foreground_color/3} "11" -> {:ok, &handle_background_color/3} _ -> {:error, :unknown_command} end end defp route_command(:dcs, command, _params) do case command do "q" -> {:ok, &handle_sixel/3} _ -> {:error, :unknown_command} end end defp route_command(_type, _command, _params) do {:error, :unknown_command} end ## CSI Command Categorization defp categorize_csi_command(command) do cond do command in ~w[A B C D E F G H f d] -> {:cursor, cursor_handlers()[command]} command in ~w[J K X] -> {:erase, erase_handlers()[command]} command in ~w[c n] -> {:device, device_handlers()[command]} command in ~w[h l] -> {:mode, mode_handlers()[command]} command == "m" -> {:text, &SGROps.handle_sgr/3} command in ~w[S T] -> {:scroll, scroll_handlers()[command]} command in ~w[L M P @] -> {:buffer, buffer_handlers()[command]} command == "g" -> {:tab, &handle_tab_clear/3} true -> :unknown end end # Handler maps defp cursor_handlers do %{ "A" => &CursorOps.handle_cursor_up/3, "B" => &CursorOps.handle_cursor_down/3, "C" => &CursorOps.handle_cursor_forward/3, "D" => &CursorOps.handle_cursor_backward/3, "E" => &CursorOps.handle_cursor_next_line/3, "F" => &CursorOps.handle_cursor_previous_line/3, "G" => &CursorOps.handle_cursor_horizontal_absolute/3, "H" => &CursorOps.handle_cursor_position/3, "f" => &CursorOps.handle_cursor_position/3, "d" => &CursorOps.handle_cursor_vertical_absolute/3 } end defp erase_handlers do %{ "J" => &EraseOps.handle_erase_display/3, "K" => &EraseOps.handle_erase_line/3, "X" => &EraseOps.handle_erase_character/3 } end defp device_handlers do %{ "c" => &DeviceOps.handle_device_attributes/3, "n" => &DeviceOps.handle_device_status_report/3 } end defp mode_handlers do %{ "h" => &ModeOps.handle_set_mode/3, "l" => &ModeOps.handle_reset_mode/3 } end defp scroll_handlers do %{ "S" => &handle_scroll_up/3, "T" => &handle_scroll_down/3 } end defp buffer_handlers do %{ "L" => &BufferLineOps.handle_insert_lines/3, "M" => &BufferLineOps.handle_delete_lines/3, "P" => &BufferLineOps.handle_delete_characters/3, "@" => &BufferLineOps.handle_insert_characters/3 } end ## Command Execution defp execute_command(emulator, handler_func, cmd_params) do handler_func.(emulator, cmd_params, %{}) rescue error -> Raxol.Core.Runtime.Log.error("Command execution failed: #{inspect(error)}") {:error, :command_execution_failed, emulator} catch :throw, reason -> Raxol.Core.Runtime.Log.error("Command threw: #{inspect(reason)}") {:error, :command_thrown, emulator} end ## Scrolling Commands defp handle_scroll_up(emulator, %{params: params}, _context) do lines = get_param(params, 0, 1) active_buffer = Emulator.get_screen_buffer(emulator) {new_buffer, _scrolled_lines} = ScreenBuffer.scroll_up(active_buffer, lines) {:ok, Emulator.update_active_buffer(emulator, new_buffer)} end defp handle_scroll_down(emulator, %{params: params}, _context) do lines = get_param(params, 0, 1) active_buffer = Emulator.get_screen_buffer(emulator) new_buffer = ScreenBuffer.scroll_down(active_buffer, lines) {:ok, Emulator.update_active_buffer(emulator, new_buffer)} end ## OSC Commands defp handle_window_title(emulator, %{params: [title]}, _context) do {:ok, %{emulator | window_title: title}} end defp handle_window_icon(emulator, %{params: [icon_name]}, _context) do {:ok, %{emulator | icon_name: icon_name}} end defp handle_color_palette(emulator, %{params: [_color_spec]}, _context), do: {:ok, emulator} defp handle_foreground_color(emulator, %{params: [_color_spec]}, _context), do: {:ok, emulator} defp handle_background_color(emulator, %{params: [_color_spec]}, _context), do: {:ok, emulator} ## Tab Commands defp handle_tab_clear(emulator, %{params: params}, _context) do mode = get_param(params, 0, 0) case mode do 0 -> {:ok, emulator} 3 -> {:ok, emulator} _ -> {:ok, emulator} end end ## DCS Commands defp handle_sixel(emulator, %{params: [_sixel_data]}, _context), do: {:ok, emulator} ## Helpers defp get_param(params, index, default) do case Enum.at(params, index) do nil -> default 0 -> default value when is_integer(value) and value > 0 -> value _ -> default end end end