How Windows Terminal Handles CSI Sequences: Inside the VT Parser State Machine

Windows Terminal processes CSI (Control Sequence Introducer) sequences through a finite-state machine that detects the CSI introducer (ESC[ or 0x9B), accumulates numeric parameters in the CsiParam state, and dispatches completed sequences to terminal actions via the OutputStateMachineEngine or InputStateMachineEngine.

Windows Terminal implements the Virtual Terminal (VT) protocol to support rich text formatting, cursor manipulation, and complex input handling. As part of the microsoft/terminal repository, the parser responsible for how Windows Terminal handles CSI sequences resides in the src/terminal/parser directory. This architecture enables modern terminal features like mouse reporting, 24-bit color rendering, and synchronized output.

The Three-Stage CSI Processing Pipeline

The parser in src/terminal/parser/stateMachine.cpp implements a finite-state machine (FSM) that processes CSI sequences through distinct detection, parameterization, and dispatch stages.

Stage 1: CSI Detection and State Transition

When the parser encounters the CSI introducer—either the two-byte sequence ESC [ or the single-byte C1 control character 0x9B—it transitions from the Ground state to CsiEntry. In stateMachine.cpp, the _isCsiIndicator helper checks for the [ character following an escape, while _EnterCsiEntry performs the actual state transition when called from _EventEscape.

Stage 2: Parameter Collection

Once in the CsiEntry state, the FSM moves to CsiParam (and optionally CsiSubParam) to accumulate sequence data. The parser collects:

  • Numeric parameter values (0-9)
  • Parameter delimiters (;)
  • Private-mode markers (?, <, =, >)

Helper predicates such as _isNumericParamValue, _isParameterDelimiter, and _isCsiPrivateMarker in stateMachine.cpp classify incoming characters. If an invalid character appears, the FSM transitions to CsiIgnore, discarding the remainder of the sequence.

Stage 3: Dispatch to Terminal Core

When the FSM encounters the terminating final byte, it invokes _ExecuteCsiCompleteCallback. This callback forwards the parsed VTID identifier and VTParameters vector to either the output engine (OutputStateMachineEngine::ActionCsiDispatch) or the input engine (InputStateMachineEngine::ActionCsiDispatch).

The OutputStateMachineEngine.cpp implementation contains a comprehensive switch statement on CsiActionCodes that maps each VTID to concrete ITerminalDispatch operations such as CursorUp, EraseInDisplay, and SetGraphicsRendition.

Mapping CSI Identifiers to Action Codes

The CSI identifier—the final character after parameters—is encoded as a VTID macro (e.g., VTID("A")). These identifiers are enumerated in InputStateMachineEngine.hpp as CsiActionCodes, which both input and output engines use to select dispatch routines.

Common CSI sequences map to specific action codes:

  • ESC[A (Cursor Up) → ArrowUp
  • ESC[2J (Erase Display) → ED_EraseDisplay
  • ESC[?25h (Show Cursor) → DECSET_PrivateModeSet
  • ESC[<0;10;20M (Mouse Down SGR) → MouseDown

Handling Mouse Events and Modifier Keys

Windows Terminal extends CSI processing to support SGR mouse reporting and modifier key detection. Mouse events encode button codes, column positions, and row values within CSI parameters (e.g., CSI < 0 ; x ; y M). The InputStateMachineEngine extracts these values and translates them into MouseEvent structures, forwarding them to IInteractDispatch::MouseEvent via the MouseDown, MouseUp, and MouseMove actions.

Modifier bits (VT_SHIFT, VT_ALT, VT_CTRL) are similarly encoded within CSI parameters, allowing the terminal to distinguish between modified key combinations and standard input.

Re-entrancy and Callback Safety

Because CSI handlers may themselves emit additional VT sequences—such as Device Status Report responses—the parser implements state preservation in _ExecuteCsiCompleteCallback. The function saves the current parsing state, invokes the user-supplied callback, then restores the saved state before continuing. This mechanism, implemented between lines 187-205 in stateMachine.cpp, ensures re-entrancy without corrupting the FSM's internal buffers.

Code Examples

Sending a CSI Sequence from a Client Application

// Write ESC[2J to clear the screen
std::wstring clearScreen = L"\x1b[2J";
dispatch->WriteInput(clearScreen);   // IInteractDispatch forwards to InputStateMachineEngine

The input engine receives these characters, the FSM detects the CSI introducer [, parses parameter 2, reads final byte J, and calls OutputStateMachineEngine::ActionCsiDispatch with action code ED_EraseDisplay.

Handling a Custom CSI Callback

// Register a custom CSI callback (e.g., ESC[?1337h – enable Kitty protocol)
stateMachine.SetCsiCompleteCallback([&]{
    // Runs after CSI fully parsed
    terminalSettings.enableKitty = true;
});

The callback stored in _onCsiCompleteCallback is invoked by _ExecuteCsiCompleteCallback after sequence completion.

Parsing SGR Mouse Events

bool InputStateMachineEngine::ActionCsiDispatch(const VTID id,
                                                const VTParameters parameters)
{
    if (id == CsiActionCodes::MouseDown) {
        auto button = static_cast<CsiMouseButtonCodes>(parameters.at(0));
        auto col    = parameters.at(1);
        auto row    = parameters.at(2);
        _pDispatch->MouseEvent(button, col, row, /*modifiers*/0);
    }
    // …
}

Summary

  • Windows Terminal uses a finite-state machine in stateMachine.cpp to parse CSI sequences through Ground, CsiEntry, and CsiParam states.
  • The parser detects CSI introducers (ESC[ or 0x9B) using _isCsiIndicator and transitions via _EnterCsiEntry.
  • Parameters accumulate in CsiParam state using helper predicates like _isParameterDelimiter, with invalid characters triggering CsiIgnore.
  • Completed sequences dispatch through _ExecuteCsiCompleteCallback to ActionCsiDispatch methods in OutputStateMachineEngine or InputStateMachineEngine.
  • The system supports SGR mouse reporting and modifier keys through the CsiActionCodes enumeration in InputStateMachineEngine.hpp.
  • Callback safety mechanisms preserve parser state to handle re-entrant VT sequence generation.

Frequently Asked Questions

What file contains the core CSI parsing logic in Windows Terminal?

The core finite-state machine implementation resides in src/terminal/parser/stateMachine.cpp. This file contains character classification helpers such as _isCsiIndicator and _isNumericParamValue, state transition functions including _EnterCsiEntry, and the dispatch mechanism _ExecuteCsiCompleteCallback that processes CSI sequences from raw input.

How does Windows Terminal distinguish between different CSI commands?

The parser encodes the final byte of a CSI sequence as a VTID and maps it to CsiActionCodes defined in InputStateMachineEngine.hpp. For example, VTID("A") maps to ArrowUp, while VTID("J") with parameter 2 maps to ED_EraseDisplay. The OutputStateMachineEngine.cpp contains a switch statement that routes these codes to specific ITerminalDispatch methods.

Can Windows Terminal handle mouse events through CSI sequences?

Yes, Windows Terminal supports SGR mouse reporting via CSI sequences formatted as ESC[<button;col;rowM. The InputStateMachineEngine parses these parameters in ActionCsiDispatch and forwards mouse coordinates and button states to IInteractDispatch::MouseEvent, enabling applications to receive precise mouse input.

What happens when a CSI sequence contains invalid characters?

If the parser encounters invalid characters during parameter accumulation in CsiParam state, it transitions to the CsiIgnore state. This state discards all subsequent characters until the sequence terminates, ensuring malformed sequences do not corrupt the terminal state or execute partial commands.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →