| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534 |
- import 'dart:math' show max, min;
- import 'package:xterm/core/buffer/position.dart';
- import 'package:xterm/core/buffer/range.dart';
- import 'package:xterm/core/cursor.dart';
- import 'package:xterm/core/buffer/line.dart';
- import 'package:xterm/core/reflow.dart';
- import 'package:xterm/core/state.dart';
- import 'package:xterm/core/charset.dart';
- import 'package:xterm/utils/circular_list.dart';
- import 'package:xterm/utils/unicode_v11.dart';
- class Buffer {
- final TerminalState terminal;
- final int maxLines;
- final bool isAltBuffer;
- Buffer(
- this.terminal, {
- required this.maxLines,
- required this.isAltBuffer,
- }) {
- for (int i = 0; i < terminal.viewHeight; i++) {
- lines.push(_newEmptyLine());
- }
- resetVerticalMargins();
- }
- int _cursorX = 0;
- int _cursorY = 0;
- late int _marginTop;
- late int _marginBottom;
- var _savedCursorX = 0;
- var _savedCursorY = 0;
- final _savedCursorStyle = CursorStyle();
- final charset = Charset();
- /// Width of the viewport in columns. Also the index of the last column.
- int get viewWidth => terminal.viewWidth;
- /// Height of the viewport in rows. Also the index of the last line.
- int get viewHeight => terminal.viewHeight;
- /// lines of the buffer. the length of [lines] should always be equal or
- /// greater than [viewHeight].
- late final lines = CircularList<BufferLine>(maxLines);
- /// Total number of lines in the buffer. Always equal or greater than
- /// [viewHeight].
- int get height => lines.length;
- /// Horizontal position of the cursor relative to the top-left cornor of the
- /// screen, starting from 0.
- int get cursorX => _cursorX.clamp(0, terminal.viewWidth - 1);
- /// Vertical position of the cursor relative to the top-left cornor of the
- /// screen, starting from 0.
- int get cursorY => _cursorY;
- /// Index of the first line in the scroll region.
- int get marginTop => _marginTop;
- /// Index of the last line in the scroll region.
- int get marginBottom => _marginBottom;
- /// The number of lines above the viewport.
- int get scrollBack => height - viewHeight;
- /// Vertical position of the cursor relative to the top of the buffer,
- /// starting from 0.
- int get absoluteCursorY => _cursorY + scrollBack;
- /// Absolute index of the first line in the scroll region.
- int get absoluteMarginTop => _marginTop + scrollBack;
- /// Absolute index of the last line in the scroll region.
- int get absoluteMarginBottom => _marginBottom + scrollBack;
- /// Writes data to the _terminal. Terminal sequences or special characters are
- /// not interpreted and directly added to the buffer.
- ///
- /// See also: [Terminal.write]
- void write(String text) {
- for (var char in text.runes) {
- writeChar(char);
- }
- }
- /// Writes a single character to the _terminal. Escape sequences or special
- /// characters are not interpreted and directly added to the buffer.
- ///
- /// See also: [Terminal.writeChar]
- void writeChar(int codePoint) {
- codePoint = charset.translate(codePoint);
- final cellWidth = unicodeV11.wcwidth(codePoint);
- if (_cursorX >= terminal.viewWidth) {
- index();
- setCursorX(0);
- if (terminal.autoWrapMode) {
- currentLine.isWrapped = true;
- }
- }
- final line = currentLine;
- line.setCell(_cursorX, codePoint, cellWidth, terminal.cursor);
- if (_cursorX < viewWidth) {
- _cursorX++;
- }
- if (cellWidth == 2) {
- writeChar(0);
- }
- }
- /// The line at the current cursor position.
- BufferLine get currentLine {
- return lines[absoluteCursorY];
- }
- void backspace() {
- if (_cursorX == 0 && currentLine.isWrapped) {
- currentLine.isWrapped = false;
- moveCursor(viewWidth - 1, -1);
- } else if (_cursorX == viewWidth) {
- moveCursor(-2, 0);
- } else {
- moveCursor(-1, 0);
- }
- }
- /// Erases the viewport from the cursor position to the end of the buffer,
- /// including the cursor position.
- void eraseDisplayFromCursor() {
- eraseLineFromCursor();
- for (var i = absoluteCursorY; i < height; i++) {
- final line = lines[i];
- line.isWrapped = false;
- line.eraseRange(0, viewWidth, terminal.cursor);
- }
- }
- /// Erases the viewport from the top-left corner to the cursor, including the
- /// cursor.
- void eraseDisplayToCursor() {
- eraseLineToCursor();
- for (var i = 0; i < _cursorY; i++) {
- final line = lines[i + scrollBack];
- line.isWrapped = false;
- line.eraseRange(0, viewWidth, terminal.cursor);
- }
- }
- /// Erases the whole viewport.
- void eraseDisplay() {
- for (var i = 0; i < viewHeight; i++) {
- final line = lines[i + scrollBack];
- line.isWrapped = false;
- line.eraseRange(0, viewWidth, terminal.cursor);
- }
- }
- /// Erases the line from the cursor to the end of the line, including the
- /// cursor position.
- void eraseLineFromCursor() {
- currentLine.isWrapped = false;
- currentLine.eraseRange(_cursorX, viewWidth, terminal.cursor);
- }
- /// Erases the line from the start of the line to the cursor, including the
- /// cursor.
- void eraseLineToCursor() {
- currentLine.isWrapped = false;
- currentLine.eraseRange(0, _cursorX, terminal.cursor);
- }
- /// Erases the line at the current cursor position.
- void eraseLine() {
- currentLine.isWrapped = false;
- currentLine.eraseRange(0, viewWidth, terminal.cursor);
- }
- /// Erases [count] cells starting at the cursor position.
- void eraseChars(int count) {
- final start = _cursorX;
- currentLine.eraseRange(start, start + count, terminal.cursor);
- }
- void scrollDown(int lines) {
- for (var i = absoluteMarginBottom; i >= absoluteMarginTop; i--) {
- if (i >= absoluteMarginTop + lines) {
- this.lines[i] = this.lines[i - lines];
- } else {
- this.lines[i] = _newEmptyLine();
- }
- }
- }
- void scrollUp(int lines) {
- for (var i = absoluteMarginTop; i <= absoluteMarginBottom; i++) {
- if (i <= absoluteMarginBottom - lines) {
- this.lines[i] = this.lines[i + lines];
- } else {
- this.lines[i] = _newEmptyLine();
- }
- }
- }
- /// https://vt100.net/docs/vt100-ug/chapter3.html#IND IND – Index
- ///
- /// ESC D
- ///
- /// [index] causes the active position to move downward one line without
- /// changing the column position. If the active position is at the bottom
- /// margin, a scroll up is performed.
- void index() {
- if (isInVerticalMargin) {
- if (_cursorY == _marginBottom) {
- if (marginTop == 0 && !isAltBuffer) {
- lines.insert(absoluteMarginBottom + 1, _newEmptyLine());
- } else {
- scrollUp(1);
- }
- } else {
- moveCursorY(1);
- }
- return;
- }
- // the cursor is not in the scrollable region
- if (_cursorY >= viewHeight - 1) {
- // we are at the bottom
- if (isAltBuffer) {
- scrollUp(1);
- } else {
- lines.push(_newEmptyLine());
- }
- } else {
- // there're still lines so we simply move cursor down.
- moveCursorY(1);
- }
- }
- void lineFeed() {
- index();
- if (terminal.lineFeedMode) {
- setCursorX(0);
- }
- }
- /// https://terminalguide.namepad.de/seq/a_esc_cm/
- void reverseIndex() {
- if (isInVerticalMargin) {
- if (_cursorY == _marginTop) {
- scrollDown(1);
- } else {
- moveCursorY(-1);
- }
- } else {
- moveCursorY(-1);
- }
- }
- void cursorGoForward() {
- _cursorX = min(_cursorX + 1, viewWidth);
- }
- void setCursorX(int cursorX) {
- _cursorX = cursorX.clamp(0, viewWidth - 1);
- }
- void setCursorY(int cursorY) {
- _cursorY = cursorY.clamp(0, viewHeight - 1);
- }
- void moveCursorX(int offset) {
- setCursorX(_cursorX + offset);
- }
- void moveCursorY(int offset) {
- setCursorY(_cursorY + offset);
- }
- void setCursor(int cursorX, int cursorY) {
- var maxCursorY = viewHeight - 1;
- if (terminal.originMode) {
- cursorY += _marginTop;
- maxCursorY = _marginBottom;
- }
- _cursorX = cursorX.clamp(0, viewWidth - 1);
- _cursorY = cursorY.clamp(0, maxCursorY);
- }
- void moveCursor(int offsetX, int offsetY) {
- final cursorX = _cursorX + offsetX;
- final cursorY = _cursorY + offsetY;
- setCursor(cursorX, cursorY);
- }
- /// Save cursor position, charmap and text attributes.
- void saveCursor() {
- _savedCursorX = _cursorX;
- _savedCursorY = _cursorY;
- _savedCursorStyle.foreground = terminal.cursor.foreground;
- _savedCursorStyle.background = terminal.cursor.background;
- _savedCursorStyle.attrs = terminal.cursor.attrs;
- charset.save();
- }
- /// Restore cursor position, charmap and text attributes.
- void restoreCursor() {
- _cursorX = _savedCursorX;
- _cursorY = _savedCursorY;
- terminal.cursor.foreground = _savedCursorStyle.foreground;
- terminal.cursor.background = _savedCursorStyle.background;
- terminal.cursor.attrs = _savedCursorStyle.attrs;
- charset.restore();
- }
- /// Sets the vertical scrolling margin to [top] and [bottom].
- /// Both values must be between 0 and [viewHeight] - 1.
- void setVerticalMargins(int top, int bottom) {
- _marginTop = top.clamp(0, viewHeight - 1);
- _marginBottom = bottom.clamp(0, viewHeight - 1);
- _marginTop = min(_marginTop, _marginBottom);
- _marginBottom = max(_marginTop, _marginBottom);
- }
- bool get isInVerticalMargin {
- return _cursorY >= _marginTop && _cursorY <= _marginBottom;
- }
- void resetVerticalMargins() {
- setVerticalMargins(0, viewHeight - 1);
- }
- void deleteChars(int count) {
- final start = _cursorX.clamp(0, viewWidth);
- count = min(count, viewWidth - start);
- currentLine.removeCells(start, count, terminal.cursor);
- }
- /// Remove all lines above the top of the viewport.
- void clearScrollback() {
- if (height <= viewHeight) {
- return;
- }
- lines.trimStart(scrollBack);
- }
- /// Clears the viewport and scrollback buffer. Then fill with empty lines.
- void clear() {
- lines.clear();
- for (int i = 0; i < viewHeight; i++) {
- lines.push(_newEmptyLine());
- }
- }
- void insertBlankChars(int count) {
- currentLine.insertCells(_cursorX, count, terminal.cursor);
- }
- void insertLines(int count) {
- if (!isInVerticalMargin) {
- return;
- }
- setCursorX(0);
- for (var i = 0; i < count; i++) {
- final shiftStart = absoluteCursorY;
- final shiftCount = absoluteMarginBottom - absoluteCursorY;
- lines.shiftElements(shiftStart, shiftCount, 1);
- lines[absoluteCursorY] = _newEmptyLine();
- }
- }
- void deleteLines(int count) {
- if (!isInVerticalMargin) {
- return;
- }
- setCursorX(0);
- for (var i = 0; i < count; i++) {
- lines.insert(absoluteMarginBottom, _newEmptyLine());
- lines.remove(absoluteCursorY);
- }
- }
- void resize(int oldWidth, int oldHeight, int newWidth, int newHeight) {
- if (newWidth > oldWidth) {
- lines.forEach((item) => item.resize(newWidth));
- }
- if (newHeight > oldHeight) {
- // Grow larger
- for (var i = 0; i < newHeight - oldHeight; i++) {
- if (newHeight > lines.length) {
- lines.push(_newEmptyLine());
- } else {
- _cursorY++;
- }
- }
- } else {
- // Shrink smallerclear
- for (var i = 0; i < oldHeight - newHeight; i++) {
- if (_cursorY > newHeight - 1) {
- _cursorY--;
- } else {
- lines.pop();
- }
- }
- }
- // Ensure cursor is within the screen.
- _cursorX = _cursorX.clamp(0, newWidth - 1);
- _cursorY = _cursorY.clamp(0, newHeight - 1);
- if (!isAltBuffer && newWidth != oldWidth) {
- final reflowResult = reflow(lines, oldWidth, newWidth);
- while (reflowResult.length < newHeight) {
- reflowResult.add(_newEmptyLine());
- }
- lines.replaceWith(reflowResult);
- }
- }
- BufferLine _newEmptyLine() {
- final line = BufferLine(viewWidth);
- return line;
- }
- static final _kWordSeparators = <int>{
- 0,
- r' '.codeUnitAt(0),
- r'.'.codeUnitAt(0),
- r':'.codeUnitAt(0),
- r'-'.codeUnitAt(0),
- r'\'.codeUnitAt(0),
- r'"'.codeUnitAt(0),
- r'*'.codeUnitAt(0),
- r'+'.codeUnitAt(0),
- r'/'.codeUnitAt(0),
- r'\'.codeUnitAt(0),
- };
- BufferRange? getWordBoundary(BufferPosition position) {
- if (position.y >= lines.length) {
- return null;
- }
- var line = lines[position.y];
- var start = position.x;
- var end = position.x;
- do {
- if (start == 0) {
- break;
- }
- final char = line.getCodePoint(start - 1);
- if (_kWordSeparators.contains(char)) {
- break;
- }
- start--;
- } while (true);
- do {
- if (end >= viewWidth) {
- break;
- }
- final char = line.getCodePoint(end);
- if (_kWordSeparators.contains(char)) {
- break;
- }
- end++;
- } while (true);
- return BufferRange(
- BufferPosition(start, position.y),
- BufferPosition(end, position.y),
- );
- }
- String getText(BufferRange range) {
- final builder = StringBuffer();
- for (var i = range.begin.y; i <= range.end.y; i++) {
- if (i < 0 || i >= lines.length) {
- break;
- }
- final line = lines[i];
- final start = i == range.begin.y ? range.begin.x : 0;
- final end = i == range.end.y ? range.end.x : line.length;
- if (i != range.begin.y && line.isWrapped) {
- builder.write('\n');
- }
- builder.write(line.getText(start, end));
- }
- return builder.toString();
- }
- @override
- String toString() {
- final builder = StringBuffer();
- final lineNumberLength = lines.length.toString().length;
- for (var i = 0; i < lines.length; i++) {
- builder.writeln('${i.toString().padLeft(lineNumberLength)}: ${lines[i]}');
- }
- return builder.toString();
- }
- }
|