| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906 |
- import 'dart:math' show max;
- import 'package:xterm/src/base/observable.dart';
- import 'package:xterm/src/core/buffer/buffer.dart';
- import 'package:xterm/src/core/buffer/cell_offset.dart';
- import 'package:xterm/src/core/buffer/line.dart';
- import 'package:xterm/src/core/cursor.dart';
- import 'package:xterm/src/core/escape/emitter.dart';
- import 'package:xterm/src/core/escape/handler.dart';
- import 'package:xterm/src/core/escape/parser.dart';
- import 'package:xterm/src/core/input/handler.dart';
- import 'package:xterm/src/core/input/keys.dart';
- import 'package:xterm/src/core/mouse/button.dart';
- import 'package:xterm/src/core/mouse/button_state.dart';
- import 'package:xterm/src/core/mouse/handler.dart';
- import 'package:xterm/src/core/mouse/mode.dart';
- import 'package:xterm/src/core/platform.dart';
- import 'package:xterm/src/core/state.dart';
- import 'package:xterm/src/core/tabs.dart';
- import 'package:xterm/src/utils/ascii.dart';
- import 'package:xterm/src/utils/circular_buffer.dart';
- /// [Terminal] is an interface to interact with command line applications. It
- /// translates escape sequences from the application into updates to the
- /// [buffer] and events such as [onTitleChange] or [onBell], as well as
- /// translating user input into escape sequences that the application can
- /// understand.
- class Terminal with Observable implements TerminalState, EscapeHandler {
- /// The number of lines that the scrollback buffer can hold. If the buffer
- /// exceeds this size, the lines at the top of the buffer will be removed.
- final int maxLines;
- /// Function that is called when the program requests the terminal to ring
- /// the bell. If not set, the terminal will do nothing.
- void Function()? onBell;
- /// Function that is called when the program requests the terminal to change
- /// the title of the window to [title].
- void Function(String title)? onTitleChange;
- /// Function that is called when the program requests the terminal to change
- /// the icon of the window. [icon] is the name of the icon.
- void Function(String icon)? onIconChange;
- /// Function that is called when the terminal emits data to the underlying
- /// program. This is typically caused by user inputs from [textInput],
- /// [keyInput], [mouseInput], or [paste].
- void Function(String data)? onOutput;
- /// Function that is called when the dimensions of the terminal change.
- void Function(int width, int height, int pixelWidth, int pixelHeight)?
- onResize;
- /// The [TerminalInputHandler] used by this terminal. [defaultInputHandler] is
- /// used when not specified. User of this class can provide their own
- /// implementation of [TerminalInputHandler] or extend [defaultInputHandler]
- /// with [CascadeInputHandler].
- TerminalInputHandler? inputHandler;
- TerminalMouseHandler? mouseHandler;
- /// The callback that is called when the terminal receives a unrecognized
- /// escape sequence.
- void Function(String code, List<String> args)? onPrivateOSC;
- /// Flag to toggle os specific behaviors.
- final TerminalTargetPlatform platform;
- /// Characters that break selection when double clicking. If not set, the
- /// [Buffer.defaultWordSeparators] will be used.
- final Set<int>? wordSeparators;
- Terminal({
- this.maxLines = 1000,
- this.onBell,
- this.onTitleChange,
- this.onIconChange,
- this.onOutput,
- this.onResize,
- this.platform = TerminalTargetPlatform.unknown,
- this.inputHandler = defaultInputHandler,
- this.mouseHandler = defaultMouseHandler,
- this.onPrivateOSC,
- this.reflowEnabled = true,
- this.wordSeparators,
- });
- late final _parser = EscapeParser(this);
- final _emitter = const EscapeEmitter();
- late var _buffer = _mainBuffer;
- late final _mainBuffer = Buffer(
- this,
- maxLines: maxLines,
- isAltBuffer: false,
- wordSeparators: wordSeparators,
- );
- late final _altBuffer = Buffer(
- this,
- maxLines: maxLines,
- isAltBuffer: true,
- wordSeparators: wordSeparators,
- );
- final _tabStops = TabStops();
- /// The last character written to the buffer. Used to implement some escape
- /// sequences that repeat the last character.
- var _precedingCodepoint = 0;
- /* TerminalState */
- int _viewWidth = 80;
- int _viewHeight = 24;
- final _cursorStyle = CursorStyle();
- bool _insertMode = false;
- bool _lineFeedMode = false;
- bool _cursorKeysMode = false;
- bool _reverseDisplayMode = false;
- bool _originMode = false;
- bool _autoWrapMode = true;
- MouseMode _mouseMode = MouseMode.none;
- MouseReportMode _mouseReportMode = MouseReportMode.normal;
- bool _cursorBlinkMode = false;
- bool _cursorVisibleMode = true;
- bool _appKeypadMode = false;
- bool _reportFocusMode = false;
- bool _altBufferMouseScrollMode = false;
- bool _bracketedPasteMode = false;
- /* State getters */
- /// Number of cells in a terminal row.
- @override
- int get viewWidth => _viewWidth;
- /// Number of rows in this terminal.
- @override
- int get viewHeight => _viewHeight;
- @override
- CursorStyle get cursor => _cursorStyle;
- @override
- bool get insertMode => _insertMode;
- @override
- bool get lineFeedMode => _lineFeedMode;
- @override
- bool get cursorKeysMode => _cursorKeysMode;
- @override
- bool get reverseDisplayMode => _reverseDisplayMode;
- @override
- bool get originMode => _originMode;
- @override
- bool get autoWrapMode => _autoWrapMode;
- @override
- MouseMode get mouseMode => _mouseMode;
- @override
- MouseReportMode get mouseReportMode => _mouseReportMode;
- @override
- bool get cursorBlinkMode => _cursorBlinkMode;
- @override
- bool get cursorVisibleMode => _cursorVisibleMode;
- @override
- bool get appKeypadMode => _appKeypadMode;
- @override
- bool get reportFocusMode => _reportFocusMode;
- @override
- bool get altBufferMouseScrollMode => _altBufferMouseScrollMode;
- @override
- bool get bracketedPasteMode => _bracketedPasteMode;
- /// Current active buffer of the terminal. This is initially [mainBuffer] and
- /// can be switched back and forth from [altBuffer] to [mainBuffer] when
- /// the underlying program requests it.
- Buffer get buffer => _buffer;
- Buffer get mainBuffer => _mainBuffer;
- Buffer get altBuffer => _altBuffer;
- bool get isUsingAltBuffer => _buffer == _altBuffer;
- /// Lines of the active buffer.
- IndexAwareCircularBuffer<BufferLine> get lines => _buffer.lines;
- /// Whether the terminal performs reflow when the viewport size changes or
- /// simply truncates lines. true by default.
- @override
- bool reflowEnabled;
- /// Writes the data from the underlying program to the terminal. Calling this
- /// updates the states of the terminal and emits events such as [onBell] or
- /// [onTitleChange] when the escape sequences in [data] request it.
- void write(String data) {
- _parser.write(data);
- notifyListeners();
- }
- /// Sends a key event to the underlying program.
- ///
- /// See also:
- /// - [charInput]
- /// - [textInput]
- /// - [paste]
- bool keyInput(
- TerminalKey key, {
- bool shift = false,
- bool alt = false,
- bool ctrl = false,
- }) {
- final output = inputHandler?.call(
- TerminalKeyboardEvent(
- key: key,
- shift: shift,
- alt: alt,
- ctrl: ctrl,
- state: this,
- altBuffer: isUsingAltBuffer,
- platform: platform,
- ),
- );
- if (output != null) {
- onOutput?.call(output);
- return true;
- }
- return false;
- }
- /// Similary to [keyInput], but takes a character as input instead of a
- /// [TerminalKey].
- ///
- /// See also:
- /// - [keyInput]
- /// - [textInput]
- /// - [paste]
- bool charInput(
- int charCode, {
- bool alt = false,
- bool ctrl = false,
- }) {
- if (ctrl) {
- // a(97) ~ z(122)
- if (charCode >= Ascii.a && charCode <= Ascii.z) {
- final output = charCode - Ascii.a + 1;
- onOutput?.call(String.fromCharCode(output));
- return true;
- }
- // [(91) ~ _(95)
- if (charCode >= Ascii.openBracket && charCode <= Ascii.underscore) {
- final output = charCode - Ascii.openBracket + 27;
- onOutput?.call(String.fromCharCode(output));
- return true;
- }
- }
- if (alt && platform != TerminalTargetPlatform.macos) {
- if (charCode >= Ascii.a && charCode <= Ascii.z) {
- final code = charCode - Ascii.a + 65;
- final input = [0x1b, code];
- onOutput?.call(String.fromCharCodes(input));
- return true;
- }
- }
- return false;
- }
- /// Sends regular text input to the underlying program.
- ///
- /// See also:
- /// - [keyInput]
- /// - [charInput]
- /// - [paste]
- void textInput(String text) {
- onOutput?.call(text);
- }
- /// Similar to [textInput], except that when the program tells the terminal
- /// that it supports [bracketedPasteMode], the text is wrapped in escape
- /// sequences to indicate that it is a paste operation. Prefer this method
- /// over [textInput] when pasting text.
- ///
- /// See also:
- /// - [textInput]
- void paste(String text) {
- if (_bracketedPasteMode) {
- onOutput?.call(_emitter.bracketedPaste(text));
- } else {
- textInput(text);
- }
- }
- // Handle a mouse event and return true if it was handled.
- bool mouseInput(
- TerminalMouseButton button,
- TerminalMouseButtonState buttonState,
- CellOffset position,
- ) {
- final output = mouseHandler?.call(TerminalMouseEvent(
- button: button,
- buttonState: buttonState,
- position: position,
- state: this,
- platform: platform,
- ));
- if (output != null) {
- onOutput?.call(output);
- return true;
- }
- return false;
- }
- /// Resize the terminal screen. [newWidth] and [newHeight] should be greater
- /// than 0. Text reflow is currently not implemented and will be avaliable in
- /// the future.
- @override
- void resize(
- int newWidth,
- int newHeight, [
- int? pixelWidth,
- int? pixelHeight,
- ]) {
- newWidth = max(newWidth, 1);
- newHeight = max(newHeight, 1);
- onResize?.call(newWidth, newHeight, pixelWidth ?? 0, pixelHeight ?? 0);
- //we need to resize both buffers so that they are ready when we switch between them
- _altBuffer.resize(_viewWidth, _viewHeight, newWidth, newHeight);
- _mainBuffer.resize(_viewWidth, _viewHeight, newWidth, newHeight);
- _viewWidth = newWidth;
- _viewHeight = newHeight;
- if (buffer == _altBuffer) {
- buffer.clearScrollback();
- }
- _altBuffer.resetVerticalMargins();
- _mainBuffer.resetVerticalMargins();
- }
- @override
- String toString() {
- return 'Terminal(#$hashCode, $_viewWidth x $_viewHeight, ${_buffer.height} lines)';
- }
- /* Handlers */
- @override
- void writeChar(int char) {
- _precedingCodepoint = char;
- _buffer.writeChar(char);
- }
- /* SBC */
- @override
- void bell() {
- onBell?.call();
- }
- @override
- void backspaceReturn() {
- _buffer.moveCursorX(-1);
- }
- @override
- void tab() {
- final nextStop = _tabStops.find(_buffer.cursorX + 1, _viewWidth);
- if (nextStop != null) {
- _buffer.setCursorX(nextStop);
- } else {
- _buffer.setCursorX(_viewWidth);
- _buffer.cursorGoForward(); // Enter pending-wrap state
- }
- }
- @override
- void lineFeed() {
- _buffer.lineFeed();
- }
- @override
- void carriageReturn() {
- _buffer.setCursorX(0);
- }
- @override
- void shiftOut() {
- _buffer.charset.use(1);
- }
- @override
- void shiftIn() {
- _buffer.charset.use(0);
- }
- @override
- void unknownSBC(int char) {
- // no-op
- }
- /* ANSI sequence */
- @override
- void saveCursor() {
- _buffer.saveCursor();
- }
- @override
- void restoreCursor() {
- _buffer.restoreCursor();
- }
- @override
- void index() {
- _buffer.index();
- }
- @override
- void nextLine() {
- _buffer.index();
- _buffer.setCursorX(0);
- }
- @override
- void setTapStop() {
- _tabStops.isSetAt(_buffer.cursorX);
- }
- @override
- void reverseIndex() {
- _buffer.reverseIndex();
- }
- @override
- void designateCharset(int charset, int name) {
- _buffer.charset.designate(charset, name);
- }
- @override
- void unkownEscape(int char) {
- // no-op
- }
- /* CSI */
- @override
- void repeatPreviousCharacter(int count) {
- if (_precedingCodepoint == 0) {
- return;
- }
- for (var i = 0; i < count; i++) {
- _buffer.writeChar(_precedingCodepoint);
- }
- }
- @override
- void setCursor(int x, int y) {
- _buffer.setCursor(x, y);
- }
- @override
- void setCursorX(int x) {
- _buffer.setCursorX(x);
- }
- @override
- void setCursorY(int y) {
- _buffer.setCursorY(y);
- }
- @override
- void moveCursorX(int offset) {
- _buffer.moveCursorX(offset);
- }
- @override
- void moveCursorY(int n) {
- _buffer.moveCursorY(n);
- }
- @override
- void clearTabStopUnderCursor() {
- _tabStops.clearAt(_buffer.cursorX);
- }
- @override
- void clearAllTabStops() {
- _tabStops.clearAll();
- }
- @override
- void sendPrimaryDeviceAttributes() {
- onOutput?.call(_emitter.primaryDeviceAttributes());
- }
- @override
- void sendSecondaryDeviceAttributes() {
- onOutput?.call(_emitter.secondaryDeviceAttributes());
- }
- @override
- void sendTertiaryDeviceAttributes() {
- onOutput?.call(_emitter.tertiaryDeviceAttributes());
- }
- @override
- void sendOperatingStatus() {
- onOutput?.call(_emitter.operatingStatus());
- }
- @override
- void sendCursorPosition() {
- onOutput?.call(_emitter.cursorPosition(_buffer.cursorX, _buffer.cursorY));
- }
- @override
- void setMargins(int top, [int? bottom]) {
- _buffer.setVerticalMargins(top, bottom ?? viewHeight - 1);
- }
- @override
- void cursorNextLine(int amount) {
- _buffer.moveCursorY(amount);
- _buffer.setCursorX(0);
- }
- @override
- void cursorPrecedingLine(int amount) {
- _buffer.moveCursorY(-amount);
- _buffer.setCursorX(0);
- }
- @override
- void eraseDisplayBelow() {
- _buffer.eraseDisplayFromCursor();
- }
- @override
- void eraseDisplayAbove() {
- _buffer.eraseDisplayToCursor();
- }
- @override
- void eraseDisplay() {
- _buffer.eraseDisplay();
- }
- @override
- void eraseScrollbackOnly() {
- _buffer.clearScrollback();
- }
- @override
- void eraseLineRight() {
- _buffer.eraseLineFromCursor();
- }
- @override
- void eraseLineLeft() {
- _buffer.eraseLineToCursor();
- }
- @override
- void eraseLine() {
- _buffer.eraseLine();
- }
- @override
- void insertLines(int amount) {
- _buffer.insertLines(amount);
- }
- @override
- void deleteLines(int amount) {
- _buffer.deleteLines(amount);
- }
- @override
- void deleteChars(int amount) {
- _buffer.deleteChars(amount);
- }
- @override
- void scrollUp(int amount) {
- _buffer.scrollUp(amount);
- }
- @override
- void scrollDown(int amount) {
- _buffer.scrollDown(amount);
- }
- @override
- void eraseChars(int amount) {
- _buffer.eraseChars(amount);
- }
- @override
- void insertBlankChars(int amount) {
- _buffer.insertBlankChars(amount);
- }
- @override
- void sendSize() {
- onOutput?.call(_emitter.size(viewHeight, viewWidth));
- }
- @override
- void unknownCSI(int finalByte) {
- // no-op
- }
- /* Modes */
- @override
- void setInsertMode(bool enabled) {
- _insertMode = enabled;
- }
- @override
- void setLineFeedMode(bool enabled) {
- _lineFeedMode = enabled;
- }
- @override
- void setUnknownMode(int mode, bool enabled) {
- // no-op
- }
- /* DEC Private modes */
- @override
- void setCursorKeysMode(bool enabled) {
- _cursorKeysMode = enabled;
- }
- @override
- void setReverseDisplayMode(bool enabled) {
- _reverseDisplayMode = enabled;
- }
- @override
- void setOriginMode(bool enabled) {
- _originMode = enabled;
- }
- @override
- void setColumnMode(bool enabled) {
- // no-op
- }
- @override
- void setAutoWrapMode(bool enabled) {
- _autoWrapMode = enabled;
- }
- @override
- void setMouseMode(MouseMode mode) {
- _mouseMode = mode;
- }
- @override
- void setCursorBlinkMode(bool enabled) {
- _cursorBlinkMode = enabled;
- }
- @override
- void setCursorVisibleMode(bool enabled) {
- _cursorVisibleMode = enabled;
- }
- @override
- void useAltBuffer() {
- _buffer = _altBuffer;
- }
- @override
- void useMainBuffer() {
- _buffer = _mainBuffer;
- }
- @override
- void clearAltBuffer() {
- _altBuffer.clear();
- }
- @override
- void setAppKeypadMode(bool enabled) {
- _appKeypadMode = enabled;
- }
- @override
- void setReportFocusMode(bool enabled) {
- _reportFocusMode = enabled;
- }
- @override
- void setMouseReportMode(MouseReportMode mode) {
- _mouseReportMode = mode;
- }
- @override
- void setAltBufferMouseScrollMode(bool enabled) {
- _altBufferMouseScrollMode = enabled;
- }
- @override
- void setBracketedPasteMode(bool enabled) {
- _bracketedPasteMode = enabled;
- }
- @override
- void setUnknownDecMode(int mode, bool enabled) {
- // no-op
- }
- /* Select Graphic Rendition (SGR) */
- @override
- void resetCursorStyle() {
- _cursorStyle.reset();
- }
- @override
- void setCursorBold() {
- _cursorStyle.setBold();
- }
- @override
- void setCursorFaint() {
- _cursorStyle.setFaint();
- }
- @override
- void setCursorItalic() {
- _cursorStyle.setItalic();
- }
- @override
- void setCursorUnderline() {
- _cursorStyle.setUnderline();
- }
- @override
- void setCursorBlink() {
- _cursorStyle.setBlink();
- }
- @override
- void setCursorInverse() {
- _cursorStyle.setInverse();
- }
- @override
- void setCursorInvisible() {
- _cursorStyle.setInvisible();
- }
- @override
- void setCursorStrikethrough() {
- _cursorStyle.setStrikethrough();
- }
- @override
- void unsetCursorBold() {
- _cursorStyle.unsetBold();
- }
- @override
- void unsetCursorFaint() {
- _cursorStyle.unsetFaint();
- }
- @override
- void unsetCursorItalic() {
- _cursorStyle.unsetItalic();
- }
- @override
- void unsetCursorUnderline() {
- _cursorStyle.unsetUnderline();
- }
- @override
- void unsetCursorBlink() {
- _cursorStyle.unsetBlink();
- }
- @override
- void unsetCursorInverse() {
- _cursorStyle.unsetInverse();
- }
- @override
- void unsetCursorInvisible() {
- _cursorStyle.unsetInvisible();
- }
- @override
- void unsetCursorStrikethrough() {
- _cursorStyle.unsetStrikethrough();
- }
- @override
- void setForegroundColor16(int color) {
- _cursorStyle.setForegroundColor16(color);
- }
- @override
- void setForegroundColor256(int index) {
- _cursorStyle.setForegroundColor256(index);
- }
- @override
- void setForegroundColorRgb(int r, int g, int b) {
- _cursorStyle.setForegroundColorRgb(r, g, b);
- }
- @override
- void resetForeground() {
- _cursorStyle.resetForegroundColor();
- }
- @override
- void setBackgroundColor16(int color) {
- _cursorStyle.setBackgroundColor16(color);
- }
- @override
- void setBackgroundColor256(int index) {
- _cursorStyle.setBackgroundColor256(index);
- }
- @override
- void setBackgroundColorRgb(int r, int g, int b) {
- _cursorStyle.setBackgroundColorRgb(r, g, b);
- }
- @override
- void resetBackground() {
- _cursorStyle.resetBackgroundColor();
- }
- @override
- void unsupportedStyle(int param) {
- // no-op
- }
- /* OSC */
- @override
- void setTitle(String name) {
- onTitleChange?.call(name);
- }
- @override
- void setIconName(String name) {
- onIconChange?.call(name);
- }
- @override
- void unknownOSC(String ps, List<String> pt) {
- onPrivateOSC?.call(ps, pt);
- }
- }
|