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/parser/states/csi_param_state.ex
defmodule Raxol.Terminal.Parser.States.CSIParamState do
@moduledoc """
Handles the :csi_param state of the terminal parser.
"""
alias Raxol.Core.Runtime.Log
alias Raxol.Terminal.Commands.Executor
alias Raxol.Terminal.Emulator
alias Raxol.Terminal.Parser.ParserState, as: State
@doc """
Processes input when the parser is in the :csi_param state.
Collects parameter digits (0-9) and semicolons (;).
"""
@spec handle(Emulator.t(), State.t(), binary()) ::
{:continue, Emulator.t(), State.t(), binary()}
| {:finished, Emulator.t(), State.t()}
| {:incomplete, Emulator.t(), State.t()}
def handle(emulator, %State{state: :csi_param} = parser_state, input) do
# Log.debug(
# "CSIParamState.handle: input=#{inspect(input)}, params_buffer=#{inspect(parser_state.params_buffer)}"
# )
dispatch_input(input, emulator, parser_state)
end
defp dispatch_input(<<>>, emulator, parser_state),
do: handle_empty_input(emulator, parser_state)
defp dispatch_input(<<digit, rest::binary>>, emulator, parser_state)
when digit >= ?0 and digit <= ?9,
do: handle_digit(emulator, parser_state, digit, rest)
defp dispatch_input(<<?;, rest::binary>>, emulator, parser_state),
do: handle_separator(emulator, parser_state, rest)
defp dispatch_input(
<<intermediate_byte, rest::binary>>,
emulator,
parser_state
)
when (intermediate_byte >= 0x20 and intermediate_byte <= 0x2F) or
intermediate_byte == ?? or
intermediate_byte == ?>,
do: handle_intermediate(emulator, parser_state, intermediate_byte, rest)
defp dispatch_input(<<final_byte, rest::binary>>, emulator, parser_state)
when final_byte >= ?@ and final_byte <= ?~,
do: handle_final_byte(emulator, parser_state, final_byte, rest)
defp dispatch_input(<<ignored_byte, rest::binary>>, emulator, parser_state)
when ignored_byte == 0x18 or ignored_byte == 0x1A,
do: handle_can_sub(emulator, parser_state, rest)
defp dispatch_input(<<ignored_byte, rest::binary>>, emulator, parser_state)
when (ignored_byte >= 0 and ignored_byte <= 23 and ignored_byte != 0x18 and
ignored_byte != 0x1A) or
(ignored_byte >= 27 and ignored_byte <= 31) or ignored_byte == 127,
do: handle_ignored_byte(emulator, parser_state, ignored_byte, rest)
defp dispatch_input(<<unhandled_byte, rest::binary>>, emulator, parser_state),
do: handle_unhandled_byte(emulator, parser_state, unhandled_byte, rest)
# Private helper functions
defp handle_empty_input(emulator, parser_state),
do: {:incomplete, emulator, parser_state}
defp handle_digit(emulator, parser_state, digit, rest) do
# Log.debug(
# "CSIParam.digit: #{inspect(<<digit>>)}, params=#{inspect(parser_state.params_buffer)}"
# )
next_parser_state = %{
parser_state
| params_buffer: parser_state.params_buffer <> <<digit>>
}
# Log.debug(
# "CSIParamState.handle_digit: new_params=#{inspect(next_parser_state.params_buffer)}"
# )
{:continue, emulator, next_parser_state, rest}
end
defp handle_separator(emulator, parser_state, rest) do
# Log.debug(
# "CSIParamState.handle_separator: current_params=#{inspect(parser_state.params_buffer)}"
# )
next_parser_state = %{
parser_state
| params_buffer: parser_state.params_buffer <> <<?;>>
}
# Log.debug(
# "CSIParamState.handle_separator: new_params=#{inspect(next_parser_state.params_buffer)}"
# )
{:continue, emulator, next_parser_state, rest}
end
defp handle_intermediate(emulator, parser_state, intermediate_byte, rest) do
Log.debug(
"CSIParamState.handle_intermediate: byte=#{inspect(<<intermediate_byte>>)}, current_params=#{inspect(parser_state.params_buffer)}, current_intermediates=#{inspect(parser_state.intermediates_buffer)}"
)
next_parser_state = %{
parser_state
| intermediates_buffer: parser_state.intermediates_buffer <> <<intermediate_byte>>,
params_buffer: "",
state: :csi_intermediate
}
Log.debug(
"CSIParamState.handle_intermediate: new_intermediates=#{inspect(next_parser_state.intermediates_buffer)}, new_params=#{inspect(next_parser_state.params_buffer)}, new_state=#{inspect(next_parser_state.state)}"
)
{:continue, emulator, next_parser_state, rest}
end
defp handle_final_byte(emulator, parser_state, final_byte, rest) do
# Log.debug(
# "CSIParam.final: byte=#{inspect(<<final_byte>>)}, params=#{inspect(parser_state.params_buffer)}"
# )
final_emulator =
Executor.execute_csi_command(
emulator,
parser_state.params_buffer,
parser_state.intermediates_buffer,
final_byte
)
next_parser_state = %{
parser_state
| state: :ground,
params_buffer: "",
intermediates_buffer: "",
final_byte: nil
}
# Log.debug(
# "CSIParamState.handle_final_byte: executed command, returning to ground state"
# )
{:continue, final_emulator, next_parser_state, rest}
end
defp handle_can_sub(emulator, parser_state, rest) do
Raxol.Core.Runtime.Log.debug("Ignoring CAN/SUB byte in CSI Param")
next_parser_state = %{parser_state | state: :ground}
{:continue, emulator, next_parser_state, rest}
end
defp handle_ignored_byte(emulator, parser_state, ignored_byte, rest) do
Raxol.Core.Runtime.Log.debug("Ignoring C0/DEL byte #{ignored_byte} in CSI Param")
{:continue, emulator, parser_state, rest}
end
defp handle_unhandled_byte(emulator, parser_state, unhandled_byte, rest) do
msg =
"Unhandled byte #{unhandled_byte} in CSI Param state, returning to ground."
Raxol.Core.Runtime.Log.warning_with_context(msg, %{})
next_parser_state = %{parser_state | state: :ground}
{:continue, emulator, next_parser_state, rest}
end
end