capture CSI intermediate bytes; stop bare-final mis-dispatch (T-123)

_consumeCsi silently discarded intermediate bytes (0x20-0x2f), so an
intermediate-bearing sequence dispatched on its bare final byte —
`CSI 5 SP @` (VT420 scroll-left) ran as "insert 5 blank characters",
and `CSI Ps SP q` (DECSCUSR) could collide with any future bare-q
handler. The parser now records intermediates on the CSI scratch
object and routes any sequence carrying them to unknownCSI, since no
intermediate form is implemented yet.

Implementing DECSCUSR itself (cursor shape + renderer support) is
filed as T-397.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 02:55:58 +02:00
co-authored by Claude Fable 5
parent f8958fd372
commit c3dd1d3e3f
5 changed files with 114 additions and 8 deletions
+28
View File
@@ -801,6 +801,34 @@ void main() {
});
});
group('EscapeParser — CSI intermediate bytes (T-123)', () {
test('CSI 5 SP @ (SL) does not mis-dispatch as insert-blank-chars', () {
final f = _newParser();
f.parser.write('\x1b[5 @');
expect(f.h.named('insertBlankChars'), isEmpty);
expect(f.h.named('unknownCSI').first.args, ['@'.codeUnitAt(0)]);
});
test('CSI 4 SP q (DECSCUSR) routes to unknownCSI, not a bare-q handler', () {
final f = _newParser();
f.parser.write('\x1b[4 q');
expect(f.h.named('unknownCSI').first.args, ['q'.codeUnitAt(0)]);
});
test('CSI ! p (DECSTR) routes to unknownCSI', () {
final f = _newParser();
f.parser.write('\x1b[!p');
expect(f.h.named('unknownCSI').first.args, ['p'.codeUnitAt(0)]);
});
test('intermediates reset between sequences', () {
final f = _newParser();
f.parser.write('\x1b[5 @'); // intermediate form → unknownCSI
f.parser.write('\x1b[3@'); // plain form must dispatch normally again
expect(f.h.named('insertBlankChars').first.args, [3]);
});
});
group('EscapeParser — token bookkeeping', () {
test('tokenBegin / tokenEnd advance with consumed bytes', () {
final f = _newParser();