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

> Discover how Windows Terminal handles CSI sequences using its internal VT parser state machine. Learn about its efficient detection, parameter accumulation, and sequence dispatching.

- Repository: [Microsoft/terminal](https://github.com/microsoft/terminal)
- Tags: internals
- Published: 2026-02-26

---

**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`](https://github.com/microsoft/terminal/blob/main/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`](https://github.com/microsoft/terminal/blob/main/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`](https://github.com/microsoft/terminal/blob/main/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`](https://github.com/microsoft/terminal/blob/main/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`](https://github.com/microsoft/terminal/blob/main/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`](https://github.com/microsoft/terminal/blob/main/stateMachine.cpp), ensures re-entrancy without corrupting the FSM's internal buffers.

## Code Examples

### Sending a CSI Sequence from a Client Application

```cpp
// 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

```cpp
// 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

```cpp
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`](https://github.com/microsoft/terminal/blob/main/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`](https://github.com/microsoft/terminal/blob/main/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`](https://github.com/microsoft/terminal/blob/main/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`](https://github.com/microsoft/terminal/blob/main/InputStateMachineEngine.hpp). For example, `VTID("A")` maps to `ArrowUp`, while `VTID("J")` with parameter `2` maps to `ED_EraseDisplay`. The [`OutputStateMachineEngine.cpp`](https://github.com/microsoft/terminal/blob/main/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.