split escape parser handlers into part files (T-123)
parser.dart (1139 LOC) now keeps only the core — byte queue, dispatch tables, ESC/CSI consumers — at 373 LOC. The handler groups move to mixins in part files of the same library, so every private member stays library-scoped and no public surface is added: - csi_handlers.dart — cursor/erase/scroll ops, DA/DSR, margins, tab clear, repeat, window manipulation - sgr_handlers.dart — SGR incl. the guarded 38/48 extended-color path (T-369) - mode_handlers.dart — ANSI + DEC private mode set/reset - osc_handlers.dart — OSC string parsing + dispatch An abstract _EscapeParserBase carries the shared state (handler sink, queue, token bookkeeping, the reusable _Csi scratch) the mixins are `on`. All 76 parser tests (and the rest of the terminal suite) pass unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4128,3 +4128,5 @@ CORRECTION to fable-ous.md: ColumnHat is NOT duplicated line-for-line in app.dar
|
||||
|
||||
Done 2026-06-12. One deviation from the plan: the shell pieces went to lib/src/shell/ rather than lib/widgets/ + lib/builtin/hat/ — SlotHost/RootLayout know kernel + contribution types (not widget primitives), and the hat bar isn''t extension-shaped (no contributions), so neither home fit. app.dart kept the planned re-exports; zero test edits needed.', NULL, '2026-06-12 00:30:11', '2026-06-12 00:30:11', '2026-06-12 00:30:11', NULL, '871c3a1d4c3545dfa41bdafa3a39aaa8', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHDH8TE9MJBXZ4GQ5YT10JM', 'status', 'backlog', 'in_progress', NULL, '2026-06-12 00:30:17', '2026-06-12 00:30:17', '2026-06-12 00:30:17', NULL, '95cd4a80d4e61af091427c6d20bf701a', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHDH8TE9MJBXZ4GQ5YT10JM', 'status', 'in_progress', 'done', NULL, '2026-06-12 00:39:04', '2026-06-12 00:39:04', '2026-06-12 00:39:04', NULL, '9354054c9dee37482c76674a331ebc26', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4RJ9K19P5AX14RHRR', 'status', 'backlog', 'in_progress', NULL, '2026-06-12 00:39:41', '2026-06-12 00:39:41', '2026-06-12 00:39:41', NULL, 'a5439ee868e2491127308f8028d29417', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
|
||||
@@ -4344,3 +4344,42 @@ Target layout under lib/builtin/claude/src/meta_sidebar/: models.dart (enums + _
|
||||
Execution order (each phase independently green): 1) primitives (icon button, inject field, permission badge, models); 2) stateless tab views (activity, config, tab strip) with prop threading; 3) team tab + roster row (most orchestrator coupling: verify show/hide, mute, inject submit-and-clear, shift-click bypass confirm, fork, close, task reassign cycle, auto-front on TeamMemberJoined at line 158); 4) cleanup of moved methods from the root state.
|
||||
|
||||
Tests: test/builtin/claude/claude_meta_sidebar_test.dart imports only the root file and public symbols — no changes needed; no goldens reference the sidebar. Caveat from the rat sweep (T-385): TeamMemberJoined currently has no emitter — coordinate before investing in the team tab plumbing.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-11 22:03:26', '2026-06-12 00:30:17', NULL, '9c58fdb1224831d870110543a53389a6', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBHDH8TE9MJBXZ4GQ5YT10JM', 'task', '06FBHBGHNEQTAEPGNJKN42C1E8', 'Split lib/builtin/claude/src/claude_meta_sidebar.dart (1192 LOC)', 'lib/builtin/claude/src/claude_meta_sidebar.dart is 1192 LOC. Split plan (verified against the file 2026-06-12). Public API (ClaudeMetaSidebar widget + SidebarTab enum) stays in the root file — tests and extension.dart need zero import changes.
|
||||
|
||||
Inventory: consts _labelColumnWidth/_rowPitch (43-44), SidebarTab enum (46), ClaudeMetaSidebar (48-79), _ClaudeMetaSidebarState monolith (81-638: lifecycle, stats polling, team membership streams, primary-session binding, broker subscription, config listener, inject state, accordion state, plus the three tab bodies), _ConfigSection/_ConfigPermKind enums (641/644), _MetaSection/_MetaRow models (646-657), _AgentRosterRow(+State) (680-928), _permissionModeBadge + _PermissionModeBadge T-181 (935-1009), _IconButton (1012-1039), _InjectTextField (1043-1070), _TaskRow T-171 (1077-1141), _TabStrip (1145-1192).
|
||||
|
||||
Target layout under lib/builtin/claude/src/meta_sidebar/: models.dart (enums + _MetaSection/_MetaRow + layout consts), activity_tab.dart (ActivityTabView ~70 LOC, from _activityBody/_runtimeSection 272-302), team_tab.dart (TeamTabView ~120 LOC, from _teamBody/_taskSection 304-380, props-driven with callbacks), config_tab.dart (ConfigTabView ~200 LOC, from _configBody family 382-597; accordion _expanded state stays in parent, passed as prop+callback), roster_row.dart (_AgentRosterRow ~250 LOC incl. bypass-confirm state), permission_badge.dart (~80), task_row.dart (~70), tab_strip.dart (~55), icon_button.dart + inject_field.dart (~30 each — keep here initially; promote to lib/widgets/ only when a second consumer appears). Root file shrinks to ~150 LOC of lifecycle + event bindings + tab switch.
|
||||
|
||||
Execution order (each phase independently green): 1) primitives (icon button, inject field, permission badge, models); 2) stateless tab views (activity, config, tab strip) with prop threading; 3) team tab + roster row (most orchestrator coupling: verify show/hide, mute, inject submit-and-clear, shift-click bypass confirm, fork, close, task reassign cycle, auto-front on TeamMemberJoined at line 158); 4) cleanup of moved methods from the root state.
|
||||
|
||||
Tests: test/builtin/claude/claude_meta_sidebar_test.dart imports only the root file and public symbols — no changes needed; no goldens reference the sidebar. Caveat from the rat sweep (T-385): TeamMemberJoined currently has no emitter — coordinate before investing in the team tab plumbing.', 'done', 'medium', NULL, NULL, NULL, '2026-06-11 22:03:26', '2026-06-12 00:39:04', NULL, '130bc44e5a82ac00eb280f13cc8c19c3', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM4RJ9K19P5AX14RHRR', 'task', '06FB0TNQM4HGK6KR0314P2M32M', 'split lib/src/terminal/.../escape/parser.dart (1139 LOC)', 'parser.dart is a single 1139-line file containing the full ESC/CSI/OSC/DCS handler tree for the terminal emulator. Functional but unwieldy; the consultant flagged it as ''consider splitting'' in the T-107 review.
|
||||
|
||||
Suggested split (sequenced with the T-91 coverage sweep on lib/src/terminal/, so the split doesn''t fight in-flight test work):
|
||||
|
||||
- parser.dart — entry point + state machine driver
|
||||
- esc_handlers.dart — single-char ESC dispatch table + handlers
|
||||
- csi_handlers.dart — CSI parameter parsing + handlers
|
||||
- osc_handlers.dart — OSC string handlers (title, colour set, etc.)
|
||||
- dcs_handlers.dart — DCS/SOS/PM/APC string handlers
|
||||
|
||||
Each handler module exports a registrar that the driver wires at construction.
|
||||
|
||||
Done when:
|
||||
- parser.dart < 400 LOC
|
||||
- All existing parser tests pass without changes
|
||||
- No new public surface; everything stays library-private
|
||||
|
||||
Source: T-107 / consultants.md "Code quality — Findings — [Major]".
|
||||
|
||||
Split breakdown from the 2026-06-11 Fable review (epic T-359), verified against the file 2026-06-12:
|
||||
|
||||
Structure today: main parser + routing (11-116), CSI core _escHandleCSI (196-209) + _consumeCsi (217-272), _csiHandlers table of 27 final bytes (274-304), cursor movement handlers (306-807), erase/scroll/line/char ops (809-945), SGR monolith (411-622, 212 LOC), mode set/reset (395-409, 946-1030), DA/DSR (334-343, 624-635), window manipulation (658-712), OSC (1034-1110), _Csi state object (1113-1131 — note the commented-out `intermediates` field at 1117/1125).
|
||||
|
||||
Target layout under escape/: parser.dart keeps EscapeParser (queue, tokenization, top-level dispatch, ~400 LOC); csi_parser.dart (CsiSequence value object — prefix, params, RESTORED intermediates, finalByte — plus the consume logic from _consumeCsi); csi_handlers.dart (dispatch table, now keyed on final byte + intermediates); cursor_handlers.dart; sgr_handler.dart (the 411-622 monolith); mode_handler.dart; osc_parser.dart + osc_handlers.dart. EscapeHandler interface unchanged. Preserve the zero-allocation/reset-able design goal noted at lines 14-16.
|
||||
|
||||
Bug this split must fix (verified): _consumeCsi DISCARDS intermediate bytes — lines 258-261 have `// intermediates.add(char);` commented out and `continue`, so CSI Ps SP q (DECSCUSR, cursor style) and CSI ! p / SP-intermediate forms dispatch on the bare final byte and fall to unknownCSI. Fix: restore the intermediates field on CsiSequence, capture them during consume, dispatch on (intermediates, finalByte), and add EscapeHandler.setCursorStyle for DECSCUSR. (DECSTR is CSI ! p — soft terminal reset — same intermediate mechanism.)
|
||||
|
||||
Related bug with its own ticket (T-369): unguarded params[i+1] lookahead in SGR 38/48 at lines ~502/512/547/557 (RangeError on truncated sequences) + colon-form sub-parameters unhandled. The split makes the fix natural: sgr_handler.dart owns guarded lookahead helpers; if T-369 lands first, carry its tests over; if this lands first, fix it inside sgr_handler.dart and close T-369 with it.
|
||||
|
||||
Tests: test/terminal/escape/parser_test.dart (786 LOC) splits along the same seams — keep parser_test.dart for top-level dispatch/SBC/rollback, add csi_parser_test.dart (intermediates capture, DECSCUSR), sgr_handler_test.dart (bounds + colon form + 256/RGB), mode_handler_test.dart, osc_parser_test.dart, window/DA splits as convenient. The _RecordingHandler fixture is reusable across all of them.', 'in_progress', 'low', NULL, NULL, NULL, '2026-05-18 10:29:02', '2026-06-12 00:39:41', NULL, 'e8810fd23adc05225565eaa3af23eb53', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
|
||||
@@ -0,0 +1,416 @@
|
||||
// Based on xterm.dart v4.0.0 by xuty (MIT). See LICENSE in this directory.
|
||||
|
||||
// CSI handlers: cursor movement, erase/scroll/line/char ops, device
|
||||
// attributes + status reports, margins, tab clear, repeat, and window
|
||||
// manipulation. Split out of parser.dart (T-123); dispatched from the
|
||||
// _csiHandlers table in the EscapeParser core.
|
||||
|
||||
part of 'parser.dart';
|
||||
|
||||
mixin _CsiHandlers on _EscapeParserBase {
|
||||
/// `ESC [ Ps a` Cursor Horizontal Position Relative (HPR)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_sa/
|
||||
// void _csiHandleCursorHorizontalRelative() {
|
||||
// if (_csi.params.isEmpty) {
|
||||
// handler.cursorHorizontal(1);
|
||||
// } else {
|
||||
// handler.cursorHorizontal(_csi.params[0]);
|
||||
// }
|
||||
// }
|
||||
/// `ESC [ Ps b` Repeat Previous Character (REP)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_sb/
|
||||
void _csiHandleRepeatPreviousCharacter() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
if (amount == 0) amount = 1;
|
||||
}
|
||||
|
||||
handler.repeatPreviousCharacter(amount);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps c` Device Attributes (DA)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_sc/
|
||||
void _csiHandleSendDeviceAttributes() {
|
||||
switch (_csi.prefix) {
|
||||
case Ascii.greaterThan:
|
||||
return handler.sendSecondaryDeviceAttributes();
|
||||
case Ascii.equal:
|
||||
return handler.sendTertiaryDeviceAttributes();
|
||||
default:
|
||||
handler.sendPrimaryDeviceAttributes();
|
||||
}
|
||||
}
|
||||
|
||||
/// `ESC [ Ps d` Cursor Vertical Position Absolute (VPA)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_sd/
|
||||
void _csiHandleLinePositionAbsolute() {
|
||||
var y = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
y = _csi.params[0];
|
||||
}
|
||||
|
||||
handler.setCursorY(y - 1);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps ; Ps f` Alias: Set Cursor Position
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_sf/
|
||||
void _csiHandleCursorPosition() {
|
||||
var row = 1;
|
||||
var col = 1;
|
||||
|
||||
if (_csi.params.length == 2) {
|
||||
row = _csi.params[0];
|
||||
col = _csi.params[1];
|
||||
}
|
||||
|
||||
handler.setCursor(col - 1, row - 1);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps g` Tab Clear (TBC)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_sg/
|
||||
void _csiHandelClearTabStop() {
|
||||
var cmd = 0;
|
||||
|
||||
if (_csi.params.length == 1) {
|
||||
cmd = _csi.params[0];
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case 0:
|
||||
return handler.clearTabStopUnderCursor();
|
||||
default:
|
||||
return handler.clearAllTabStops();
|
||||
}
|
||||
}
|
||||
|
||||
/// `ESC [ Ps n` Device Status Report [Dispatch] (DSR)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_sn/
|
||||
void _csiHandleDeviceStatusReport() {
|
||||
if (_csi.params.isEmpty) return;
|
||||
|
||||
switch (_csi.params[0]) {
|
||||
case 5:
|
||||
return handler.sendOperatingStatus();
|
||||
case 6:
|
||||
return handler.sendCursorPosition();
|
||||
}
|
||||
}
|
||||
|
||||
/// `ESC [ Ps ; Ps r` Set Top and Bottom Margins (DECSTBM)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_sr/
|
||||
void _csiHandleSetMargins() {
|
||||
var top = 1;
|
||||
int? bottom;
|
||||
|
||||
if (_csi.params.length > 2) return;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
top = _csi.params[0];
|
||||
|
||||
if (_csi.params.length == 2) {
|
||||
bottom = _csi.params[1] - 1;
|
||||
}
|
||||
}
|
||||
|
||||
handler.setMargins(top - 1, bottom);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps t` Window operations [DISPATCH]
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_st/
|
||||
void _csiWindowManipulation() {
|
||||
// The sequence needs at least one parameter.
|
||||
if (_csi.params.isEmpty) {
|
||||
return;
|
||||
}
|
||||
// Most the commands in this group are either of the scope of this package,
|
||||
// or should be disabled for security risks.
|
||||
switch (_csi.params.first) {
|
||||
// Window handling is currently not in the scope of the package.
|
||||
case 1: // Restore Terminal Window (show window if minimized)
|
||||
case 2: // Minimize Terminal Window
|
||||
case 3: // Set Terminal Window Position
|
||||
case 4: // Set Terminal Window Size in Pixels
|
||||
case 5: // Raise Terminal Window
|
||||
case 6: // Lower Terminal Window
|
||||
case 7: // Refresh/Redraw Terminal Window
|
||||
return;
|
||||
case 8: // Set Terminal Window Size (in characters)
|
||||
// This CSI contains 2 more parameters: width and height.
|
||||
if (_csi.params.length != 3) {
|
||||
return;
|
||||
}
|
||||
final rows = _csi.params[1];
|
||||
final cols = _csi.params[2];
|
||||
handler.resize(cols, rows);
|
||||
return;
|
||||
// Window handling is currently no in the scope of the package.
|
||||
case 9: // Maximize Terminal Window
|
||||
case 10: // Alias: Maximize Terminal Window
|
||||
case 11: // Report Terminal Window State
|
||||
case 13: // Report Terminal Window Position
|
||||
case 14: // Report Terminal Window Size in Pixels
|
||||
case 15: // Report Screen Size in Pixels
|
||||
case 16: // Report Cell Size in Pixels
|
||||
return;
|
||||
case 18: // Report Terminal Size (in characters)
|
||||
handler.sendSize();
|
||||
return;
|
||||
// Screen handling is currently no in the scope of the package.
|
||||
case 19: // Report Screen Size (in characters)
|
||||
// Disabled as these can a security risk.
|
||||
case 20: // Get Icon Title
|
||||
case 21: // Get Terminal Title
|
||||
// Not implemented.
|
||||
case 22: // Push Terminal Title
|
||||
case 23: // Pop Terminal Title
|
||||
return;
|
||||
// Unknown CSI.
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// `ESC [ Ps A` Cursor Up (CUU)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_ca/
|
||||
void _csiHandleCursorUp() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
if (amount == 0) amount = 1;
|
||||
}
|
||||
|
||||
handler.moveCursorY(-amount);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps B` Cursor Down (CUD)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_cb/
|
||||
void _csiHandleCursorDown() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
if (amount == 0) amount = 1;
|
||||
}
|
||||
|
||||
handler.moveCursorY(amount);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps C` Cursor Right (CUF)
|
||||
///
|
||||
/// Cursor Right (CUF)
|
||||
void _csiHandleCursorForward() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
if (amount == 0) amount = 1;
|
||||
}
|
||||
|
||||
handler.moveCursorX(amount);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps D` Cursor Left (CUB)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_cd/
|
||||
void _csiHandleCursorBackward() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
if (amount == 0) amount = 1;
|
||||
}
|
||||
|
||||
handler.moveCursorX(-amount);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps E` Cursor Next Line (CNL)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_ce/
|
||||
void _csiHandleCursorNextLine() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
if (amount == 0) amount = 1;
|
||||
}
|
||||
|
||||
handler.cursorNextLine(amount);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps F` Cursor Previous Line (CPL)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_cf/
|
||||
void _csiHandleCursorPrecedingLine() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
if (amount == 0) amount = 1;
|
||||
}
|
||||
|
||||
handler.cursorPrecedingLine(amount);
|
||||
}
|
||||
|
||||
void _csiHandleCursorHorizontalAbsolute() {
|
||||
var x = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
x = _csi.params[0];
|
||||
if (x == 0) x = 1;
|
||||
}
|
||||
|
||||
handler.setCursorX(x - 1);
|
||||
}
|
||||
|
||||
/// ESC [ Ps J Erase Display [Dispatch] (ED)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_cj/
|
||||
void _csiHandleEraseDisplay() {
|
||||
var cmd = 0;
|
||||
|
||||
if (_csi.params.length == 1) {
|
||||
cmd = _csi.params[0];
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case 0:
|
||||
return handler.eraseDisplayBelow();
|
||||
case 1:
|
||||
return handler.eraseDisplayAbove();
|
||||
case 2:
|
||||
return handler.eraseDisplay();
|
||||
case 3:
|
||||
return handler.eraseScrollbackOnly();
|
||||
}
|
||||
}
|
||||
|
||||
/// `ESC [ Ps K` Erase Line [Dispatch] (EL)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_ck/
|
||||
void _csiHandleEraseLine() {
|
||||
var cmd = 0;
|
||||
|
||||
if (_csi.params.length == 1) {
|
||||
cmd = _csi.params[0];
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case 0:
|
||||
return handler.eraseLineRight();
|
||||
case 1:
|
||||
return handler.eraseLineLeft();
|
||||
case 2:
|
||||
return handler.eraseLine();
|
||||
}
|
||||
}
|
||||
|
||||
/// `ESC [ Ps L` Insert Line (IL)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_cl/
|
||||
void _csiHandleInsertLines() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
}
|
||||
|
||||
handler.insertLines(amount);
|
||||
}
|
||||
|
||||
/// ESC [ Ps M Delete Line (DL)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_cm/
|
||||
void _csiHandleDeleteLines() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
}
|
||||
|
||||
handler.deleteLines(amount);
|
||||
}
|
||||
|
||||
/// ESC [ Ps P Delete Character (DCH)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_cp/
|
||||
void _csiHandleDelete() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
}
|
||||
|
||||
handler.deleteChars(amount);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps S` Scroll Up (SU)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_cs/
|
||||
void _csiHandleScrollUp() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
}
|
||||
|
||||
handler.scrollUp(amount);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps T `Scroll Down (SD)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_ct_1param/
|
||||
void _csiHandleScrollDown() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
}
|
||||
|
||||
handler.scrollDown(amount);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps X` Erase Character (ECH)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_cx/
|
||||
void _csiHandleEraseCharacters() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
}
|
||||
|
||||
handler.eraseChars(amount);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps @` Insert Blanks (ICH)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_x40_at/
|
||||
///
|
||||
/// Inserts amount spaces at current cursor position moving existing cell
|
||||
/// contents to the right. The contents of the amount right-most columns in
|
||||
/// the scroll region are lost. The cursor position is not changed.
|
||||
void _csiHandleInsertBlankCharacters() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
}
|
||||
|
||||
handler.insertBlankChars(amount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// Based on xterm.dart v4.0.0 by xuty (MIT). See LICENSE in this directory.
|
||||
|
||||
// ANSI + DEC private mode set/reset (CSI h / CSI l, with and without
|
||||
// the ? prefix). Split out of parser.dart (T-123).
|
||||
|
||||
part of 'parser.dart';
|
||||
|
||||
mixin _ModeHandlers on _EscapeParserBase {
|
||||
/// - `ESC [ [ Pm ] h Set Mode (SM)` https://terminalguide.namepad.de/seq/csi_sm/
|
||||
/// - `ESC [ ? [ Pm ] h` Set Mode (?) (SM) https://terminalguide.namepad.de/seq/csi_sh__p/
|
||||
/// - `ESC [ [ Pm ] l` Reset Mode (RM) https://terminalguide.namepad.de/seq/csi_rm/
|
||||
/// - `ESC [ ? [ Pm ] l` Reset Mode (?) (RM) https://terminalguide.namepad.de/seq/csi_sl__p/
|
||||
void _csiHandleMode() {
|
||||
final isEnabled = _csi.finalByte == Ascii.h;
|
||||
|
||||
final isDecModes = _csi.prefix == Ascii.questionMark;
|
||||
|
||||
if (isDecModes) {
|
||||
for (var mode in _csi.params) {
|
||||
_setDecMode(mode, isEnabled);
|
||||
}
|
||||
} else {
|
||||
for (var mode in _csi.params) {
|
||||
_setMode(mode, isEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _setMode(int mode, bool enabled) {
|
||||
switch (mode) {
|
||||
case 4:
|
||||
return handler.setInsertMode(enabled);
|
||||
case 20:
|
||||
return handler.setLineFeedMode(enabled);
|
||||
default:
|
||||
return handler.setUnknownMode(mode, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
void _setDecMode(int mode, bool enabled) {
|
||||
switch (mode) {
|
||||
case 1:
|
||||
return handler.setCursorKeysMode(enabled);
|
||||
case 3:
|
||||
return handler.setColumnMode(enabled);
|
||||
case 5:
|
||||
return handler.setReverseDisplayMode(enabled);
|
||||
case 6:
|
||||
return handler.setOriginMode(enabled);
|
||||
case 7:
|
||||
return handler.setAutoWrapMode(enabled);
|
||||
case 9:
|
||||
return enabled ? handler.setMouseMode(MouseMode.clickOnly) : handler.setMouseMode(MouseMode.none);
|
||||
case 12:
|
||||
case 13:
|
||||
return handler.setCursorBlinkMode(enabled);
|
||||
case 25:
|
||||
return handler.setCursorVisibleMode(enabled);
|
||||
case 47:
|
||||
if (enabled) {
|
||||
return handler.useAltBuffer();
|
||||
} else {
|
||||
return handler.useMainBuffer();
|
||||
}
|
||||
case 66:
|
||||
return handler.setAppKeypadMode(enabled);
|
||||
case 1000:
|
||||
return enabled ? handler.setMouseMode(MouseMode.upDownScroll) : handler.setMouseMode(MouseMode.none);
|
||||
case 1001:
|
||||
return enabled ? handler.setMouseMode(MouseMode.upDownScroll) : handler.setMouseMode(MouseMode.none);
|
||||
case 1002:
|
||||
return enabled ? handler.setMouseMode(MouseMode.upDownScrollDrag) : handler.setMouseMode(MouseMode.none);
|
||||
case 1003:
|
||||
return enabled ? handler.setMouseMode(MouseMode.upDownScrollMove) : handler.setMouseMode(MouseMode.none);
|
||||
case 1004:
|
||||
return handler.setReportFocusMode(enabled);
|
||||
case 1005:
|
||||
return enabled ? handler.setMouseReportMode(MouseReportMode.utf) : handler.setMouseReportMode(MouseReportMode.normal);
|
||||
case 1006:
|
||||
return enabled ? handler.setMouseReportMode(MouseReportMode.sgr) : handler.setMouseReportMode(MouseReportMode.normal);
|
||||
case 1007:
|
||||
return handler.setAltBufferMouseScrollMode(enabled);
|
||||
case 1015:
|
||||
return enabled ? handler.setMouseReportMode(MouseReportMode.urxvt) : handler.setMouseReportMode(MouseReportMode.normal);
|
||||
case 1047:
|
||||
if (enabled) {
|
||||
handler.useAltBuffer();
|
||||
} else {
|
||||
handler.clearAltBuffer();
|
||||
handler.useMainBuffer();
|
||||
}
|
||||
return;
|
||||
case 1048:
|
||||
if (enabled) {
|
||||
return handler.saveCursor();
|
||||
} else {
|
||||
return handler.restoreCursor();
|
||||
}
|
||||
case 1049:
|
||||
if (enabled) {
|
||||
handler.saveCursor();
|
||||
handler.clearAltBuffer();
|
||||
handler.useAltBuffer();
|
||||
} else {
|
||||
handler.useMainBuffer();
|
||||
}
|
||||
return;
|
||||
case 2004:
|
||||
return handler.setBracketedPasteMode(enabled);
|
||||
default:
|
||||
return handler.setUnknownDecMode(mode, enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// Based on xterm.dart v4.0.0 by xuty (MIT). See LICENSE in this directory.
|
||||
|
||||
// OSC string parsing + dispatch (title / icon name / private
|
||||
// pass-through), BEL or ST terminated. Split out of parser.dart
|
||||
// (T-123).
|
||||
|
||||
part of 'parser.dart';
|
||||
|
||||
mixin _OscHandlers on _EscapeParserBase {
|
||||
/// Parse a OSC sequence from the queue. Returns true if a sequence was
|
||||
/// found and handled.
|
||||
bool _escHandleOSC() {
|
||||
final consumed = _consumeOsc();
|
||||
if (!consumed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_osc.isEmpty) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Common OSCs
|
||||
if (_osc.length >= 2) {
|
||||
final ps = _osc[0];
|
||||
final pt = _osc[1];
|
||||
|
||||
switch (ps) {
|
||||
case '0':
|
||||
handler.setTitle(pt);
|
||||
handler.setIconName(pt);
|
||||
return true;
|
||||
case '1':
|
||||
handler.setIconName(pt);
|
||||
return true;
|
||||
case '2':
|
||||
handler.setTitle(pt);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Private extensions
|
||||
handler.unknownOSC(_osc[0], _osc.sublist(1));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
final _osc = <String>[];
|
||||
|
||||
bool _consumeOsc() {
|
||||
_osc.clear();
|
||||
final param = StringBuffer();
|
||||
|
||||
while (true) {
|
||||
if (_queue.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final char = _queue.consume();
|
||||
|
||||
// OSC terminates with BEL
|
||||
if (char == Ascii.BEL) {
|
||||
_osc.add(param.toString());
|
||||
return true;
|
||||
}
|
||||
|
||||
/// OSC terminates with ST
|
||||
if (char == Ascii.ESC) {
|
||||
if (_queue.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_queue.consume() == Ascii.backslash) {
|
||||
_osc.add(param.toString());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Parse next parameter
|
||||
if (char == Ascii.semicolon) {
|
||||
_osc.add(param.toString());
|
||||
param.clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
param.writeCharCode(char);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,16 +8,18 @@ import 'package:clide/src/terminal/src/utils/byte_consumer.dart';
|
||||
import 'package:clide/src/terminal/src/utils/char_code.dart';
|
||||
import 'package:clide/src/terminal/src/utils/lookup_table.dart';
|
||||
|
||||
/// [EscapeParser] translates control characters and escape sequences into
|
||||
/// function calls that the terminal can handle.
|
||||
///
|
||||
/// Design goals:
|
||||
/// * Zero object allocation during processing.
|
||||
/// * No internal state. Same input will always produce same output.
|
||||
class EscapeParser {
|
||||
final EscapeHandler handler;
|
||||
part 'csi_handlers.dart';
|
||||
part 'mode_handlers.dart';
|
||||
part 'osc_handlers.dart';
|
||||
part 'sgr_handlers.dart';
|
||||
|
||||
EscapeParser(this.handler);
|
||||
/// Shared parser state the handler mixins operate on: the escape
|
||||
/// handler sink, the byte queue, token bookkeeping, and the reusable
|
||||
/// CSI scratch object (zero-allocation design — see [EscapeParser]).
|
||||
abstract class _EscapeParserBase {
|
||||
_EscapeParserBase(this.handler);
|
||||
|
||||
final EscapeHandler handler;
|
||||
|
||||
final _queue = ByteConsumer();
|
||||
|
||||
@@ -27,6 +29,24 @@ class EscapeParser {
|
||||
/// End of sequence or character being processed. Useful for debugging.
|
||||
int get tokenEnd => _queue.totalConsumed;
|
||||
|
||||
/// The last parsed [_Csi]. This is a mutable singletion by design to reduce
|
||||
/// object allocations.
|
||||
final _csi = _Csi(finalByte: 0, params: []);
|
||||
}
|
||||
|
||||
/// [EscapeParser] translates control characters and escape sequences into
|
||||
/// function calls that the terminal can handle.
|
||||
///
|
||||
/// Design goals:
|
||||
/// * Zero object allocation during processing.
|
||||
/// * No internal state. Same input will always produce same output.
|
||||
///
|
||||
/// The handler groups live as mixins in this library's part files
|
||||
/// (csi/sgr/mode/osc handlers, T-123); this core owns the byte queue,
|
||||
/// the dispatch tables, and the ESC/CSI consumers.
|
||||
class EscapeParser extends _EscapeParserBase with _CsiHandlers, _ModeHandlers, _OscHandlers, _SgrHandlers {
|
||||
EscapeParser(super.handler);
|
||||
|
||||
void write(String chunk) {
|
||||
_queue.unrefConsumedBlocks();
|
||||
_queue.add(chunk);
|
||||
@@ -208,10 +228,6 @@ class EscapeParser {
|
||||
return true;
|
||||
}
|
||||
|
||||
/// The last parsed [_Csi]. This is a mutable singletion by design to reduce
|
||||
/// object allocations.
|
||||
final _csi = _Csi(finalByte: 0, params: []);
|
||||
|
||||
/// Parse a CSI from the head of the queue. Return false if the CSI isn't
|
||||
/// complete. After a CSI is successfully parsed, [_csi] is updated.
|
||||
bool _consumeCsi() {
|
||||
@@ -322,839 +338,6 @@ class EscapeParser {
|
||||
'X'.codeUnitAt(0): _csiHandleEraseCharacters,
|
||||
'@'.codeUnitAt(0): _csiHandleInsertBlankCharacters,
|
||||
});
|
||||
|
||||
/// `ESC [ Ps a` Cursor Horizontal Position Relative (HPR)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_sa/
|
||||
// void _csiHandleCursorHorizontalRelative() {
|
||||
// if (_csi.params.isEmpty) {
|
||||
// handler.cursorHorizontal(1);
|
||||
// } else {
|
||||
// handler.cursorHorizontal(_csi.params[0]);
|
||||
// }
|
||||
// }
|
||||
|
||||
/// `ESC [ Ps b` Repeat Previous Character (REP)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_sb/
|
||||
void _csiHandleRepeatPreviousCharacter() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
if (amount == 0) amount = 1;
|
||||
}
|
||||
|
||||
handler.repeatPreviousCharacter(amount);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps c` Device Attributes (DA)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_sc/
|
||||
void _csiHandleSendDeviceAttributes() {
|
||||
switch (_csi.prefix) {
|
||||
case Ascii.greaterThan:
|
||||
return handler.sendSecondaryDeviceAttributes();
|
||||
case Ascii.equal:
|
||||
return handler.sendTertiaryDeviceAttributes();
|
||||
default:
|
||||
handler.sendPrimaryDeviceAttributes();
|
||||
}
|
||||
}
|
||||
|
||||
/// `ESC [ Ps d` Cursor Vertical Position Absolute (VPA)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_sd/
|
||||
void _csiHandleLinePositionAbsolute() {
|
||||
var y = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
y = _csi.params[0];
|
||||
}
|
||||
|
||||
handler.setCursorY(y - 1);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps ; Ps f` Alias: Set Cursor Position
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_sf/
|
||||
void _csiHandleCursorPosition() {
|
||||
var row = 1;
|
||||
var col = 1;
|
||||
|
||||
if (_csi.params.length == 2) {
|
||||
row = _csi.params[0];
|
||||
col = _csi.params[1];
|
||||
}
|
||||
|
||||
handler.setCursor(col - 1, row - 1);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps g` Tab Clear (TBC)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_sg/
|
||||
void _csiHandelClearTabStop() {
|
||||
var cmd = 0;
|
||||
|
||||
if (_csi.params.length == 1) {
|
||||
cmd = _csi.params[0];
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case 0:
|
||||
return handler.clearTabStopUnderCursor();
|
||||
default:
|
||||
return handler.clearAllTabStops();
|
||||
}
|
||||
}
|
||||
|
||||
/// - `ESC [ [ Pm ] h Set Mode (SM)` https://terminalguide.namepad.de/seq/csi_sm/
|
||||
/// - `ESC [ ? [ Pm ] h` Set Mode (?) (SM) https://terminalguide.namepad.de/seq/csi_sh__p/
|
||||
/// - `ESC [ [ Pm ] l` Reset Mode (RM) https://terminalguide.namepad.de/seq/csi_rm/
|
||||
/// - `ESC [ ? [ Pm ] l` Reset Mode (?) (RM) https://terminalguide.namepad.de/seq/csi_sl__p/
|
||||
void _csiHandleMode() {
|
||||
final isEnabled = _csi.finalByte == Ascii.h;
|
||||
|
||||
final isDecModes = _csi.prefix == Ascii.questionMark;
|
||||
|
||||
if (isDecModes) {
|
||||
for (var mode in _csi.params) {
|
||||
_setDecMode(mode, isEnabled);
|
||||
}
|
||||
} else {
|
||||
for (var mode in _csi.params) {
|
||||
_setMode(mode, isEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `ESC [ [ Ps ] m` Select Graphic Rendition (SGR)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_sm/
|
||||
void _csiHandleSgr() {
|
||||
final params = _csi.params;
|
||||
|
||||
if (params.isEmpty) {
|
||||
return handler.resetCursorStyle();
|
||||
}
|
||||
|
||||
for (var i = 0; i < _csi.params.length; i++) {
|
||||
final param = params[i];
|
||||
switch (param) {
|
||||
case 0:
|
||||
handler.resetCursorStyle();
|
||||
continue;
|
||||
case 1:
|
||||
handler.setCursorBold();
|
||||
continue;
|
||||
case 2:
|
||||
handler.setCursorFaint();
|
||||
continue;
|
||||
case 3:
|
||||
handler.setCursorItalic();
|
||||
continue;
|
||||
case 4:
|
||||
handler.setCursorUnderline();
|
||||
continue;
|
||||
case 5:
|
||||
handler.setCursorBlink();
|
||||
continue;
|
||||
case 7:
|
||||
handler.setCursorInverse();
|
||||
continue;
|
||||
case 8:
|
||||
handler.setCursorInvisible();
|
||||
continue;
|
||||
case 9:
|
||||
handler.setCursorStrikethrough();
|
||||
continue;
|
||||
|
||||
case 21:
|
||||
handler.unsetCursorBold();
|
||||
continue;
|
||||
case 22:
|
||||
handler.unsetCursorFaint();
|
||||
continue;
|
||||
case 23:
|
||||
handler.unsetCursorItalic();
|
||||
continue;
|
||||
case 24:
|
||||
handler.unsetCursorUnderline();
|
||||
continue;
|
||||
case 25:
|
||||
handler.unsetCursorBlink();
|
||||
continue;
|
||||
case 27:
|
||||
handler.unsetCursorInverse();
|
||||
continue;
|
||||
case 28:
|
||||
handler.unsetCursorInvisible();
|
||||
continue;
|
||||
case 29:
|
||||
handler.unsetCursorStrikethrough();
|
||||
continue;
|
||||
|
||||
case 30:
|
||||
handler.setForegroundColor16(NamedColor.black);
|
||||
continue;
|
||||
case 31:
|
||||
handler.setForegroundColor16(NamedColor.red);
|
||||
continue;
|
||||
case 32:
|
||||
handler.setForegroundColor16(NamedColor.green);
|
||||
continue;
|
||||
case 33:
|
||||
handler.setForegroundColor16(NamedColor.yellow);
|
||||
continue;
|
||||
case 34:
|
||||
handler.setForegroundColor16(NamedColor.blue);
|
||||
continue;
|
||||
case 35:
|
||||
handler.setForegroundColor16(NamedColor.magenta);
|
||||
continue;
|
||||
case 36:
|
||||
handler.setForegroundColor16(NamedColor.cyan);
|
||||
continue;
|
||||
case 37:
|
||||
handler.setForegroundColor16(NamedColor.white);
|
||||
continue;
|
||||
case 38:
|
||||
i = _csiHandleExtendedColor(i, foreground: true);
|
||||
continue;
|
||||
case 39:
|
||||
handler.resetForeground();
|
||||
continue;
|
||||
|
||||
case 40:
|
||||
handler.setBackgroundColor16(NamedColor.black);
|
||||
continue;
|
||||
case 41:
|
||||
handler.setBackgroundColor16(NamedColor.red);
|
||||
continue;
|
||||
case 42:
|
||||
handler.setBackgroundColor16(NamedColor.green);
|
||||
continue;
|
||||
case 43:
|
||||
handler.setBackgroundColor16(NamedColor.yellow);
|
||||
continue;
|
||||
case 44:
|
||||
handler.setBackgroundColor16(NamedColor.blue);
|
||||
continue;
|
||||
case 45:
|
||||
handler.setBackgroundColor16(NamedColor.magenta);
|
||||
continue;
|
||||
case 46:
|
||||
handler.setBackgroundColor16(NamedColor.cyan);
|
||||
continue;
|
||||
case 47:
|
||||
handler.setBackgroundColor16(NamedColor.white);
|
||||
continue;
|
||||
case 48:
|
||||
i = _csiHandleExtendedColor(i, foreground: false);
|
||||
continue;
|
||||
case 49:
|
||||
handler.resetBackground();
|
||||
continue;
|
||||
|
||||
case 90:
|
||||
handler.setForegroundColor16(NamedColor.brightBlack);
|
||||
continue;
|
||||
case 91:
|
||||
handler.setForegroundColor16(NamedColor.brightRed);
|
||||
continue;
|
||||
case 92:
|
||||
handler.setForegroundColor16(NamedColor.brightGreen);
|
||||
continue;
|
||||
case 93:
|
||||
handler.setForegroundColor16(NamedColor.brightYellow);
|
||||
continue;
|
||||
case 94:
|
||||
handler.setForegroundColor16(NamedColor.brightBlue);
|
||||
continue;
|
||||
case 95:
|
||||
handler.setForegroundColor16(NamedColor.brightMagenta);
|
||||
continue;
|
||||
case 96:
|
||||
handler.setForegroundColor16(NamedColor.brightCyan);
|
||||
continue;
|
||||
case 97:
|
||||
handler.setForegroundColor16(NamedColor.brightWhite);
|
||||
continue;
|
||||
|
||||
case 100:
|
||||
handler.setBackgroundColor16(NamedColor.brightBlack);
|
||||
continue;
|
||||
case 101:
|
||||
handler.setBackgroundColor16(NamedColor.brightRed);
|
||||
continue;
|
||||
case 102:
|
||||
handler.setBackgroundColor16(NamedColor.brightGreen);
|
||||
continue;
|
||||
case 103:
|
||||
handler.setBackgroundColor16(NamedColor.brightYellow);
|
||||
continue;
|
||||
case 104:
|
||||
handler.setBackgroundColor16(NamedColor.brightBlue);
|
||||
continue;
|
||||
case 105:
|
||||
handler.setBackgroundColor16(NamedColor.brightMagenta);
|
||||
continue;
|
||||
case 106:
|
||||
handler.setBackgroundColor16(NamedColor.brightCyan);
|
||||
continue;
|
||||
case 107:
|
||||
handler.setBackgroundColor16(NamedColor.brightWhite);
|
||||
continue;
|
||||
|
||||
default:
|
||||
handler.unsupportedStyle(param);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extended fg/bg color (SGR 38/48), semicolon or colon form.
|
||||
///
|
||||
/// Returns the index of the last parameter consumed. Never reads past the
|
||||
/// end of the parameter list — a truncated sequence (`ESC [38m`,
|
||||
/// `ESC [38;2;255m`) is ignored instead of throwing; an emulator must never
|
||||
/// throw on hostile bytes (T-369). Colon-form sub-parameters per ITU T.416
|
||||
/// (`38:2:r:g:b`, `38:2:<colorspace>:r:g:b`, `38:5:n`) are treated as one
|
||||
/// logical group: parsed equivalently to the semicolon form, and dropped
|
||||
/// whole when malformed so they never spill into neighbouring parameters.
|
||||
int _csiHandleExtendedColor(int i, {required bool foreground}) {
|
||||
final params = _csi.params;
|
||||
final sub = _csi.subParam;
|
||||
|
||||
// End of the colon-linked group starting at params[i] (exclusive).
|
||||
var end = i + 1;
|
||||
while (end < params.length && sub[end]) {
|
||||
end++;
|
||||
}
|
||||
|
||||
if (end > i + 1) {
|
||||
// Colon form. Group is params[i..end-1]; n includes the 38/48 itself.
|
||||
final n = end - i;
|
||||
final mode = params[i + 1];
|
||||
if (mode == 5 && n >= 3) {
|
||||
foreground ? handler.setForegroundColor256(params[i + 2]) : handler.setBackgroundColor256(params[i + 2]);
|
||||
} else if (mode == 2) {
|
||||
// A 6+ element group carries the T.416 colorspace id slot — skip it.
|
||||
final base = n >= 6 ? i + 3 : i + 2;
|
||||
if (base + 2 < end) {
|
||||
foreground
|
||||
? handler.setForegroundColorRgb(params[base], params[base + 1], params[base + 2])
|
||||
: handler.setBackgroundColorRgb(params[base], params[base + 1], params[base + 2]);
|
||||
}
|
||||
}
|
||||
return end - 1;
|
||||
}
|
||||
|
||||
// Semicolon form (legacy).
|
||||
if (i + 1 >= params.length) return i; // bare 38/48 — ignore
|
||||
switch (params[i + 1]) {
|
||||
case 2:
|
||||
if (i + 4 >= params.length) return params.length - 1; // truncated — ignore
|
||||
foreground
|
||||
? handler.setForegroundColorRgb(params[i + 2], params[i + 3], params[i + 4])
|
||||
: handler.setBackgroundColorRgb(params[i + 2], params[i + 3], params[i + 4]);
|
||||
return i + 4;
|
||||
case 5:
|
||||
if (i + 2 >= params.length) return params.length - 1; // truncated — ignore
|
||||
foreground ? handler.setForegroundColor256(params[i + 2]) : handler.setBackgroundColor256(params[i + 2]);
|
||||
return i + 2;
|
||||
}
|
||||
// Unknown mode — consume it so it isn't re-interpreted as an SGR code.
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
/// `ESC [ Ps n` Device Status Report [Dispatch] (DSR)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_sn/
|
||||
void _csiHandleDeviceStatusReport() {
|
||||
if (_csi.params.isEmpty) return;
|
||||
|
||||
switch (_csi.params[0]) {
|
||||
case 5:
|
||||
return handler.sendOperatingStatus();
|
||||
case 6:
|
||||
return handler.sendCursorPosition();
|
||||
}
|
||||
}
|
||||
|
||||
/// `ESC [ Ps ; Ps r` Set Top and Bottom Margins (DECSTBM)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_sr/
|
||||
void _csiHandleSetMargins() {
|
||||
var top = 1;
|
||||
int? bottom;
|
||||
|
||||
if (_csi.params.length > 2) return;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
top = _csi.params[0];
|
||||
|
||||
if (_csi.params.length == 2) {
|
||||
bottom = _csi.params[1] - 1;
|
||||
}
|
||||
}
|
||||
|
||||
handler.setMargins(top - 1, bottom);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps t` Window operations [DISPATCH]
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_st/
|
||||
void _csiWindowManipulation() {
|
||||
// The sequence needs at least one parameter.
|
||||
if (_csi.params.isEmpty) {
|
||||
return;
|
||||
}
|
||||
// Most the commands in this group are either of the scope of this package,
|
||||
// or should be disabled for security risks.
|
||||
switch (_csi.params.first) {
|
||||
// Window handling is currently not in the scope of the package.
|
||||
case 1: // Restore Terminal Window (show window if minimized)
|
||||
case 2: // Minimize Terminal Window
|
||||
case 3: // Set Terminal Window Position
|
||||
case 4: // Set Terminal Window Size in Pixels
|
||||
case 5: // Raise Terminal Window
|
||||
case 6: // Lower Terminal Window
|
||||
case 7: // Refresh/Redraw Terminal Window
|
||||
return;
|
||||
case 8: // Set Terminal Window Size (in characters)
|
||||
// This CSI contains 2 more parameters: width and height.
|
||||
if (_csi.params.length != 3) {
|
||||
return;
|
||||
}
|
||||
final rows = _csi.params[1];
|
||||
final cols = _csi.params[2];
|
||||
handler.resize(cols, rows);
|
||||
return;
|
||||
// Window handling is currently no in the scope of the package.
|
||||
case 9: // Maximize Terminal Window
|
||||
case 10: // Alias: Maximize Terminal Window
|
||||
case 11: // Report Terminal Window State
|
||||
case 13: // Report Terminal Window Position
|
||||
case 14: // Report Terminal Window Size in Pixels
|
||||
case 15: // Report Screen Size in Pixels
|
||||
case 16: // Report Cell Size in Pixels
|
||||
return;
|
||||
case 18: // Report Terminal Size (in characters)
|
||||
handler.sendSize();
|
||||
return;
|
||||
// Screen handling is currently no in the scope of the package.
|
||||
case 19: // Report Screen Size (in characters)
|
||||
// Disabled as these can a security risk.
|
||||
case 20: // Get Icon Title
|
||||
case 21: // Get Terminal Title
|
||||
// Not implemented.
|
||||
case 22: // Push Terminal Title
|
||||
case 23: // Pop Terminal Title
|
||||
return;
|
||||
// Unknown CSI.
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// `ESC [ Ps A` Cursor Up (CUU)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_ca/
|
||||
void _csiHandleCursorUp() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
if (amount == 0) amount = 1;
|
||||
}
|
||||
|
||||
handler.moveCursorY(-amount);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps B` Cursor Down (CUD)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_cb/
|
||||
void _csiHandleCursorDown() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
if (amount == 0) amount = 1;
|
||||
}
|
||||
|
||||
handler.moveCursorY(amount);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps C` Cursor Right (CUF)
|
||||
///
|
||||
/// Cursor Right (CUF)
|
||||
void _csiHandleCursorForward() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
if (amount == 0) amount = 1;
|
||||
}
|
||||
|
||||
handler.moveCursorX(amount);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps D` Cursor Left (CUB)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_cd/
|
||||
void _csiHandleCursorBackward() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
if (amount == 0) amount = 1;
|
||||
}
|
||||
|
||||
handler.moveCursorX(-amount);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps E` Cursor Next Line (CNL)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_ce/
|
||||
void _csiHandleCursorNextLine() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
if (amount == 0) amount = 1;
|
||||
}
|
||||
|
||||
handler.cursorNextLine(amount);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps F` Cursor Previous Line (CPL)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_cf/
|
||||
void _csiHandleCursorPrecedingLine() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
if (amount == 0) amount = 1;
|
||||
}
|
||||
|
||||
handler.cursorPrecedingLine(amount);
|
||||
}
|
||||
|
||||
void _csiHandleCursorHorizontalAbsolute() {
|
||||
var x = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
x = _csi.params[0];
|
||||
if (x == 0) x = 1;
|
||||
}
|
||||
|
||||
handler.setCursorX(x - 1);
|
||||
}
|
||||
|
||||
/// ESC [ Ps J Erase Display [Dispatch] (ED)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_cj/
|
||||
void _csiHandleEraseDisplay() {
|
||||
var cmd = 0;
|
||||
|
||||
if (_csi.params.length == 1) {
|
||||
cmd = _csi.params[0];
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case 0:
|
||||
return handler.eraseDisplayBelow();
|
||||
case 1:
|
||||
return handler.eraseDisplayAbove();
|
||||
case 2:
|
||||
return handler.eraseDisplay();
|
||||
case 3:
|
||||
return handler.eraseScrollbackOnly();
|
||||
}
|
||||
}
|
||||
|
||||
/// `ESC [ Ps K` Erase Line [Dispatch] (EL)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_ck/
|
||||
void _csiHandleEraseLine() {
|
||||
var cmd = 0;
|
||||
|
||||
if (_csi.params.length == 1) {
|
||||
cmd = _csi.params[0];
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case 0:
|
||||
return handler.eraseLineRight();
|
||||
case 1:
|
||||
return handler.eraseLineLeft();
|
||||
case 2:
|
||||
return handler.eraseLine();
|
||||
}
|
||||
}
|
||||
|
||||
/// `ESC [ Ps L` Insert Line (IL)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_cl/
|
||||
void _csiHandleInsertLines() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
}
|
||||
|
||||
handler.insertLines(amount);
|
||||
}
|
||||
|
||||
/// ESC [ Ps M Delete Line (DL)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_cm/
|
||||
void _csiHandleDeleteLines() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
}
|
||||
|
||||
handler.deleteLines(amount);
|
||||
}
|
||||
|
||||
/// ESC [ Ps P Delete Character (DCH)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_cp/
|
||||
void _csiHandleDelete() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
}
|
||||
|
||||
handler.deleteChars(amount);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps S` Scroll Up (SU)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_cs/
|
||||
void _csiHandleScrollUp() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
}
|
||||
|
||||
handler.scrollUp(amount);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps T `Scroll Down (SD)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_ct_1param/
|
||||
void _csiHandleScrollDown() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
}
|
||||
|
||||
handler.scrollDown(amount);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps X` Erase Character (ECH)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_cx/
|
||||
void _csiHandleEraseCharacters() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
}
|
||||
|
||||
handler.eraseChars(amount);
|
||||
}
|
||||
|
||||
/// `ESC [ Ps @` Insert Blanks (ICH)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_x40_at/
|
||||
///
|
||||
/// Inserts amount spaces at current cursor position moving existing cell
|
||||
/// contents to the right. The contents of the amount right-most columns in
|
||||
/// the scroll region are lost. The cursor position is not changed.
|
||||
void _csiHandleInsertBlankCharacters() {
|
||||
var amount = 1;
|
||||
|
||||
if (_csi.params.isNotEmpty) {
|
||||
amount = _csi.params[0];
|
||||
}
|
||||
|
||||
handler.insertBlankChars(amount);
|
||||
}
|
||||
|
||||
void _setMode(int mode, bool enabled) {
|
||||
switch (mode) {
|
||||
case 4:
|
||||
return handler.setInsertMode(enabled);
|
||||
case 20:
|
||||
return handler.setLineFeedMode(enabled);
|
||||
default:
|
||||
return handler.setUnknownMode(mode, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
void _setDecMode(int mode, bool enabled) {
|
||||
switch (mode) {
|
||||
case 1:
|
||||
return handler.setCursorKeysMode(enabled);
|
||||
case 3:
|
||||
return handler.setColumnMode(enabled);
|
||||
case 5:
|
||||
return handler.setReverseDisplayMode(enabled);
|
||||
case 6:
|
||||
return handler.setOriginMode(enabled);
|
||||
case 7:
|
||||
return handler.setAutoWrapMode(enabled);
|
||||
case 9:
|
||||
return enabled ? handler.setMouseMode(MouseMode.clickOnly) : handler.setMouseMode(MouseMode.none);
|
||||
case 12:
|
||||
case 13:
|
||||
return handler.setCursorBlinkMode(enabled);
|
||||
case 25:
|
||||
return handler.setCursorVisibleMode(enabled);
|
||||
case 47:
|
||||
if (enabled) {
|
||||
return handler.useAltBuffer();
|
||||
} else {
|
||||
return handler.useMainBuffer();
|
||||
}
|
||||
case 66:
|
||||
return handler.setAppKeypadMode(enabled);
|
||||
case 1000:
|
||||
return enabled ? handler.setMouseMode(MouseMode.upDownScroll) : handler.setMouseMode(MouseMode.none);
|
||||
case 1001:
|
||||
return enabled ? handler.setMouseMode(MouseMode.upDownScroll) : handler.setMouseMode(MouseMode.none);
|
||||
case 1002:
|
||||
return enabled ? handler.setMouseMode(MouseMode.upDownScrollDrag) : handler.setMouseMode(MouseMode.none);
|
||||
case 1003:
|
||||
return enabled ? handler.setMouseMode(MouseMode.upDownScrollMove) : handler.setMouseMode(MouseMode.none);
|
||||
case 1004:
|
||||
return handler.setReportFocusMode(enabled);
|
||||
case 1005:
|
||||
return enabled ? handler.setMouseReportMode(MouseReportMode.utf) : handler.setMouseReportMode(MouseReportMode.normal);
|
||||
case 1006:
|
||||
return enabled ? handler.setMouseReportMode(MouseReportMode.sgr) : handler.setMouseReportMode(MouseReportMode.normal);
|
||||
case 1007:
|
||||
return handler.setAltBufferMouseScrollMode(enabled);
|
||||
case 1015:
|
||||
return enabled ? handler.setMouseReportMode(MouseReportMode.urxvt) : handler.setMouseReportMode(MouseReportMode.normal);
|
||||
case 1047:
|
||||
if (enabled) {
|
||||
handler.useAltBuffer();
|
||||
} else {
|
||||
handler.clearAltBuffer();
|
||||
handler.useMainBuffer();
|
||||
}
|
||||
return;
|
||||
case 1048:
|
||||
if (enabled) {
|
||||
return handler.saveCursor();
|
||||
} else {
|
||||
return handler.restoreCursor();
|
||||
}
|
||||
case 1049:
|
||||
if (enabled) {
|
||||
handler.saveCursor();
|
||||
handler.clearAltBuffer();
|
||||
handler.useAltBuffer();
|
||||
} else {
|
||||
handler.useMainBuffer();
|
||||
}
|
||||
return;
|
||||
case 2004:
|
||||
return handler.setBracketedPasteMode(enabled);
|
||||
default:
|
||||
return handler.setUnknownDecMode(mode, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a OSC sequence from the queue. Returns true if a sequence was
|
||||
/// found and handled.
|
||||
bool _escHandleOSC() {
|
||||
final consumed = _consumeOsc();
|
||||
if (!consumed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_osc.isEmpty) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Common OSCs
|
||||
if (_osc.length >= 2) {
|
||||
final ps = _osc[0];
|
||||
final pt = _osc[1];
|
||||
|
||||
switch (ps) {
|
||||
case '0':
|
||||
handler.setTitle(pt);
|
||||
handler.setIconName(pt);
|
||||
return true;
|
||||
case '1':
|
||||
handler.setIconName(pt);
|
||||
return true;
|
||||
case '2':
|
||||
handler.setTitle(pt);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Private extensions
|
||||
handler.unknownOSC(_osc[0], _osc.sublist(1));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
final _osc = <String>[];
|
||||
|
||||
bool _consumeOsc() {
|
||||
_osc.clear();
|
||||
final param = StringBuffer();
|
||||
|
||||
while (true) {
|
||||
if (_queue.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final char = _queue.consume();
|
||||
|
||||
// OSC terminates with BEL
|
||||
if (char == Ascii.BEL) {
|
||||
_osc.add(param.toString());
|
||||
return true;
|
||||
}
|
||||
|
||||
/// OSC terminates with ST
|
||||
if (char == Ascii.ESC) {
|
||||
if (_queue.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_queue.consume() == Ascii.backslash) {
|
||||
_osc.add(param.toString());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Parse next parameter
|
||||
if (char == Ascii.semicolon) {
|
||||
_osc.add(param.toString());
|
||||
param.clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
param.writeCharCode(char);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _Csi {
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
// Based on xterm.dart v4.0.0 by xuty (MIT). See LICENSE in this directory.
|
||||
|
||||
// SGR (Select Graphic Rendition) handling, including the guarded
|
||||
// extended-color (38/48) path with ITU T.416 colon sub-parameters
|
||||
// (T-369). Split out of parser.dart (T-123).
|
||||
|
||||
part of 'parser.dart';
|
||||
|
||||
mixin _SgrHandlers on _EscapeParserBase {
|
||||
/// `ESC [ [ Ps ] m` Select Graphic Rendition (SGR)
|
||||
///
|
||||
/// https://terminalguide.namepad.de/seq/csi_sm/
|
||||
void _csiHandleSgr() {
|
||||
final params = _csi.params;
|
||||
|
||||
if (params.isEmpty) {
|
||||
return handler.resetCursorStyle();
|
||||
}
|
||||
|
||||
for (var i = 0; i < _csi.params.length; i++) {
|
||||
final param = params[i];
|
||||
switch (param) {
|
||||
case 0:
|
||||
handler.resetCursorStyle();
|
||||
continue;
|
||||
case 1:
|
||||
handler.setCursorBold();
|
||||
continue;
|
||||
case 2:
|
||||
handler.setCursorFaint();
|
||||
continue;
|
||||
case 3:
|
||||
handler.setCursorItalic();
|
||||
continue;
|
||||
case 4:
|
||||
handler.setCursorUnderline();
|
||||
continue;
|
||||
case 5:
|
||||
handler.setCursorBlink();
|
||||
continue;
|
||||
case 7:
|
||||
handler.setCursorInverse();
|
||||
continue;
|
||||
case 8:
|
||||
handler.setCursorInvisible();
|
||||
continue;
|
||||
case 9:
|
||||
handler.setCursorStrikethrough();
|
||||
continue;
|
||||
|
||||
case 21:
|
||||
handler.unsetCursorBold();
|
||||
continue;
|
||||
case 22:
|
||||
handler.unsetCursorFaint();
|
||||
continue;
|
||||
case 23:
|
||||
handler.unsetCursorItalic();
|
||||
continue;
|
||||
case 24:
|
||||
handler.unsetCursorUnderline();
|
||||
continue;
|
||||
case 25:
|
||||
handler.unsetCursorBlink();
|
||||
continue;
|
||||
case 27:
|
||||
handler.unsetCursorInverse();
|
||||
continue;
|
||||
case 28:
|
||||
handler.unsetCursorInvisible();
|
||||
continue;
|
||||
case 29:
|
||||
handler.unsetCursorStrikethrough();
|
||||
continue;
|
||||
|
||||
case 30:
|
||||
handler.setForegroundColor16(NamedColor.black);
|
||||
continue;
|
||||
case 31:
|
||||
handler.setForegroundColor16(NamedColor.red);
|
||||
continue;
|
||||
case 32:
|
||||
handler.setForegroundColor16(NamedColor.green);
|
||||
continue;
|
||||
case 33:
|
||||
handler.setForegroundColor16(NamedColor.yellow);
|
||||
continue;
|
||||
case 34:
|
||||
handler.setForegroundColor16(NamedColor.blue);
|
||||
continue;
|
||||
case 35:
|
||||
handler.setForegroundColor16(NamedColor.magenta);
|
||||
continue;
|
||||
case 36:
|
||||
handler.setForegroundColor16(NamedColor.cyan);
|
||||
continue;
|
||||
case 37:
|
||||
handler.setForegroundColor16(NamedColor.white);
|
||||
continue;
|
||||
case 38:
|
||||
i = _csiHandleExtendedColor(i, foreground: true);
|
||||
continue;
|
||||
case 39:
|
||||
handler.resetForeground();
|
||||
continue;
|
||||
|
||||
case 40:
|
||||
handler.setBackgroundColor16(NamedColor.black);
|
||||
continue;
|
||||
case 41:
|
||||
handler.setBackgroundColor16(NamedColor.red);
|
||||
continue;
|
||||
case 42:
|
||||
handler.setBackgroundColor16(NamedColor.green);
|
||||
continue;
|
||||
case 43:
|
||||
handler.setBackgroundColor16(NamedColor.yellow);
|
||||
continue;
|
||||
case 44:
|
||||
handler.setBackgroundColor16(NamedColor.blue);
|
||||
continue;
|
||||
case 45:
|
||||
handler.setBackgroundColor16(NamedColor.magenta);
|
||||
continue;
|
||||
case 46:
|
||||
handler.setBackgroundColor16(NamedColor.cyan);
|
||||
continue;
|
||||
case 47:
|
||||
handler.setBackgroundColor16(NamedColor.white);
|
||||
continue;
|
||||
case 48:
|
||||
i = _csiHandleExtendedColor(i, foreground: false);
|
||||
continue;
|
||||
case 49:
|
||||
handler.resetBackground();
|
||||
continue;
|
||||
|
||||
case 90:
|
||||
handler.setForegroundColor16(NamedColor.brightBlack);
|
||||
continue;
|
||||
case 91:
|
||||
handler.setForegroundColor16(NamedColor.brightRed);
|
||||
continue;
|
||||
case 92:
|
||||
handler.setForegroundColor16(NamedColor.brightGreen);
|
||||
continue;
|
||||
case 93:
|
||||
handler.setForegroundColor16(NamedColor.brightYellow);
|
||||
continue;
|
||||
case 94:
|
||||
handler.setForegroundColor16(NamedColor.brightBlue);
|
||||
continue;
|
||||
case 95:
|
||||
handler.setForegroundColor16(NamedColor.brightMagenta);
|
||||
continue;
|
||||
case 96:
|
||||
handler.setForegroundColor16(NamedColor.brightCyan);
|
||||
continue;
|
||||
case 97:
|
||||
handler.setForegroundColor16(NamedColor.brightWhite);
|
||||
continue;
|
||||
|
||||
case 100:
|
||||
handler.setBackgroundColor16(NamedColor.brightBlack);
|
||||
continue;
|
||||
case 101:
|
||||
handler.setBackgroundColor16(NamedColor.brightRed);
|
||||
continue;
|
||||
case 102:
|
||||
handler.setBackgroundColor16(NamedColor.brightGreen);
|
||||
continue;
|
||||
case 103:
|
||||
handler.setBackgroundColor16(NamedColor.brightYellow);
|
||||
continue;
|
||||
case 104:
|
||||
handler.setBackgroundColor16(NamedColor.brightBlue);
|
||||
continue;
|
||||
case 105:
|
||||
handler.setBackgroundColor16(NamedColor.brightMagenta);
|
||||
continue;
|
||||
case 106:
|
||||
handler.setBackgroundColor16(NamedColor.brightCyan);
|
||||
continue;
|
||||
case 107:
|
||||
handler.setBackgroundColor16(NamedColor.brightWhite);
|
||||
continue;
|
||||
|
||||
default:
|
||||
handler.unsupportedStyle(param);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extended fg/bg color (SGR 38/48), semicolon or colon form.
|
||||
///
|
||||
/// Returns the index of the last parameter consumed. Never reads past the
|
||||
/// end of the parameter list — a truncated sequence (`ESC [38m`,
|
||||
/// `ESC [38;2;255m`) is ignored instead of throwing; an emulator must never
|
||||
/// throw on hostile bytes (T-369). Colon-form sub-parameters per ITU T.416
|
||||
/// (`38:2:r:g:b`, `38:2:<colorspace>:r:g:b`, `38:5:n`) are treated as one
|
||||
/// logical group: parsed equivalently to the semicolon form, and dropped
|
||||
/// whole when malformed so they never spill into neighbouring parameters.
|
||||
int _csiHandleExtendedColor(int i, {required bool foreground}) {
|
||||
final params = _csi.params;
|
||||
final sub = _csi.subParam;
|
||||
|
||||
// End of the colon-linked group starting at params[i] (exclusive).
|
||||
var end = i + 1;
|
||||
while (end < params.length && sub[end]) {
|
||||
end++;
|
||||
}
|
||||
|
||||
if (end > i + 1) {
|
||||
// Colon form. Group is params[i..end-1]; n includes the 38/48 itself.
|
||||
final n = end - i;
|
||||
final mode = params[i + 1];
|
||||
if (mode == 5 && n >= 3) {
|
||||
foreground ? handler.setForegroundColor256(params[i + 2]) : handler.setBackgroundColor256(params[i + 2]);
|
||||
} else if (mode == 2) {
|
||||
// A 6+ element group carries the T.416 colorspace id slot — skip it.
|
||||
final base = n >= 6 ? i + 3 : i + 2;
|
||||
if (base + 2 < end) {
|
||||
foreground
|
||||
? handler.setForegroundColorRgb(params[base], params[base + 1], params[base + 2])
|
||||
: handler.setBackgroundColorRgb(params[base], params[base + 1], params[base + 2]);
|
||||
}
|
||||
}
|
||||
return end - 1;
|
||||
}
|
||||
|
||||
// Semicolon form (legacy).
|
||||
if (i + 1 >= params.length) return i; // bare 38/48 — ignore
|
||||
switch (params[i + 1]) {
|
||||
case 2:
|
||||
if (i + 4 >= params.length) return params.length - 1; // truncated — ignore
|
||||
foreground
|
||||
? handler.setForegroundColorRgb(params[i + 2], params[i + 3], params[i + 4])
|
||||
: handler.setBackgroundColorRgb(params[i + 2], params[i + 3], params[i + 4]);
|
||||
return i + 4;
|
||||
case 5:
|
||||
if (i + 2 >= params.length) return params.length - 1; // truncated — ignore
|
||||
foreground ? handler.setForegroundColor256(params[i + 2]) : handler.setBackgroundColor256(params[i + 2]);
|
||||
return i + 2;
|
||||
}
|
||||
// Unknown mode — consume it so it isn't re-interpreted as an SGR code.
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user