|
|
@@ -0,0 +1,1095 @@
|
|
|
+import 'package:xterm/core/color.dart';
|
|
|
+import 'package:xterm/core/mouse.dart';
|
|
|
+import 'package:xterm/core/escape/handler.dart';
|
|
|
+import 'package:xterm/utils/ascii.dart';
|
|
|
+import 'package:xterm/utils/byte_consumer.dart';
|
|
|
+import 'package:xterm/utils/char_code.dart';
|
|
|
+import 'package:xterm/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;
|
|
|
+
|
|
|
+ EscapeParser(this.handler);
|
|
|
+
|
|
|
+ final _queue = ByteConsumer();
|
|
|
+
|
|
|
+ /// Start of sequence or character being processed. Useful for debugging.
|
|
|
+ var tokenBegin = 0;
|
|
|
+
|
|
|
+ /// End of sequence or character being processed. Useful for debugging.
|
|
|
+ int get tokenEnd => _queue.totalConsumed;
|
|
|
+
|
|
|
+ void write(String chunk) {
|
|
|
+ _queue.unrefConsumedBlocks();
|
|
|
+ _queue.add(chunk);
|
|
|
+ _process();
|
|
|
+ }
|
|
|
+
|
|
|
+ void _process() {
|
|
|
+ while (_queue.isNotEmpty) {
|
|
|
+ tokenBegin = _queue.totalConsumed;
|
|
|
+ final char = _queue.consume();
|
|
|
+
|
|
|
+ if (char == Ascii.ESC) {
|
|
|
+ final processed = _processEscape();
|
|
|
+ if (!processed) {
|
|
|
+ _queue.rollback(tokenEnd - tokenBegin);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ _processChar(char);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void _processChar(int char) {
|
|
|
+ if (char > _sbcHandlers.maxIndex) {
|
|
|
+ handler.writeChar(char);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ final sbcHandler = _sbcHandlers[char];
|
|
|
+ if (sbcHandler == null) {
|
|
|
+ handler.unkownEscape(char);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ sbcHandler();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Processes a sequence of characters that starts with an escape character.
|
|
|
+ /// Returns [true] if the sequence was processed, [false] if it was not.
|
|
|
+ bool _processEscape() {
|
|
|
+ if (_queue.isEmpty) return false;
|
|
|
+
|
|
|
+ final escapeChar = _queue.consume();
|
|
|
+ final escapeHandler = _escHandlers[escapeChar];
|
|
|
+
|
|
|
+ if (escapeHandler == null) {
|
|
|
+ handler.unkownEscape(escapeChar);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return escapeHandler();
|
|
|
+ }
|
|
|
+
|
|
|
+ late final _sbcHandlers = FastLookupTable<_SbcHandler>({
|
|
|
+ 0x07: handler.bell,
|
|
|
+ 0x08: handler.backspaceReturn,
|
|
|
+ 0x09: handler.tab,
|
|
|
+ 0x0a: handler.lineFeed,
|
|
|
+ 0x0b: handler.lineFeed,
|
|
|
+ 0x0c: handler.lineFeed,
|
|
|
+ 0x0d: handler.carriageReturn,
|
|
|
+ 0x0e: handler.shiftOut,
|
|
|
+ 0x0f: handler.shiftIn,
|
|
|
+ });
|
|
|
+
|
|
|
+ late final _escHandlers = FastLookupTable<_EscHandler>({
|
|
|
+ '['.charCode: _escHandleCSI,
|
|
|
+ ']'.charCode: _escHandleOSC,
|
|
|
+ '7'.charCode: _escHandleSaveCursor,
|
|
|
+ '8'.charCode: _escHandleRestoreCursor,
|
|
|
+ 'D'.charCode: _escHandleIndex,
|
|
|
+ 'E'.charCode: _escHandleNextLine,
|
|
|
+ 'H'.charCode: _escHandleTabSet,
|
|
|
+ 'M'.charCode: _escHandleReverseIndex,
|
|
|
+ // 'P'.charCode: _unsupportedHandler, // Sixel
|
|
|
+ // 'c'.charCode: _unsupportedHandler,
|
|
|
+ // '#'.charCode: _unsupportedHandler,
|
|
|
+ '('.charCode: _escHandleDesignateCharset0, // SCS - G0
|
|
|
+ ')'.charCode: _escHandleDesignateCharset1, // SCS - G1
|
|
|
+ // '*'.charCode: _voidHandler(1), // TODO: G2 (vt220)
|
|
|
+ // '+'.charCode: _voidHandler(1), // TODO: G3 (vt220)
|
|
|
+ '>'.charCode: _escHandleResetAppKeypadMode, // TODO: Normal Keypad
|
|
|
+ '='.charCode: _escHandleSetAppKeypadMode, // TODO: Application Keypad
|
|
|
+ });
|
|
|
+
|
|
|
+ /// `ESC 7` Save Cursor (DECSC)
|
|
|
+ ///
|
|
|
+ /// https://terminalguide.namepad.de/seq/a_esc_a7/
|
|
|
+ bool _escHandleSaveCursor() {
|
|
|
+ handler.saveCursor();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// `ESC 8` Restore Cursor (DECRC)
|
|
|
+ ///
|
|
|
+ /// https://terminalguide.namepad.de/seq/a_esc_a8/
|
|
|
+ bool _escHandleRestoreCursor() {
|
|
|
+ handler.restoreCursor();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// `ESC D` Index (IND)
|
|
|
+ ///
|
|
|
+ /// https://terminalguide.namepad.de/seq/a_esc_cd/
|
|
|
+ bool _escHandleIndex() {
|
|
|
+ handler.index();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// `ESC E` Next Line (NEL)
|
|
|
+ ///
|
|
|
+ /// https://terminalguide.namepad.de/seq/a_esc_ce/
|
|
|
+ bool _escHandleNextLine() {
|
|
|
+ handler.nextLine();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// `ESC H` Horizontal Tab Set (HTS)
|
|
|
+ ///
|
|
|
+ /// https://terminalguide.namepad.de/seq/a_esc_ch/
|
|
|
+ bool _escHandleTabSet() {
|
|
|
+ handler.setTapStop();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// `ESC M` Reverse Index (RI)
|
|
|
+ ///
|
|
|
+ /// https://terminalguide.namepad.de/seq/a_esc_cm/
|
|
|
+ bool _escHandleReverseIndex() {
|
|
|
+ handler.reverseIndex();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ bool _escHandleDesignateCharset0() {
|
|
|
+ if (_queue.isEmpty) return false;
|
|
|
+ _queue.consume();
|
|
|
+ handler.designateCharset(0);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ bool _escHandleDesignateCharset1() {
|
|
|
+ if (_queue.isEmpty) return false;
|
|
|
+ _queue.consume();
|
|
|
+ handler.designateCharset(1);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// `ESC >` Reset Application Keypad Mode (DECKPNM)
|
|
|
+ ///
|
|
|
+ /// https://terminalguide.namepad.de/seq/a_esc_x3c_greater_than/
|
|
|
+ bool _escHandleSetAppKeypadMode() {
|
|
|
+ handler.setAppKeypadMode(true);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// `ESC =` Set Application Keypad Mode (DECKPAM)
|
|
|
+ ///
|
|
|
+ /// https://terminalguide.namepad.de/seq/a_esc_x3d_equals/
|
|
|
+ bool _escHandleResetAppKeypadMode() {
|
|
|
+ handler.setAppKeypadMode(false);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ bool _escHandleCSI() {
|
|
|
+ final consumed = _consumeCsi();
|
|
|
+ if (!consumed) return false;
|
|
|
+
|
|
|
+ final csiHandler = _csiHandlers[_csi.finalByte];
|
|
|
+
|
|
|
+ if (csiHandler == null) {
|
|
|
+ handler.unknownCSI(_csi.finalByte);
|
|
|
+ } else {
|
|
|
+ csiHandler();
|
|
|
+ }
|
|
|
+
|
|
|
+ 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() {
|
|
|
+ if (_queue.isEmpty) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ _csi.params.clear();
|
|
|
+
|
|
|
+ // test whether the csi is a `CSI ? Ps ...` or `CSI Ps ...`
|
|
|
+ final prefix = _queue.peek();
|
|
|
+ if (prefix >= Ascii.colon && prefix <= Ascii.questionMark) {
|
|
|
+ _csi.prefix = prefix;
|
|
|
+ _queue.consume();
|
|
|
+ } else {
|
|
|
+ _csi.prefix = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ var param = 0;
|
|
|
+ var hasParam = false;
|
|
|
+ while (true) {
|
|
|
+ // The sequence isn't completed, just ignore it.
|
|
|
+ if (_queue.isEmpty) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ final char = _queue.consume();
|
|
|
+
|
|
|
+ if (char == Ascii.semicolon) {
|
|
|
+ if (hasParam) {
|
|
|
+ _csi.params.add(param);
|
|
|
+ }
|
|
|
+ param = 0;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (char >= Ascii.num0 && char <= Ascii.num9) {
|
|
|
+ hasParam = true;
|
|
|
+ param *= 10;
|
|
|
+ param += char - Ascii.num0;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (char > Ascii.NULL && char < Ascii.num0) {
|
|
|
+ // intermediates.add(char);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (char >= Ascii.atSign && char <= Ascii.tilde) {
|
|
|
+ if (hasParam) {
|
|
|
+ _csi.params.add(param);
|
|
|
+ }
|
|
|
+
|
|
|
+ _csi.finalByte = char;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ late final _csiHandlers = FastLookupTable<_CsiHandler>({
|
|
|
+ // 'a'.codeUnitAt(0): _csiHandleCursorHorizontalRelative,
|
|
|
+ 'b'.codeUnitAt(0): _csiHandleRepeatPreviousCharacter,
|
|
|
+ 'c'.codeUnitAt(0): _csiHandleSendDeviceAttributes,
|
|
|
+ 'd'.codeUnitAt(0): _csiHandleLinePositionAbsolute,
|
|
|
+ 'f'.codeUnitAt(0): _csiHandleCursorPosition,
|
|
|
+ 'g'.codeUnitAt(0): _csiHandelClearTabStop,
|
|
|
+ 'h'.codeUnitAt(0): _csiHandleMode,
|
|
|
+ 'l'.codeUnitAt(0): _csiHandleMode,
|
|
|
+ 'm'.codeUnitAt(0): _csiHandleSgr,
|
|
|
+ 'n'.codeUnitAt(0): _csiHandleDeviceStatusReport,
|
|
|
+ 'r'.codeUnitAt(0): _csiHandleSetMargins,
|
|
|
+ 't'.codeUnitAt(0): _csiWindowManipulation,
|
|
|
+ 'A'.codeUnitAt(0): _csiHandleCursorUp,
|
|
|
+ 'B'.codeUnitAt(0): _csiHandleCursorDown,
|
|
|
+ 'C'.codeUnitAt(0): _csiHandleCursorForward,
|
|
|
+ 'D'.codeUnitAt(0): _csiHandleCursorBackward,
|
|
|
+ 'E'.codeUnitAt(0): _csiHandleCursorNextLine,
|
|
|
+ 'F'.codeUnitAt(0): _csiHandleCursorPrecedingLine,
|
|
|
+ 'G'.codeUnitAt(0): _csiHandleCursorHorizontalAbsolute,
|
|
|
+ 'H'.codeUnitAt(0): _csiHandleCursorPosition,
|
|
|
+ 'J'.codeUnitAt(0): _csiHandleEraseDisplay,
|
|
|
+ 'K'.codeUnitAt(0): _csiHandleEraseLine,
|
|
|
+ 'L'.codeUnitAt(0): _csiHandleInsertLines,
|
|
|
+ 'M'.codeUnitAt(0): _csiHandleDeleteLines,
|
|
|
+ 'P'.codeUnitAt(0): _csiHandleDelete,
|
|
|
+ 'S'.codeUnitAt(0): _csiHandleScrollUp,
|
|
|
+ 'T'.codeUnitAt(0): _csiHandleScrollDown,
|
|
|
+ '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:
|
|
|
+ final mode = params[i + 1];
|
|
|
+ switch (mode) {
|
|
|
+ case 2:
|
|
|
+ final r = params[i + 2];
|
|
|
+ final g = params[i + 3];
|
|
|
+ final b = params[i + 4];
|
|
|
+ handler.setForegroundColorRgb(r, g, b);
|
|
|
+ i += 4;
|
|
|
+ break;
|
|
|
+ case 5:
|
|
|
+ final index = params[i + 2];
|
|
|
+ handler.setForegroundColor256(index);
|
|
|
+ i += 2;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ 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:
|
|
|
+ final mode = params[i + 1];
|
|
|
+ switch (mode) {
|
|
|
+ case 2:
|
|
|
+ final r = params[i + 2];
|
|
|
+ final g = params[i + 3];
|
|
|
+ final b = params[i + 4];
|
|
|
+ handler.setBackgroundColorRgb(r, g, b);
|
|
|
+ i += 4;
|
|
|
+ break;
|
|
|
+ case 5:
|
|
|
+ final index = params[i + 2];
|
|
|
+ handler.setBackgroundColor256(index);
|
|
|
+ i += 2;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// `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() {
|
|
|
+ // Not supported.
|
|
|
+ }
|
|
|
+
|
|
|
+ /// `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:
|
|
|
+ case 10061000:
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ bool _escHandleOSC() {
|
|
|
+ final consumed = _consumeOsc();
|
|
|
+ if (!consumed) return false;
|
|
|
+
|
|
|
+ if (_osc.length < 2) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ final ps = _osc[0];
|
|
|
+ final pt = _osc[1];
|
|
|
+
|
|
|
+ switch (ps) {
|
|
|
+ case '0':
|
|
|
+ handler.setTitle(pt);
|
|
|
+ handler.setIconName(pt);
|
|
|
+ break;
|
|
|
+ case '1':
|
|
|
+ handler.setIconName(pt);
|
|
|
+ break;
|
|
|
+ case '2':
|
|
|
+ handler.setTitle(pt);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ handler.unknownOSC(ps);
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 {
|
|
|
+ _Csi({
|
|
|
+ required this.params,
|
|
|
+ required this.finalByte,
|
|
|
+ // required this.intermediates,
|
|
|
+ });
|
|
|
+
|
|
|
+ int? prefix;
|
|
|
+
|
|
|
+ List<int> params;
|
|
|
+
|
|
|
+ int finalByte;
|
|
|
+ // final List<int> intermediates;
|
|
|
+
|
|
|
+ @override
|
|
|
+ String toString() {
|
|
|
+ return params.join(';') + String.fromCharCode(finalByte);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// Function that handles a sequence of characters that starts with an escape.
|
|
|
+/// Returns [true] if the sequence was processed, [false] if it was not.
|
|
|
+typedef _EscHandler = bool Function();
|
|
|
+
|
|
|
+typedef _SbcHandler = void Function();
|
|
|
+
|
|
|
+typedef _CsiHandler = void Function();
|