import 'package:xterm/src/core/escape/handler.dart'; import 'package:xterm/src/core/escape/parser.dart'; import 'package:xterm/src/core/mouse/mode.dart'; import 'package:xterm/src/utils/observable.dart'; class TerminalCommand { TerminalCommand( this.start, this.end, this.chars, this.escapedChars, this.explanation, this.error, ); final int start; final int end; final String chars; final String escapedChars; final List explanation; final bool error; } class TerminalDebugger with Observable { late final _parser = EscapeParser(_handler); late final _handler = _TerminalDebuggerHandler(recordCommand); final recorded = []; final commands = []; void write(String chunk) { recorded.addAll(chunk.runes); _parser.write(chunk); notifyListeners(); } void recordCommand(String explanation, {bool error = false}) { final start = _parser.tokenBegin; final end = _parser.tokenEnd; if (commands.isNotEmpty && commands.last.end == end) { commands.last.explanation.add(explanation); } else { final charCodes = recorded.sublist(start, end); final chars = String.fromCharCodes(charCodes); final escapedChars = _escape(chars); commands.add( TerminalCommand(start, end, chars, escapedChars, [explanation], error), ); } } String getRecord(TerminalCommand command) { final charCodes = recorded.sublist(0, command.end); return String.fromCharCodes(charCodes); } static String _escape(String chars) { final escaped = StringBuffer(); for (final char in chars.runes) { if (char == 0x1b) { escaped.write('ESC'); } else if (char < 32) { escaped.write('^0x${char.toRadixString(16)}'); } else if (char == 127) { escaped.write('^?'); } else { escaped.writeCharCode(char); } } return escaped.toString(); } } class _TerminalDebuggerHandler implements EscapeHandler { _TerminalDebuggerHandler(this.onCommand); final void Function(String explanation, {bool error}) onCommand; @override void writeChar(int char) { onCommand('writeChar(${String.fromCharCode(char)})'); } /* SBC */ @override void bell() { onCommand('bell'); } @override void backspaceReturn() { onCommand('backspaceReturn'); } @override void tab() { onCommand('tab'); } @override void lineFeed() { onCommand('lineFeed'); } @override void carriageReturn() { onCommand('carriageReturn'); } @override void shiftOut() { onCommand('shiftOut'); } @override void shiftIn() { onCommand('shiftIn'); } @override void unknownSBC(int char) { onCommand('unkownSBC(${String.fromCharCode(char)})', error: true); } /* ANSI sequence */ @override void saveCursor() { onCommand('saveCursor'); } @override void restoreCursor() { onCommand('restoreCursor'); } @override void index() { onCommand('index'); } @override void nextLine() { onCommand('nextLine'); } @override void setTapStop() { onCommand('setTapStop'); } @override void reverseIndex() { onCommand('reverseIndex'); } @override void designateCharset(int charset) { onCommand('designateCharset($charset)'); } @override void unkownEscape(int char) { onCommand('unkownEscape(${String.fromCharCode(char)})', error: true); } /* CSI */ @override void repeatPreviousCharacter(int count) { onCommand('repeatPreviousCharacter($count)'); } @override void unknownCSI(int finalByte) { onCommand('unkownCSI(${String.fromCharCode(finalByte)})', error: true); } @override void setCursor(int x, int y) { onCommand('setCursor($x, $y)'); } @override void setCursorX(int x) { onCommand('setCursorX($x)'); } @override void setCursorY(int y) { onCommand('setCursorY($y)'); } @override void sendPrimaryDeviceAttributes() { onCommand('sendPrimaryDeviceAttributes'); } @override void clearTabStopUnderCursor() { onCommand('clearTabStopUnderCursor'); } @override void clearAllTabStops() { onCommand('clearAllTabStops'); } @override void moveCursorX(int offset) { onCommand('moveCursorX($offset)'); } @override void moveCursorY(int n) { onCommand('moveCursorY($n)'); } @override void sendSecondaryDeviceAttributes() { onCommand('sendSecondaryDeviceAttributes'); } @override void sendTertiaryDeviceAttributes() { onCommand('sendTertiaryDeviceAttributes'); } @override void sendOperatingStatus() { onCommand('sendOperatingStatus'); } @override void sendCursorPosition() { onCommand('sendCursorPosition'); } @override void setMargins(int i, [int? bottom]) { onCommand('setMargins($i, $bottom)'); } @override void cursorNextLine(int amount) { onCommand('cursorNextLine($amount)'); } @override void cursorPrecedingLine(int amount) { onCommand('cursorPrecedingLine($amount)'); } @override void eraseDisplayBelow() { onCommand('eraseDisplayBelow'); } @override void eraseDisplayAbove() { onCommand('eraseDisplayAbove'); } @override void eraseDisplay() { onCommand('eraseDisplay'); } @override void eraseScrollbackOnly() { onCommand('eraseScrollbackOnly'); } @override void eraseLineRight() { onCommand('eraseLineRight'); } @override void eraseLineLeft() { onCommand('eraseLineLeft'); } @override void eraseLine() { onCommand('eraseLine'); } @override void insertLines(int amount) { onCommand('insertLines($amount)'); } @override void deleteLines(int amount) { onCommand('deleteLines($amount)'); } @override void deleteChars(int amount) { onCommand('deleteChars($amount)'); } @override void scrollUp(int amount) { onCommand('scrollUp($amount)'); } @override void scrollDown(int amount) { onCommand('scrollDown($amount)'); } @override void eraseChars(int amount) { onCommand('eraseChars($amount)'); } @override void insertBlankChars(int amount) { onCommand('insertBlankChars($amount)'); } @override void resize(int cols, int rows) { onCommand('resize($cols, $rows)'); } @override void sendSize() { onCommand('sendSize'); } /* Modes */ @override void setInsertMode(bool enabled) { onCommand('setInsertMode($enabled)'); } @override void setLineFeedMode(bool enabled) { onCommand('setLineFeedMode($enabled)'); } @override void setUnknownMode(int mode, bool enabled) { onCommand('setUnknownMode($mode, $enabled)', error: true); } /* DEC Private modes */ @override void setCursorKeysMode(bool enabled) { onCommand('setCursorKeysMode($enabled)'); } @override void setReverseDisplayMode(bool enabled) { onCommand('setReverseDisplayMode($enabled)'); } @override void setOriginMode(bool enabled) { onCommand('setOriginMode($enabled)'); } @override void setColumnMode(bool enabled) { onCommand('setColumnMode($enabled)'); } @override void setAutoWrapMode(bool enabled) { onCommand('setAutoWrapMode($enabled)'); } @override void setMouseMode(MouseMode mode) { onCommand('setMouseMode($mode)'); } @override void setCursorBlinkMode(bool enabled) { onCommand('setCursorBlinkMode($enabled)'); } @override void setCursorVisibleMode(bool enabled) { onCommand('setCursorVisibleMode($enabled)'); } @override void useAltBuffer() { onCommand('useAltBuffer'); } @override void useMainBuffer() { onCommand('useMainBuffer'); } @override void clearAltBuffer() { onCommand('clearAltBuffer'); } @override void setAppKeypadMode(bool enabled) { onCommand('setAppKeypadMode($enabled)'); } @override void setReportFocusMode(bool enabled) { onCommand('setReportFocusMode($enabled)'); } @override void setMouseReportMode(MouseReportMode mode) { onCommand('setMouseReportMode($mode)'); } @override void setAltBufferMouseScrollMode(bool enabled) { onCommand('setAltBufferMouseScrollMode($enabled)'); } @override void setBracketedPasteMode(bool enabled) { onCommand('setBracketedPasteMode($enabled)'); } @override void setUnknownDecMode(int mode, bool enabled) { onCommand('setUnknownDecMode($mode, $enabled)', error: true); } /* Select Graphic Rendition (SGR) */ @override void resetCursorStyle() { onCommand('resetCursorStyle'); } @override void setCursorBold() { onCommand('setCursorBold'); } @override void setCursorFaint() { onCommand('setCursorFaint'); } @override void setCursorItalic() { onCommand('setCursorItalic'); } @override void setCursorUnderline() { onCommand('setCursorUnderline'); } @override void setCursorBlink() { onCommand('setCursorBlink'); } @override void setCursorInverse() { onCommand('setCursorInverse'); } @override void setCursorInvisible() { onCommand('setCursorInvisible'); } @override void setCursorStrikethrough() { onCommand('setCursorStrikethrough'); } @override void unsetCursorBold() { onCommand('unsetCursorBold'); } @override void unsetCursorFaint() { onCommand('unsetCursorFaint'); } @override void unsetCursorItalic() { onCommand('unsetCursorItalic'); } @override void unsetCursorUnderline() { onCommand('unsetCursorUnderline'); } @override void unsetCursorBlink() { onCommand('unsetCursorBlink'); } @override void unsetCursorInverse() { onCommand('unsetCursorInverse'); } @override void unsetCursorInvisible() { onCommand('unsetCursorInvisible'); } @override void unsetCursorStrikethrough() { onCommand('unsetCursorStrikethrough'); } @override void setForegroundColor16(int color) { onCommand('setForegroundColor16($color)'); } @override void setForegroundColor256(int index) { onCommand('setForegroundColor256($index)'); } @override void setForegroundColorRgb(int r, int g, int b) { onCommand('setForegroundColorRgb($r, $g, $b)'); } @override void resetForeground() { onCommand('resetForeground'); } @override void setBackgroundColor16(int color) { onCommand('setBackgroundColor16($color)'); } @override void setBackgroundColor256(int index) { onCommand('setBackgroundColor256($index)'); } @override void setBackgroundColorRgb(int r, int g, int b) { onCommand('setBackgroundColorRgb($r, $g, $b)'); } @override void resetBackground() { onCommand('resetBackground'); } @override void unsupportedStyle(int param) { onCommand('unsupportedStyle($param)', error: true); } /* OSC */ @override void setTitle(String name) { onCommand('setTitle($name)'); } @override void setIconName(String name) { onCommand('setIconName($name)'); } @override void unknownOSC(String ps) { onCommand('unknownOSC($ps)', error: true); } }