import 'dart:math' show min; import 'dart:typed_data'; import 'package:xterm/core/cell.dart'; import 'package:xterm/core/cursor.dart'; import 'package:xterm/utils/unicode_v11.dart'; const _cellSize = 4; const _cellForeground = 0; const _cellBackground = 1; const _cellAttributes = 2; const _cellContent = 3; class BufferLine { BufferLine( this._length, { this.isWrapped = false, }) : _data = Uint32List(_calcCapacity(_length) * _cellSize); int _length; Uint32List _data; Uint32List get data => _data; var isWrapped = false; int get length => _length; int getForeground(int index) { return _data[index * _cellSize + _cellForeground]; } int getBackground(int index) { return _data[index * _cellSize + _cellBackground]; } int getAttributes(int index) { return _data[index * _cellSize + _cellAttributes]; } int getContent(int index) { return _data[index * _cellSize + _cellContent]; } int getCodePoint(int index) { return _data[index * _cellSize + _cellContent] & CellContent.codepointMask; } int getWidth(int index) { return _data[index * _cellSize + _cellContent] >> CellContent.widthShift; } void getCellData(int index, CellData cellData) { final offset = index * _cellSize; cellData.foreground = _data[offset + _cellForeground]; cellData.background = _data[offset + _cellBackground]; cellData.flags = _data[offset + _cellAttributes]; cellData.content = _data[offset + _cellContent]; } CellData createCellData(int index) { final cellData = CellData.empty(); final offset = index * _cellSize; _data[offset + _cellForeground] = cellData.foreground; _data[offset + _cellBackground] = cellData.background; _data[offset + _cellAttributes] = cellData.flags; _data[offset + _cellContent] = cellData.content; return cellData; } void setForeground(int index, int value) { _data[index * _cellSize + _cellForeground] = value; } void setBackground(int index, int value) { _data[index * _cellSize + _cellBackground] = value; } void setAttributes(int index, int value) { _data[index * _cellSize + _cellAttributes] = value; } void setContent(int index, int value) { _data[index * _cellSize + _cellContent] = value; } void setCodePoint(int index, int char) { final width = unicodeV11.wcwidth(char); setContent(index, char | (width << CellContent.widthShift)); } void setCell(int index, int char, int witdh, CursorStyle style) { final offset = index * _cellSize; _data[offset + _cellForeground] = style.foreground; _data[offset + _cellBackground] = style.background; _data[offset + _cellAttributes] = style.attrs; _data[offset + _cellContent] = char | (witdh << CellContent.widthShift); } void setCellData(int index, CellData cellData) { final offset = index * _cellSize; _data[offset + _cellForeground] = cellData.foreground; _data[offset + _cellBackground] = cellData.background; _data[offset + _cellAttributes] = cellData.flags; _data[offset + _cellContent] = cellData.content; } void eraseCell(int index, CursorStyle style) { final offset = index * _cellSize; _data[offset + _cellForeground] = style.foreground; _data[offset + _cellBackground] = style.background; _data[offset + _cellAttributes] = style.attrs; _data[offset + _cellContent] = 0; } void resetCell(int index) { final offset = index * _cellSize; _data[offset + _cellForeground] = 0; _data[offset + _cellBackground] = 0; _data[offset + _cellAttributes] = 0; _data[offset + _cellContent] = 0; } void eraseRange(int start, int end, CursorStyle style) { // reset cell one to the left if start is second cell of a wide char if (start > 0 && getWidth(start - 1) == 2) { eraseCell(start - 1, style); } // reset cell one to the right if end is second cell of a wide char if (end < _length && getWidth(end - 1) == 2) { eraseCell(end - 1, style); } end = min(end, _length); for (var i = start; i < end; i++) { eraseCell(i, style); } } void removeCells(int start, int count, [CursorStyle? style]) { assert(start >= 0 && start < _length); assert(count >= 0 && start + count <= _length); style ??= CursorStyle.empty; if (start + count < _length) { final moveStart = start * _cellSize; final moveEnd = (_length - count) * _cellSize; final moveOffset = count * _cellSize; for (var i = moveStart; i < moveEnd; i++) { _data[i] = _data[i + moveOffset]; } } for (var i = _length - count; i < _length; i++) { eraseCell(i, style); } if (start > 0 && getWidth(start - 1) == 2) { eraseCell(start - 1, style); } } void insertCells(int start, int count, [CursorStyle? style]) { style ??= CursorStyle.empty; if (start > 0 && getWidth(start - 1) == 2) { eraseCell(start - 1, style); } if (start + count < _length) { final moveStart = start * _cellSize; final moveEnd = (_length - count) * _cellSize; final moveOffset = count * _cellSize; for (var i = moveEnd - 1; i >= moveStart; i--) { _data[i + moveOffset] = _data[i]; } } final end = min(start + count, _length); for (var i = start; i < end; i++) { eraseCell(i, style); } if (getWidth(_length - 1) == 2) { eraseCell(_length - 1, style); } } void resize(int length) { assert(length >= 0); if (length == _length) { return; } final newBufferSize = _calcCapacity(length) * _cellSize; if (newBufferSize > _data.length) { final newBuffer = Uint32List(newBufferSize); newBuffer.setRange(0, _data.length, _data); _data = newBuffer; } _length = length; } int getTrimmedLength([int? cols]) { if (cols == null) { cols = _data.length ~/ _cellSize; } for (var i = cols - 1; i >= 0; i--) { var codePoint = getCodePoint(i); if (codePoint != 0) { // we are at the last cell in this line that has content. // the length of this line is the index of this cell + 1 // the only exception is that if that last cell is wider // than 1 then we have to add the diff final lastCellWidth = getWidth(i); return i + lastCellWidth; } } return 0; } void copyFrom(BufferLine src, int srcCol, int dstCol, int len) { resize(dstCol + len); // data.setRange( // dstCol * _cellSize, // (dstCol + len) * _cellSize, // Uint32List.sublistView(src.data, srcCol * _cellSize, len * _cellSize), // ); var srcOffset = srcCol * _cellSize; var dstOffset = dstCol * _cellSize; for (var i = 0; i < len * _cellSize; i++) { _data[dstOffset++] = src._data[srcOffset++]; } } static int _calcCapacity(int length) { assert(length >= 0); var capacity = 64; if (length < 256) { while (capacity < length) { capacity *= 2; } } else { capacity = 256; while (capacity < length) { capacity += 32; } } return capacity; } String getText([int? from, int? to]) { if (from == null) { from = 0; } if (to == null) { to = _length; } final builder = StringBuffer(); for (var i = from; i < to; i++) { final codePoint = getCodePoint(i); final width = getWidth(i); if (codePoint != 0 && i + width <= to) { builder.writeCharCode(codePoint); } } return builder.toString(); } @override String toString() { return getText(); } }