* Check if CSI sequence is complete * CSI sequences: ESC [ ... followed by a final byte (0x40-0x7E)
(data: string)
| 82 | * CSI sequences: ESC [ ... followed by a final byte (0x40-0x7E) |
| 83 | */ |
| 84 | function isCompleteCsiSequence(data: string): "complete" | "incomplete" { |
| 85 | if (!data.startsWith(`${ESC}[`)) { |
| 86 | return "complete"; |
| 87 | } |
| 88 | |
| 89 | // Need at least ESC [ and one more character |
| 90 | if (data.length < 3) { |
| 91 | return "incomplete"; |
| 92 | } |
| 93 | |
| 94 | const payload = data.slice(2); |
| 95 | |
| 96 | // CSI sequences end with a byte in the range 0x40-0x7E (@-~) |
| 97 | // This includes all letters and several special characters |
| 98 | const lastChar = payload[payload.length - 1]!; |
| 99 | const lastCharCode = lastChar.charCodeAt(0); |
| 100 | |
| 101 | if (lastCharCode >= 0x40 && lastCharCode <= 0x7e) { |
| 102 | // Special handling for SGR mouse sequences |
| 103 | // Format: ESC[<B;X;Ym or ESC[<B;X;YM |
| 104 | if (payload.startsWith("<")) { |
| 105 | // Must have format: <digits;digits;digits[Mm] |
| 106 | const mouseMatch = /^<\d+;\d+;\d+[Mm]$/.test(payload); |
| 107 | if (mouseMatch) { |
| 108 | return "complete"; |
| 109 | } |
| 110 | // If it ends with M or m but doesn't match the pattern, still incomplete |
| 111 | if (lastChar === "M" || lastChar === "m") { |
| 112 | // Check if we have the right structure |
| 113 | const parts = payload.slice(1, -1).split(";"); |
| 114 | if (parts.length === 3 && parts.every((p) => /^\d+$/.test(p))) { |
| 115 | return "complete"; |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | return "incomplete"; |
| 120 | } |
| 121 | |
| 122 | return "complete"; |
| 123 | } |
| 124 | |
| 125 | return "incomplete"; |
| 126 | } |
| 127 | |
| 128 | /** |
| 129 | * Check if OSC sequence is complete |
no outgoing calls
no test coverage detected