Kaynağa Gözat

Merge remote-tracking branch 'origin/master' into feature/reflow

# Conflicts:
#	lib/buffer/buffer.dart
#	lib/buffer/buffer_line.dart
#	lib/buffer/cell.dart
#	lib/terminal/terminal.dart
devmil 4 yıl önce
ebeveyn
işleme
0990255d17

+ 81 - 104
lib/buffer/buffer.dart

@@ -1,19 +1,19 @@
 import 'dart:math' show max, min;
 
 import 'package:xterm/buffer/buffer_line.dart';
-import 'package:xterm/buffer/cell.dart';
-import 'package:xterm/buffer/cell_attr.dart';
 import 'package:xterm/terminal/charset.dart';
 import 'package:xterm/terminal/terminal.dart';
 import 'package:xterm/utli/scroll_range.dart';
 import 'package:xterm/utli/unicode_v11.dart';
 
-import 'buffer_reflow.dart';
-
 class Buffer {
   Buffer(this.terminal) {
     resetVerticalMargins();
-    lines = List.generate(terminal.viewHeight, (_) => BufferLine());
+
+    lines = List.generate(
+      terminal.viewHeight,
+      (_) => _newEmptyLine(),
+    );
   }
 
   final Terminal terminal;
@@ -25,7 +25,9 @@ class Buffer {
 
   int? _savedCursorX;
   int? _savedCursorY;
-  CellAttr? _savedCellAttr;
+  int? _savedCellFgColor;
+  int? _savedCellBgColor;
+  int? _savedCellFlags;
 
   // Indicates how far the bottom of the viewport is from the bottom of the
   // entire buffer. 0 if the viewport overlaps the terminal screen.
@@ -85,14 +87,14 @@ class Buffer {
     }
 
     final line = currentLine;
-    while (line.length <= _cursorX) {
-      line.add(Cell());
-    }
+    line.ensure(_cursorX + 1);
 
-    final cell = line.getCell(_cursorX);
-    cell.setCodePoint(codePoint);
-    cell.setWidth(cellWidth);
-    cell.setAttr(terminal.cellAttr.value);
+    line.cellInitialize(
+      _cursorX,
+      content: codePoint,
+      width: cellWidth,
+      cursor: terminal.cursor,
+    );
 
     if (_cursorX < terminal.viewWidth) {
       _cursorX++;
@@ -103,18 +105,11 @@ class Buffer {
     }
   }
 
+  /// get line in the viewport. [index] starts from 0, must be smaller than
+  /// [Terminal.viewHeight].
   BufferLine getViewLine(int index) {
-    if (index > terminal.viewHeight) {
-      return lines.last;
-    }
-
-    final rawIndex = convertViewLineToRawLine(index);
-
-    if (rawIndex >= lines.length) {
-      return BufferLine();
-    }
-
-    return lines[rawIndex];
+    index = index.clamp(0, terminal.viewHeight - 1);
+    return lines[convertViewLineToRawLine(index)];
   }
 
   BufferLine get currentLine {
@@ -184,8 +179,7 @@ class Buffer {
     eraseLineFromCursor();
 
     for (var i = _cursorY + 1; i < terminal.viewHeight; i++) {
-      getViewLine(i)
-          .erase(terminal.cellAttr.value, 0, terminal.viewWidth, true);
+      getViewLine(i).erase(terminal.cursor, 0, terminal.viewWidth);
     }
   }
 
@@ -193,40 +187,32 @@ class Buffer {
     eraseLineToCursor();
 
     for (var i = 0; i < _cursorY; i++) {
-      getViewLine(i)
-          .erase(terminal.cellAttr.value, 0, terminal.viewWidth, true);
+      getViewLine(i).erase(terminal.cursor, 0, terminal.viewWidth);
     }
   }
 
   void eraseDisplay() {
     for (var i = 0; i < terminal.viewHeight; i++) {
       final line = getViewLine(i);
-      line.erase(terminal.cellAttr.value, 0, terminal.viewWidth, true);
+      line.erase(terminal.cursor, 0, terminal.viewWidth);
     }
   }
 
   void eraseLineFromCursor() {
-    currentLine.erase(
-        terminal.cellAttr.value, _cursorX, terminal.viewWidth, _cursorX == 0);
+    currentLine.erase(terminal.cursor, _cursorX, terminal.viewWidth);
   }
 
   void eraseLineToCursor() {
-    currentLine.erase(terminal.cellAttr.value, 0, _cursorX, _cursorX == 0);
+    currentLine.erase(terminal.cursor, 0, _cursorX);
   }
 
   void eraseLine() {
-    currentLine.erase(terminal.cellAttr.value, 0, terminal.viewWidth, true);
+    currentLine.erase(terminal.cursor, 0, terminal.viewWidth);
   }
 
   void eraseCharacters(int count) {
     final start = _cursorX;
-    for (var i = start; i < start + count; i++) {
-      if (i >= currentLine.length) {
-        currentLine.add(Cell(attr: terminal.cellAttr.value));
-      } else {
-        currentLine.getCell(i).erase(terminal.cellAttr.value);
-      }
-    }
+    currentLine.erase(terminal.cursor, start, start + count);
   }
 
   ScrollRange getAreaScrollRange() {
@@ -246,7 +232,7 @@ class Buffer {
       if (i >= scrollRange.top + lines) {
         this.lines[i] = this.lines[i - lines];
       } else {
-        this.lines[i] = BufferLine();
+        this.lines[i] = _newEmptyLine();
       }
     }
   }
@@ -258,7 +244,7 @@ class Buffer {
       if (i + lines < scrollRange.bottom) {
         this.lines[i] = this.lines[i + lines];
       } else {
-        this.lines[i] = BufferLine();
+        this.lines[i] = _newEmptyLine();
       }
     }
   }
@@ -283,7 +269,7 @@ class Buffer {
     // the cursor is not in the scrollable region
     if (_cursorY >= terminal.viewHeight - 1) {
       // we are at the bottom so a new line is created.
-      lines.add(BufferLine());
+      lines.add(_newEmptyLine());
 
       // keep viewport from moving if user is scrolling.
       if (isUserScrolling) {
@@ -310,28 +296,6 @@ class Buffer {
     }
   }
 
-  Cell? getCell(int col, int row) {
-    final rawRow = convertViewLineToRawLine(row);
-    return getRawCell(col, rawRow);
-  }
-
-  Cell? getRawCell(int col, int rawRow) {
-    if (col < 0 || rawRow < 0 || rawRow >= lines.length) {
-      return null;
-    }
-
-    final line = lines[rawRow];
-    if (col >= line.length) {
-      return null;
-    }
-
-    return line.getCell(col);
-  }
-
-  Cell? getCellUnderCursor() {
-    return getCell(cursorX, cursorY);
-  }
-
   void cursorGoForward() {
     setCursorX(_cursorX + 1);
   }
@@ -390,24 +354,25 @@ class Buffer {
   }
 
   void saveCursor() {
-    _savedCellAttr = terminal.cellAttr.value;
+    _savedCellFlags = terminal.cursor.flags;
+    _savedCellFgColor = terminal.cursor.fg;
+    _savedCellBgColor = terminal.cursor.bg;
     _savedCursorX = _cursorX;
     _savedCursorY = _cursorY;
     charset.save();
   }
 
-  void adjustSavedCursor(int diffX, int diffY) {
-    if (_savedCursorX != null) {
-      _savedCursorX = _savedCursorX! + diffX;
+  void restoreCursor() {
+    if (_savedCellFlags != null) {
+      terminal.cursor.flags = _savedCellFlags!;
     }
-    if (_savedCursorY != null) {
-      _savedCursorY = _savedCursorY! + diffY;
+
+    if (_savedCellFgColor != null) {
+      terminal.cursor.fg = _savedCellFgColor!;
     }
-  }
 
-  void restoreCursor() {
-    if (_savedCellAttr != null) {
-      terminal.cellAttr.use(_savedCellAttr!);
+    if (_savedCellBgColor != null) {
+      terminal.cursor.bg = _savedCellBgColor!;
     }
 
     if (_savedCursorX != null) {
@@ -444,8 +409,8 @@ class Buffer {
   }
 
   void deleteChars(int count) {
-    final start = _cursorX.clamp(0, currentLine.length);
-    final end = min(_cursorX + count, currentLine.length);
+    final start = _cursorX.clamp(0, terminal.viewWidth);
+    final end = min(_cursorX + count, terminal.viewWidth);
     currentLine.removeRange(start, end);
   }
 
@@ -459,13 +424,17 @@ class Buffer {
 
   void clear() {
     lines.clear();
-    lines.addAll(List.generate(terminal.viewHeight, (_) => BufferLine()));
+
+    lines.addAll(List.generate(
+      terminal.viewHeight,
+      (_) => _newEmptyLine(),
+    ));
   }
 
   void insertBlankCharacters(int count) {
     for (var i = 0; i < count; i++) {
-      final cell = Cell(attr: terminal.cellAttr.value);
-      currentLine.insert(_cursorX + i, cell);
+      currentLine.insert(_cursorX + i);
+      currentLine.cellSetFlags(_cursorX + i, terminal.cursor.flags);
     }
   }
 
@@ -484,7 +453,7 @@ class Buffer {
   void insertLine() {
     if (!isInScrollableRegion) {
       final index = convertViewLineToRawLine(_cursorX);
-      final newLine = BufferLine();
+      final newLine = _newEmptyLine();
       lines.insert(index, newLine);
 
       final maxLines = terminal.maxLines;
@@ -497,7 +466,7 @@ class Buffer {
       final movedLines = lines.getRange(_cursorY, bottom - 1);
       lines.setRange(_cursorY + 1, bottom, movedLines);
 
-      final newLine = BufferLine();
+      final newLine = _newEmptyLine();
       lines[_cursorY] = newLine;
     }
   }
@@ -524,33 +493,41 @@ class Buffer {
     lines.removeAt(index);
   }
 
-  void resize(
-      int width, int height, int oldWidth, int oldHeight, bool doReflow) {
-    if (this.lines.length > 0) {
-      if (oldHeight < height) {
-        for (int y = oldHeight; y < height; y++) {
-          if (_cursorY < terminal.viewHeight - 1) {
-            lines.add(BufferLine());
-          } else {
-            _cursorY++;
-          }
+  void resize(int newWidth, int newHeight) {
+    if (newWidth > terminal.viewWidth) {
+      for (var line in lines) {
+        line.ensure(newWidth);
+      }
+    }
+
+    if (newHeight > terminal.viewHeight) {
+      // Grow larger
+      for (var i = 0; i < newHeight - terminal.viewHeight; i++) {
+        if (_cursorY < terminal.viewHeight - 1) {
+          lines.add(_newEmptyLine());
+        } else {
+          _cursorY++;
         }
-      } else {
-        for (var i = 0; i < oldHeight - height; i++) {
-          if (_cursorY < terminal.viewHeight - 1) {
-            lines.removeLast();
-          } else {
-            _cursorY++;
-          }
+      }
+    } else {
+      // Shrink smaller
+      for (var i = 0; i < terminal.viewHeight - newHeight; i++) {
+        if (_cursorY < terminal.viewHeight - 1) {
+          lines.removeLast();
+        } else {
+          _cursorY++;
         }
       }
     }
 
-    // ScrollBottom = newRows - 1;
+    // Ensure cursor is within the screen.
+    _cursorX = _cursorX.clamp(0, newWidth - 1);
+    _cursorY = _cursorY.clamp(0, newHeight - 1);
+  }
 
-    if (doReflow) {
-      final rf = BufferReflow(this);
-      rf.doReflow(oldWidth, width);
-    }
+  BufferLine _newEmptyLine() {
+    final line = BufferLine();
+    line.ensure(terminal.viewWidth);
+    return line;
   }
 }

+ 156 - 63
lib/buffer/buffer_line.dart

@@ -1,94 +1,187 @@
-import 'package:xterm/buffer/cell.dart';
-import 'package:xterm/buffer/cell_attr.dart';
+import 'dart:typed_data';
+
+import 'package:xterm/terminal/cursor.dart';
+
+/// Line layout:
+///   |  cell  |  cell  |  cell  |  cell  | ...
+///   (16 bytes per cell)
+///
+/// Cell layout:
+///   | code point |  fg color  |  bg color  | attributes |
+///       4bytes       4bytes       4bytes       4bytes
+///
+/// Attributes layout:
+///   |  width  |  flags  | reserved | reserved |
+///      1byte     1byte     1byte      1byte
+
+const _cellSize = 16;
+
+const _cellContent = 0;
+const _cellFgColor = 4;
+const _cellBgColor = 8;
+
+// const _cellAttributes = 12;
+const _cellWidth = 12;
+const _cellFlags = 13;
 
 class BufferLine {
-  final _cells = <Cell>[];
+  BufferLine() {
+    const initLength = 64;
+    _cells = ByteData(initLength * _cellSize);
+  }
+
+  late ByteData _cells;
+
+  bool get isWrapped => _isWrapped;
   bool _isWrapped = false;
 
-  BufferLine({isWrapped = false}) {
-    _isWrapped = isWrapped;
+  void ensure(int length) {
+    final expectedLengthInBytes = length * _cellSize;
+
+    if (expectedLengthInBytes < _cells.lengthInBytes) {
+      return;
+    }
+
+    var newLengthInBytes = _cells.lengthInBytes;
+    while (newLengthInBytes < expectedLengthInBytes) {
+      newLengthInBytes *= 2;
+    }
+
+    final newCells = ByteData(newLengthInBytes);
+    newCells.buffer.asInt64List().setAll(0, _cells.buffer.asInt64List());
+    _cells = newCells;
   }
 
-  bool get isWrapped {
-    return _isWrapped;
+  void insert(int index) {
+    insertN(index, 1);
   }
 
-  int get length {
-    return _cells.length;
+  void insertN(int index, int count) {
+    //                       start
+    // +--------------------------|-----------------------------------+
+    // |                          |                                   |
+    // +--------------------------\--\--------------------------------+
+    //                             \  \
+    //                              \  \
+    //                               v  v
+    // +--------------------------|--|--------------------------------+
+    // |                          |  |                                |
+    // +--------------------------|--|--------------------------------+
+    //                       start   start+offset
+
+    final start = (index * _cellSize).clamp(0, _cells.lengthInBytes);
+    final offset = (count * _cellSize).clamp(0, _cells.lengthInBytes - start);
+
+    // move data forward
+    final cells = _cells.buffer.asInt8List();
+    for (var i = _cells.lengthInBytes - offset - 1; i >= start; i++) {
+      cells[i + offset] = cells[i];
+    }
+
+    // set inserted cells to 0
+    for (var i = start; i < start + offset; i++) {
+      cells[i] = 0x00;
+    }
   }
 
-  void add(Cell cell) {
-    _cells.add(cell);
+  void clear() {
+    _cells.buffer.asInt64List().clear();
   }
 
-  void insert(int index, Cell cell) {
-    _cells.insert(index, cell);
+  void erase(Cursor cursor, int start, int end) {
+    ensure(end);
+    for (var i = start; i < end; i++) {
+      cellErase(i, cursor);
+    }
   }
 
-  void clear() {
-    _cells.clear();
+  void cellInitialize(
+    int index, {
+    required int content,
+    required int width,
+    required Cursor cursor,
+  }) {
+    final cell = index * _cellSize;
+    _cells.setInt32(cell + _cellContent, content);
+    _cells.setInt32(cell + _cellFgColor, cursor.fg);
+    _cells.setInt32(cell + _cellBgColor, cursor.bg);
+    _cells.setInt8(cell + _cellWidth, width);
+    _cells.setInt8(cell + _cellFlags, cursor.flags);
   }
 
-  int getTrimmedLength() {
-    int width = 0;
-    for (int i = 0; i < _cells.length; i++)
-      if (_cells[i].codePoint != null && _cells[i].codePoint != 0) {
-        width += _cells[i].width;
-      } else {
-        return width;
-      }
-    return width;
+  int cellGetContent(int index) {
+    return _cells.getInt32(index * _cellSize + _cellContent);
   }
 
-  void erase(CellAttr attr, int start, int end, bool clearWrap) {
-    for (var i = start; i < end; i++) {
-      if (i >= length) {
-        add(Cell(attr: attr));
-      } else {
-        getCell(i).erase(attr);
-      }
-    }
-    if (_isWrapped && clearWrap) {
-      _isWrapped = false;
-    }
+  void cellSetContent(int index, int content) {
+    return _cells.setInt32(index * _cellSize + _cellContent, content);
   }
 
-  Cell getCell(int index) {
-    return _cells[index];
+  int cellGetFgColor(int index) {
+    return _cells.getInt32(index * _cellSize + _cellFgColor);
   }
 
-  void removeRange(int start, [int? end]) {
-    start = start.clamp(0, _cells.length);
-    end ??= _cells.length;
-    end = end.clamp(start, _cells.length);
-    _cells.removeRange(start, end);
+  void cellSetFgColor(int index, int color) {
+    _cells.setInt32(index * _cellSize + _cellFgColor, color);
   }
 
-  void copyCellsFrom(BufferLine src, int srcCol, int dstCol, int len) {
-    final requiredCells = dstCol + len;
-    if (_cells.length < requiredCells) {
-      _cells.addAll(List<Cell>.generate(
-          requiredCells - _cells.length, (index) => Cell()));
-    }
-    //we have to make a copy first as src and dst might be the same line
-    List<Cell> sourceCells =
-        List<Cell>.generate(len, (index) => src._cells[srcCol + index].clone());
-    for (var i = 0; i < len; i++) {
-      _cells[dstCol + i] = sourceCells[i];
-    }
+  int cellGetBgColor(int index) {
+    return _cells.getInt32(index * _cellSize + _cellBgColor);
   }
 
-  int getWidthAt(int col) {
-    if (col >= _cells.length) {
-      return 1;
-    }
-    return _cells[col].width;
+  void cellSetBgColor(int index, int color) {
+    _cells.setInt32(index * _cellSize + _cellBgColor, color);
+  }
+
+  int cellGetFlags(int index) {
+    return _cells.getInt8(index * _cellSize + _cellFlags);
+  }
+
+  void cellSetFlags(int index, int flags) {
+    _cells.setInt8(index * _cellSize + _cellFlags, flags);
+  }
+
+  int cellGetWidth(int index) {
+    return _cells.getInt8(index * _cellSize + _cellWidth);
   }
 
-  bool hasContentAt(int col) {
-    if (col >= _cells.length) {
-      return false;
+  void cellSetWidth(int index, int width) {
+    _cells.setInt8(index * _cellSize + _cellWidth, width);
+  }
+
+  void cellClearFlags(int index) {
+    cellSetFlags(index, 0);
+  }
+
+  bool cellHasFlag(int index, int flag) {
+    return cellGetFlags(index) & flag != 0;
+  }
+
+  void cellSetFlag(int index, int flag) {
+    cellSetFlags(index, cellGetFlags(index) | flag);
+  }
+
+  void cellErase(int index, Cursor cursor) {
+    cellSetContent(index, 0x00);
+    cellSetFgColor(index, cursor.fg);
+    cellSetBgColor(index, cursor.bg);
+    cellSetFlags(index, cursor.flags);
+  }
+
+  // int cellGetHash(int index) {
+  //   final cell = index * _cellSize;
+  //   final a = _cells.getInt64(cell);
+  //   final b = _cells.getInt64(cell + 8);
+  //   return a ^ b;
+  // }
+
+  void removeRange(int start, int end) {
+    // start = start.clamp(0, _cells.length);
+    // end ??= _cells.length;
+    // end = end.clamp(start, _cells.length);
+    // _cells.removeRange(start, end);
+    for (var index = start; index < end; index++) {
+      cellSetContent(index, 0x00);
     }
-    return _cells[col].codePoint != 0;
   }
 }

+ 0 - 38
lib/buffer/cell.dart

@@ -1,38 +0,0 @@
-import 'package:xterm/buffer/cell_attr.dart';
-
-class Cell {
-  Cell({this.codePoint, this.width = 1, this.attr});
-
-  int? codePoint;
-  int width;
-  CellAttr? attr;
-
-  void setCodePoint(int codePoint) {
-    this.codePoint = codePoint;
-  }
-
-  void setAttr(CellAttr attr) {
-    this.attr = attr;
-  }
-
-  void setWidth(int width) {
-    this.width = width;
-  }
-
-  void reset(CellAttr attr) {
-    codePoint = null;
-    this.attr = attr;
-  }
-
-  void erase(CellAttr attr) {
-    codePoint = null;
-    this.attr = attr;
-  }
-
-  @override
-  String toString() {
-    return 'Cell($codePoint)';
-  }
-
-  Cell clone() => Cell(codePoint: this.codePoint, width: this.width, attr: this.attr);
-}

+ 0 - 156
lib/buffer/cell_attr.dart

@@ -1,156 +0,0 @@
-import 'package:xterm/theme/terminal_color.dart';
-import 'package:xterm/utli/hash_values.dart';
-
-class CellAttr {
-  CellAttr({
-    this.fgColor,
-    this.bgColor,
-    this.bold = false,
-    this.faint = false,
-    this.italic = false,
-    this.underline = false,
-    this.blink = false,
-    this.inverse = false,
-    this.invisible = false,
-  }) : hashCode = hashValues(
-          fgColor,
-          bgColor,
-          bold,
-          faint,
-          italic,
-          underline,
-          blink,
-          inverse,
-          invisible,
-        );
-
-  final TerminalColor? fgColor;
-  final TerminalColor? bgColor;
-  final bool bold;
-  final bool faint;
-  final bool italic;
-  final bool underline;
-  final bool blink;
-  final bool inverse;
-  final bool invisible;
-
-  @override
-  final int hashCode;
-
-  @override
-  bool operator ==(Object other) => other.hashCode == hashCode;
-
-  // CellAttr copy() {
-  //   return CellAttr(
-  //     fgColor: this.fgColor,
-  //     bgColor: this.bgColor,
-  //     bold: this.bold,
-  //     faint: this.faint,
-  //     italic: this.italic,
-  //     underline: this.underline,
-  //     blink: this.blink,
-  //     inverse: this.inverse,
-  //     invisible: this.invisible,
-  //   );
-  // }
-
-  // void reset({
-  //   @required fgColor,
-  //   bgColor,
-  //   bold = false,
-  //   faint = false,
-  //   italic = false,
-  //   underline = false,
-  //   blink = false,
-  //   inverse = false,
-  //   invisible = false,
-  // }) {
-  //   this.fgColor = fgColor;
-  //   this.bgColor = bgColor;
-  //   this.bold = bold;
-  //   this.faint = faint;
-  //   this.italic = italic;
-  //   this.underline = underline;
-  //   this.blink = blink;
-  //   this.inverse = inverse;
-  //   this.invisible = invisible;
-  // }
-
-  CellAttr copyWith({
-    TerminalColor? fgColor,
-    TerminalColor? bgColor,
-    bool? bold,
-    bool? faint,
-    bool? italic,
-    bool? underline,
-    bool? blink,
-    bool? inverse,
-    bool? invisible,
-  }) {
-    return CellAttr(
-      fgColor: fgColor ?? this.fgColor,
-      bgColor: bgColor ?? this.bgColor,
-      bold: bold ?? this.bold,
-      faint: faint ?? this.faint,
-      italic: italic ?? this.italic,
-      underline: underline ?? this.underline,
-      blink: blink ?? this.blink,
-      inverse: inverse ?? this.inverse,
-      invisible: invisible ?? this.invisible,
-    );
-  }
-}
-
-class CellAttrTemplate {
-  CellAttrTemplate() {
-    reset();
-  }
-
-  late CellAttr _attr;
-
-  set fgColor(TerminalColor value) {
-    _attr = _attr.copyWith(fgColor: value);
-  }
-
-  set bgColor(TerminalColor value) {
-    _attr = _attr.copyWith(bgColor: value);
-  }
-
-  set bold(bool value) {
-    _attr = _attr.copyWith(bold: value);
-  }
-
-  set faint(bool value) {
-    _attr = _attr.copyWith(faint: value);
-  }
-
-  set italic(bool value) {
-    _attr = _attr.copyWith(italic: value);
-  }
-
-  set underline(bool value) {
-    _attr = _attr.copyWith(underline: value);
-  }
-
-  set blink(bool value) {
-    _attr = _attr.copyWith(blink: value);
-  }
-
-  set inverse(bool value) {
-    _attr = _attr.copyWith(inverse: value);
-  }
-
-  set invisible(bool value) {
-    _attr = _attr.copyWith(invisible: value);
-  }
-
-  CellAttr get value => _attr;
-
-  void reset() {
-    _attr = CellAttr();
-  }
-
-  void use(CellAttr attr) {
-    _attr = attr;
-  }
-}

+ 9 - 0
lib/buffer/cell_flags.dart

@@ -0,0 +1,9 @@
+abstract class CellFlags {
+  static const bold = 1 << 0;
+  static const faint = 1 << 1;
+  static const italic = 1 << 2;
+  static const underline = 1 << 3;
+  static const blink = 1 << 4;
+  static const inverse = 1 << 5;
+  static const invisible = 1 << 6;
+}

+ 90 - 52
lib/frontend/terminal_view.dart

@@ -6,7 +6,8 @@ import 'package:flutter/material.dart';
 import 'package:flutter/rendering.dart';
 import 'package:flutter/scheduler.dart';
 import 'package:flutter/services.dart';
-import 'package:xterm/buffer/cell.dart';
+import 'package:xterm/buffer/buffer_line.dart';
+import 'package:xterm/buffer/cell_flags.dart';
 import 'package:xterm/frontend/char_size.dart';
 import 'package:xterm/frontend/helpers.dart';
 import 'package:xterm/frontend/input_behavior.dart';
@@ -17,6 +18,7 @@ import 'package:xterm/frontend/cache.dart';
 import 'package:xterm/mouse/position.dart';
 import 'package:xterm/terminal/terminal.dart';
 import 'package:xterm/theme/terminal_style.dart';
+import 'package:xterm/utli/bit_flags.dart';
 import 'package:xterm/utli/hash_values.dart';
 
 typedef TerminalResizeHandler = void Function(int width, int height);
@@ -196,6 +198,9 @@ class _TerminalViewState extends State<TerminalView> {
     return GestureDetector(
       behavior: HitTestBehavior.deferToChild,
       dragStartBehavior: DragStartBehavior.down,
+      onDoubleTapDown: (details) {
+        print('details : $details');
+      },
       onTapDown: (detail) {
         if (widget.terminal.selection.isEmpty) {
           InputListener.of(context)!.requestKeyboard();
@@ -221,8 +226,6 @@ class _TerminalViewState extends State<TerminalView> {
       },
       child: Container(
         constraints: BoxConstraints.expand(),
-        color: Color(widget.terminal.theme.background.value)
-            .withOpacity(widget.opacity),
         child: CustomPaint(
           painter: TerminalPainter(
             terminal: widget.terminal,
@@ -232,10 +235,13 @@ class _TerminalViewState extends State<TerminalView> {
             charSize: _cellSize,
           ),
         ),
+        color:
+            Color(widget.terminal.theme.background).withOpacity(widget.opacity),
       ),
     );
   }
 
+  /// Get global cell position from mouse position.
   Position getMouseOffset(double px, double py) {
     final col = (px / _cellSize.cellWidth).floor();
     final row = (py / _cellSize.cellHeight).floor();
@@ -333,30 +339,41 @@ class TerminalPainter extends CustomPainter {
   void _paintBackground(Canvas canvas) {
     final lines = terminal.getVisibleLines();
 
-    for (var i = 0; i < lines.length; i++) {
-      final line = lines[i];
-      final offsetY = i * charSize.cellHeight;
-      final cellCount = math.min(terminal.viewWidth, line.length);
-
-      for (var i = 0; i < cellCount; i++) {
-        final cell = line.getCell(i);
-        final attr = cell.attr;
+    for (var row = 0; row < lines.length; row++) {
+      final line = lines[row];
+      final offsetY = row * charSize.cellHeight;
+      // final cellCount = math.min(terminal.viewWidth, line.length);
+      final cellCount = terminal.viewWidth;
 
-        if (attr == null || cell.width == 0) {
+      for (var col = 0; col < cellCount; col++) {
+        final cellWidth = line.cellGetWidth(col);
+        if (cellWidth == 0) {
           continue;
         }
 
-        final offsetX = i * charSize.cellWidth;
-        final effectWidth = charSize.cellWidth * cell.width + 1;
-        final effectHeight = charSize.cellHeight + 1;
-
-        final bgColor = attr.inverse ? attr.fgColor : attr.bgColor;
+        final cellFgColor = line.cellGetFgColor(col);
+        final cellBgColor = line.cellGetBgColor(col);
+        final effectBgColor = line.cellHasFlag(col, CellFlags.inverse)
+            ? cellFgColor
+            : cellBgColor;
 
-        if (bgColor == null) {
+        if (effectBgColor == 0x00) {
           continue;
         }
 
-        final paint = Paint()..color = Color(bgColor.value);
+        // final cellFlags = line.cellGetFlags(i);
+        // final cell = line.getCell(i);
+        // final attr = cell.attr;
+
+        final offsetX = col * charSize.cellWidth;
+        final effectWidth = charSize.cellWidth * cellWidth + 1;
+        final effectHeight = charSize.cellHeight + 1;
+
+        // background color is already painted with opacity by the Container of
+        // TerminalPainter so wo don't need to fallback to
+        // terminal.theme.background here.
+
+        final paint = Paint()..color = Color(effectBgColor);
         canvas.drawRect(
           Rect.fromLTWH(offsetX, offsetY, effectWidth, effectHeight),
           paint,
@@ -366,6 +383,8 @@ class TerminalPainter extends CustomPainter {
   }
 
   void _paintSelection(Canvas canvas) {
+    final paint = Paint()..color = Colors.white.withOpacity(0.3);
+
     for (var y = 0; y < terminal.viewHeight; y++) {
       final offsetY = y * charSize.cellHeight;
       final absoluteY = terminal.buffer.convertViewLineToRawLine(y) -
@@ -388,7 +407,6 @@ class TerminalPainter extends CustomPainter {
         final effectWidth = cellCount * charSize.cellWidth;
         final effectHeight = charSize.cellHeight;
 
-        final paint = Paint()..color = Colors.white.withOpacity(0.3);
         canvas.drawRect(
           Rect.fromLTWH(offsetX, offsetY, effectWidth, effectHeight),
           paint,
@@ -402,70 +420,90 @@ class TerminalPainter extends CustomPainter {
   void _paintText(Canvas canvas) {
     final lines = terminal.getVisibleLines();
 
-    for (var i = 0; i < lines.length; i++) {
-      final line = lines[i];
-      final offsetY = i * charSize.cellHeight;
-      final cellCount = math.min(terminal.viewWidth, line.length);
+    for (var row = 0; row < lines.length; row++) {
+      final line = lines[row];
+      final offsetY = row * charSize.cellHeight;
+      // final cellCount = math.min(terminal.viewWidth, line.length);
+      final cellCount = terminal.viewWidth;
 
-      for (var i = 0; i < cellCount; i++) {
-        final cell = line.getCell(i);
+      for (var col = 0; col < cellCount; col++) {
+        final width = line.cellGetWidth(col);
 
-        if (cell.attr == null || cell.width == 0) {
+        if (width == 0) {
           continue;
         }
 
-        final offsetX = i * charSize.cellWidth;
-        _paintCell(canvas, cell, offsetX, offsetY);
+        final offsetX = col * charSize.cellWidth;
+        _paintCell(canvas, line, col, offsetX, offsetY);
       }
     }
   }
 
-  void _paintCell(Canvas canvas, Cell cell, double offsetX, double offsetY) {
-    final attr = cell.attr!;
-
-    if (cell.codePoint == null || attr.invisible) {
+  void _paintCell(
+    Canvas canvas,
+    BufferLine line,
+    int cell,
+    double offsetX,
+    double offsetY,
+  ) {
+    final codePoint = line.cellGetContent(cell);
+    final fgColor = line.cellGetFgColor(cell);
+    final bgColor = line.cellGetBgColor(cell);
+    final flags = line.cellGetFlags(cell);
+
+    if (codePoint == 0 || flags.hasFlag(CellFlags.invisible)) {
       return;
     }
 
-    final cellHash = hashValues(cell.codePoint, attr);
+    // final cellHash = line.cellGetHash(cell);
+    final cellHash = hashValues(codePoint, fgColor, bgColor, flags);
+
     var tp = textLayoutCache.getLayoutFromCache(cellHash);
     if (tp != null) {
       tp.paint(canvas, Offset(offsetX, offsetY));
       return;
     }
 
-    final cellColor = attr.inverse
-        ? attr.bgColor ?? terminal.theme.background
-        : attr.fgColor ?? terminal.theme.foreground;
+    final cellColor = flags.hasFlag(CellFlags.inverse) ? bgColor : fgColor;
 
-    var color = Color(cellColor.value);
+    var color = Color(cellColor);
 
-    if (attr.faint) {
+    if (flags & CellFlags.faint != 0) {
       color = color.withOpacity(0.5);
     }
 
     final style = (view.style.textStyleProvider != null)
         ? view.style.textStyleProvider!(
             color: color,
-            fontWeight: attr.bold ? FontWeight.bold : FontWeight.normal,
-            fontStyle: attr.italic ? FontStyle.italic : FontStyle.normal,
             fontSize: view.style.fontSize,
-            decoration:
-                attr.underline ? TextDecoration.underline : TextDecoration.none,
+            fontWeight: flags.hasFlag(CellFlags.bold)
+                ? FontWeight.bold
+                : FontWeight.normal,
+            fontStyle: flags.hasFlag(CellFlags.italic)
+                ? FontStyle.italic
+                : FontStyle.normal,
+            decoration: flags.hasFlag(CellFlags.underline)
+                ? TextDecoration.underline
+                : TextDecoration.none,
           )
         : TextStyle(
             color: color,
-            fontWeight: attr.bold ? FontWeight.bold : FontWeight.normal,
-            fontStyle: attr.italic ? FontStyle.italic : FontStyle.normal,
             fontSize: view.style.fontSize,
-            decoration:
-                attr.underline ? TextDecoration.underline : TextDecoration.none,
+            fontWeight: flags.hasFlag(CellFlags.bold)
+                ? FontWeight.bold
+                : FontWeight.normal,
+            fontStyle: flags.hasFlag(CellFlags.italic)
+                ? FontStyle.italic
+                : FontStyle.normal,
+            decoration: flags.hasFlag(CellFlags.underline)
+                ? TextDecoration.underline
+                : TextDecoration.none,
             fontFamily: 'monospace',
             fontFamilyFallback: view.style.fontFamily,
           );
 
     final span = TextSpan(
-      text: String.fromCharCode(cell.codePoint!),
+      text: String.fromCharCode(codePoint),
       // text: codePointCache.getOrConstruct(cell.codePoint),
       style: style,
     );
@@ -482,16 +520,16 @@ class TerminalPainter extends CustomPainter {
       return;
     }
 
-    final char = terminal.buffer.getCellUnderCursor();
-    final width =
-        char != null ? charSize.cellWidth * char.width : charSize.cellWidth;
+    final width = charSize.cellWidth *
+        terminal.buffer.currentLine.cellGetWidth(terminal.cursorX).clamp(1, 2);
 
     final offsetX = charSize.cellWidth * terminal.cursorX;
     final offsetY = charSize.cellHeight * screenCursorY;
     final paint = Paint()
-      ..color = Color(terminal.theme.cursor.value)
+      ..color = Color(terminal.theme.cursor)
       ..strokeWidth = focused ? 0.0 : 1.0
       ..style = focused ? PaintingStyle.fill : PaintingStyle.stroke;
+
     canvas.drawRect(
         Rect.fromLTWH(offsetX, offsetY, width, charSize.cellHeight), paint);
   }

+ 51 - 16
lib/terminal/ansi.dart

@@ -3,24 +3,39 @@ import 'dart:collection';
 import 'package:xterm/terminal/csi.dart';
 import 'package:xterm/terminal/osc.dart';
 import 'package:xterm/terminal/terminal.dart';
+import 'package:xterm/utli/lookup_table.dart';
 
-typedef AnsiHandler = void Function(Queue<int>, Terminal);
+/// Handler of terminal sequences. Returns true if the sequence is consumed,
+/// false to indicate that the sequence is not completed and no charater is
+/// consumed from the queue.
+typedef AnsiHandler = bool Function(Queue<int>, Terminal);
+
+bool ansiHandler(Queue<int> queue, Terminal terminal) {
+  // The sequence isn't completed, just ignore it.
+  if (queue.isEmpty) {
+    return false;
+  }
 
-void ansiHandler(Queue<int> queue, Terminal terminal) {
   final charAfterEsc = queue.removeFirst();
 
   final handler = _ansiHandlers[charAfterEsc];
   if (handler != null) {
-    if (handler != csiHandler && handler != oscHandler) {
-      terminal.debug.onEsc(charAfterEsc);
+    // if (handler != csiHandler && handler != oscHandler) {
+    //   terminal.debug.onEsc(charAfterEsc);
+    // }
+
+    final finished = handler(queue, terminal);
+    if (!finished) {
+      queue.addFirst(charAfterEsc);
     }
-    return handler(queue, terminal);
+    return finished;
   }
 
   terminal.debug.onError('unsupported ansi sequence: $charAfterEsc');
+  return true;
 }
 
-final _ansiHandlers = <int, AnsiHandler>{
+final _ansiHandlers = FastLookupTable({
   '['.codeUnitAt(0): csiHandler,
   ']'.codeUnitAt(0): oscHandler,
   '7'.codeUnitAt(0): _ansiSaveCursorHandler,
@@ -38,24 +53,34 @@ final _ansiHandlers = <int, AnsiHandler>{
   '+'.codeUnitAt(0): _voidHandler(1), // TODO: G3 (vt220)
   '>'.codeUnitAt(0): _voidHandler(0), // TODO: Normal Keypad
   '='.codeUnitAt(0): _voidHandler(0), // TODO: Application Keypad
-};
+});
 
 AnsiHandler _voidHandler(int sequenceLength) {
   return (queue, terminal) {
-    queue.take(sequenceLength);
+    if (queue.length < sequenceLength) {
+      return false;
+    }
+
+    for (var i = 0; i < sequenceLength; i++) {
+      queue.removeFirst();
+    }
+    return true;
   };
 }
 
-void _unsupportedHandler(Queue<int> queue, Terminal terminal) async {
+bool _unsupportedHandler(Queue<int> queue, Terminal terminal) {
   // print('unimplemented ansi sequence.');
+  return true;
 }
 
-void _ansiSaveCursorHandler(Queue<int> queue, Terminal terminal) {
+bool _ansiSaveCursorHandler(Queue<int> queue, Terminal terminal) {
   terminal.buffer.saveCursor();
+  return true;
 }
 
-void _ansiRestoreCursorHandler(Queue<int> queue, Terminal terminal) {
+bool _ansiRestoreCursorHandler(Queue<int> queue, Terminal terminal) {
   terminal.buffer.restoreCursor();
+  return true;
 }
 
 /// https://vt100.net/docs/vt100-ug/chapter3.html#IND IND – Index
@@ -65,12 +90,14 @@ void _ansiRestoreCursorHandler(Queue<int> queue, Terminal terminal) {
 /// This sequence 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 _ansiIndexHandler(Queue<int> queue, Terminal terminal) {
+bool _ansiIndexHandler(Queue<int> queue, Terminal terminal) {
   terminal.buffer.index();
+  return true;
 }
 
-void _ansiReverseIndexHandler(Queue<int> queue, Terminal terminal) {
+bool _ansiReverseIndexHandler(Queue<int> queue, Terminal terminal) {
   terminal.buffer.reverseIndex();
+  return true;
 }
 
 /// SCS – Select Character Set
@@ -80,16 +107,24 @@ void _ansiReverseIndexHandler(Queue<int> queue, Terminal terminal) {
 /// SO (shift in and shift out) respectively.
 AnsiHandler _scsHandler(int which) {
   return (Queue<int> queue, Terminal terminal) {
-    final name = String.fromCharCode(queue.removeFirst());
+    // The sequence isn't completed, just ignore it.
+    if (queue.isEmpty) {
+      return false;
+    }
+
+    final name = queue.removeFirst();
     terminal.buffer.charset.designate(which, name);
+    return true;
   };
 }
 
-void _ansiNextLineHandler(Queue<int> queue, Terminal terminal) {
+bool _ansiNextLineHandler(Queue<int> queue, Terminal terminal) {
   terminal.buffer.newLine();
   terminal.buffer.setCursorX(0);
+  return true;
 }
 
-void _ansiTabSetHandler(Queue<int> queue, Terminal terminal) {
+bool _ansiTabSetHandler(Queue<int> queue, Terminal terminal) {
   terminal.tabSetAtCursor();
+  return true;
 }

+ 4 - 4
lib/terminal/charset.dart

@@ -1,8 +1,8 @@
 typedef CharsetTranslator = int Function(int);
 
-const _charsets = <String, CharsetTranslator>{
-  '0': decSpecGraphicsTranslator,
-  'B': asciiTranslator,
+final _charsets = <int, CharsetTranslator>{
+  '0'.codeUnitAt(0): decSpecGraphicsTranslator,
+  'B'.codeUnitAt(0): asciiTranslator,
 };
 
 class Charset {
@@ -22,7 +22,7 @@ class Charset {
     return _cached(codePoint);
   }
 
-  void designate(int index, String name) {
+  void designate(int index, int name) {
     final charset = _charsets[name];
     if (charset != null) {
       _charsetMap[index] = charset;

+ 122 - 58
lib/terminal/csi.dart

@@ -1,12 +1,14 @@
 import 'dart:collection';
+import 'dart:convert';
 
 import 'package:xterm/terminal/modes.dart';
 import 'package:xterm/terminal/sgr.dart';
 import 'package:xterm/terminal/terminal.dart';
+import 'package:xterm/utli/lookup_table.dart';
 
 typedef CsiSequenceHandler = void Function(CSI, Terminal);
 
-final _csiHandlers = <int, CsiSequenceHandler>{
+final _csiHandlers = FastLookupTable({
   'c'.codeUnitAt(0): csiSendDeviceAttributesHandler,
   'd'.codeUnitAt(0): csiLinePositionAbsolute,
   'f'.codeUnitAt(0): csiCursorPositionHandler,
@@ -34,18 +36,19 @@ final _csiHandlers = <int, CsiSequenceHandler>{
   'T'.codeUnitAt(0): csiScrollDownHandler,
   'X'.codeUnitAt(0): csiEraseCharactersHandler,
   '@'.codeUnitAt(0): csiInsertBlankCharactersHandler,
-};
+});
 
 class CSI {
   CSI({
     required this.params,
     required this.finalByte,
-    required this.intermediates,
+    // required this.intermediates,
   });
 
-  final List<String> params;
-  final int finalByte;
-  final List<int> intermediates;
+  int? prefix;
+  List<int> params;
+  int finalByte;
+  // final List<int> intermediates;
 
   @override
   String toString() {
@@ -53,22 +56,79 @@ class CSI {
   }
 }
 
+/// Keep a singleton of [CSI] to reduce object allocation. This should only be
+/// modified by [_parseCsi].
+final _csi = CSI(
+  finalByte: 0,
+  params: [],
+);
+
+final _semicolon = ';'.codeUnitAt(0);
+
+/// Parse a CSI from the head of the queue. Return null if the CSI isn't
+/// complete.
 CSI? _parseCsi(Queue<int> queue) {
-  final paramBuffer = StringBuffer();
-  final intermediates = <int>[];
+  _csi.params.clear();
+
+  // Keep track of how many characters should be taken from the queue.
+  var readOffset = 0;
+
+  if (queue.isEmpty) {
+    return null;
+  }
+
+  // ascii  char
+  // 48     '0'
+  // 49     '1'
+  // 50     '2'
+  // 51     '3'
+  // 52     '4'
+  // 53     '5'
+  // 54     '6'
+  // 55     '7'
+  // 56     '8'
+  // 57     '9'
+  // 58     ':'
+  // 59     ';'
+  // 60     '<'
+  // 61     '='
+  // 62     '>'
+  // 63     '?'
+
+  // test whether the csi is a `CSI ? Ps ...` or `CSI Ps ...`
+  final firstChar = queue.first;
+  if (firstChar >= 58 && firstChar <= 63) {
+    _csi.prefix = firstChar;
+    readOffset++;
+  } else {
+    _csi.prefix = null;
+  }
+
+  var param = 0;
+  while (true) {
+    // The sequence isn't completed, just ignore it.
+    if (queue.length <= readOffset) {
+      return null;
+    }
 
-  while (queue.isNotEmpty) {
-    // TODO: handle special case when queue is empty as this time.
+    // final char = queue.removeFirst();
+    final char = queue.elementAt(readOffset++);
 
-    final char = queue.removeFirst();
+    if (char == _semicolon) {
+      _csi.params.add(param);
+      param = 0;
+      continue;
+    }
 
-    if (char >= 0x30 && char <= 0x3F) {
-      paramBuffer.writeCharCode(char);
+    // '0' <= char <= '9'
+    if (char >= 48 && char <= 57) {
+      param *= 10;
+      param += char - 48;
       continue;
     }
 
     if (char > 0 && char <= 0x2F) {
-      intermediates.add(char);
+      // intermediates.add(char);
       continue;
     }
 
@@ -76,33 +136,39 @@ CSI? _parseCsi(Queue<int> queue) {
     const csiMax = 0x7e;
 
     if (char >= csiMin && char <= csiMax) {
-      final params = paramBuffer.toString().split(';');
-      return CSI(
-        params: params,
-        finalByte: char,
-        intermediates: intermediates,
-      );
+      // The sequence is complete. So we consume it from the queue.
+      for (var i = 0; i < readOffset; i++) {
+        queue.removeFirst();
+      }
+
+      // final params = paramBuffer.toString().split(';');
+      _csi.params.add(param);
+      _csi.finalByte = char;
+      return _csi;
     }
   }
 }
 
-void csiHandler(Queue<int> queue, Terminal terminal) {
+/// CSI - Control Sequence Introducer: sequence starting with ESC [ (7bit) or
+/// CSI (\x9B, 8bit)
+bool csiHandler(Queue<int> queue, Terminal terminal) {
   final csi = _parseCsi(queue);
 
   if (csi == null) {
-    return;
+    return false;
   }
 
-  terminal.debug.onCsi(csi);
+  // terminal.debug.onCsi(csi);
 
   final handler = _csiHandlers[csi.finalByte];
 
   if (handler != null) {
     handler(csi, terminal);
-    return;
+  } else {
+    terminal.debug.onError('unknown: $csi');
   }
 
-  terminal.debug.onError('unknown: $csi');
+  return true;
 }
 
 /// DECSED - Selective Erase In Display
@@ -117,22 +183,21 @@ void csiHandler(Queue<int> queue, Terminal terminal) {
 /// P s = 2 → Selective Erase All
 /// ```
 void csiEraseInDisplayHandler(CSI csi, Terminal terminal) {
-  var ps = '0';
+  var ps = 0;
 
   if (csi.params.isNotEmpty) {
     ps = csi.params.first;
   }
 
   switch (ps) {
-    case '':
-    case '0':
+    case 0:
       terminal.buffer.eraseDisplayFromCursor();
       break;
-    case '1':
+    case 1:
       terminal.buffer.eraseDisplayToCursor();
       break;
-    case '2':
-    case '3':
+    case 2:
+    case 3:
       terminal.buffer.eraseDisplay();
       break;
     default:
@@ -141,21 +206,20 @@ void csiEraseInDisplayHandler(CSI csi, Terminal terminal) {
 }
 
 void csiEraseInLineHandler(CSI csi, Terminal terminal) {
-  var ps = '0';
+  var ps = 0;
 
   if (csi.params.isNotEmpty) {
     ps = csi.params.first;
   }
 
   switch (ps) {
-    case '':
-    case '0':
+    case 0:
       terminal.buffer.eraseLineFromCursor();
       break;
-    case '1':
+    case 1:
       terminal.buffer.eraseLineToCursor();
       break;
-    case '2':
+    case 2:
       terminal.buffer.eraseLine();
       break;
     default:
@@ -169,8 +233,8 @@ void csiCursorPositionHandler(CSI csi, Terminal terminal) {
   var y = 1;
 
   if (csi.params.length == 2) {
-    y = int.tryParse(csi.params[0]) ?? x;
-    x = int.tryParse(csi.params[1]) ?? y;
+    y = csi.params[0];
+    x = csi.params[1];
   }
 
   terminal.buffer.setPosition(x - 1, y - 1);
@@ -180,7 +244,7 @@ void csiLinePositionAbsolute(CSI csi, Terminal terminal) {
   var row = 1;
 
   if (csi.params.isNotEmpty) {
-    row = int.tryParse(csi.params.first) ?? row;
+    row = csi.params.first;
   }
 
   terminal.buffer.setCursorY(row - 1);
@@ -190,7 +254,7 @@ void csiCursorHorizontalAbsoluteHandler(CSI csi, Terminal terminal) {
   var x = 1;
 
   if (csi.params.isNotEmpty) {
-    x = int.tryParse(csi.params.first) ?? x;
+    x = csi.params.first;
   }
 
   terminal.buffer.setCursorX(x - 1);
@@ -200,7 +264,7 @@ void csiCursorForwardHandler(CSI csi, Terminal terminal) {
   var offset = 1;
 
   if (csi.params.isNotEmpty) {
-    offset = int.tryParse(csi.params.first) ?? offset;
+    offset = csi.params.first;
   }
 
   terminal.buffer.movePosition(offset, 0);
@@ -210,7 +274,7 @@ void csiCursorBackwardHandler(CSI csi, Terminal terminal) {
   var offset = 1;
 
   if (csi.params.isNotEmpty) {
-    offset = int.tryParse(csi.params.first) ?? offset;
+    offset = csi.params.first;
   }
 
   terminal.buffer.movePosition(-offset, 0);
@@ -220,7 +284,7 @@ void csiEraseCharactersHandler(CSI csi, Terminal terminal) {
   var count = 1;
 
   if (csi.params.isNotEmpty) {
-    count = int.tryParse(csi.params.first) ?? count;
+    count = csi.params.first;
   }
 
   terminal.buffer.eraseCharacters(count);
@@ -235,10 +299,10 @@ void csiDeviceStatusReportHandler(CSI csi, Terminal terminal) {
   if (csi.params.isEmpty) return;
 
   switch (csi.params[0]) {
-    case "5":
+    case 5:
       terminal.onInput("\x1b[0n");
       break;
-    case "6": // report cursor position
+    case 6: // report cursor position
       terminal.onInput("\x1b[${terminal.cursorX + 1};${terminal.cursorY + 1}R");
       break;
     default:
@@ -251,7 +315,7 @@ void csiDeviceStatusReportHandler(CSI csi, Terminal terminal) {
 void csiSendDeviceAttributesHandler(CSI csi, Terminal terminal) {
   var response = '?1;2';
 
-  if (csi.params.isNotEmpty && csi.params.first.startsWith('>')) {
+  if (csi.prefix == 62 /* '>' */) {
     response = '>0;0;0';
   }
 
@@ -262,7 +326,7 @@ void csiCursorUpHandler(CSI csi, Terminal terminal) {
   var distance = 1;
 
   if (csi.params.isNotEmpty) {
-    distance = int.tryParse(csi.params.first) ?? distance;
+    distance = csi.params.first;
   }
 
   terminal.buffer.movePosition(0, -distance);
@@ -272,7 +336,7 @@ void csiCursorDownHandler(CSI csi, Terminal terminal) {
   var distance = 1;
 
   if (csi.params.isNotEmpty) {
-    distance = int.tryParse(csi.params.first) ?? distance;
+    distance = csi.params.first;
   }
 
   terminal.buffer.movePosition(0, distance);
@@ -298,10 +362,10 @@ void csiSetMarginsHandler(CSI csi, Terminal terminal) {
   }
 
   if (csi.params.isNotEmpty) {
-    top = int.tryParse(csi.params[0]) ?? top;
+    top = csi.params[0];
 
     if (csi.params.length > 1) {
-      bottom = int.tryParse(csi.params[1]) ?? bottom;
+      bottom = csi.params[1];
     }
   }
 
@@ -313,7 +377,7 @@ void csiDeleteHandler(CSI csi, Terminal terminal) {
   var count = 1;
 
   if (csi.params.isNotEmpty) {
-    count = int.tryParse(csi.params.first) ?? count;
+    count = csi.params.first;
   }
 
   if (count < 1) {
@@ -335,7 +399,7 @@ void csiCursorNextLineHandler(CSI csi, Terminal terminal) {
   var count = 1;
 
   if (csi.params.isNotEmpty) {
-    count = int.tryParse(csi.params.first) ?? count;
+    count = csi.params.first;
   }
 
   if (count < 1) {
@@ -350,7 +414,7 @@ void csiCursorPrecedingLineHandler(CSI csi, Terminal terminal) {
   var count = 1;
 
   if (csi.params.isNotEmpty) {
-    count = int.tryParse(csi.params.first) ?? count;
+    count = csi.params.first;
   }
 
   if (count < 1) {
@@ -365,7 +429,7 @@ void csiInsertLinesHandler(CSI csi, Terminal terminal) {
   var count = 1;
 
   if (csi.params.isNotEmpty) {
-    count = int.tryParse(csi.params.first) ?? count;
+    count = csi.params.first;
   }
 
   if (count < 1) {
@@ -379,7 +443,7 @@ void csiDeleteLinesHandler(CSI csi, Terminal terminal) {
   var count = 1;
 
   if (csi.params.isNotEmpty) {
-    count = int.tryParse(csi.params.first) ?? count;
+    count = csi.params.first;
   }
 
   if (count < 1) {
@@ -393,7 +457,7 @@ void csiScrollUpHandler(CSI csi, Terminal terminal) {
   var count = 1;
 
   if (csi.params.isNotEmpty) {
-    count = int.tryParse(csi.params.first) ?? count;
+    count = csi.params.first;
   }
 
   if (count < 1) {
@@ -407,7 +471,7 @@ void csiScrollDownHandler(CSI csi, Terminal terminal) {
   var count = 1;
 
   if (csi.params.isNotEmpty) {
-    count = int.tryParse(csi.params.first) ?? count;
+    count = csi.params.first;
   }
 
   if (count < 1) {
@@ -421,7 +485,7 @@ void csiInsertBlankCharactersHandler(CSI csi, Terminal terminal) {
   var count = 1;
 
   if (csi.params.isNotEmpty) {
-    count = int.tryParse(csi.params.first) ?? count;
+    count = csi.params.first;
   }
 
   if (count < 1) {

+ 12 - 0
lib/terminal/cursor.dart

@@ -0,0 +1,12 @@
+/// Keeps the default style of newly created cells.
+class Cursor {
+  Cursor({
+    required this.fg,
+    required this.bg,
+    required this.flags,
+  });
+
+  int fg;
+  int bg;
+  int flags;
+}

+ 36 - 31
lib/terminal/modes.dart

@@ -26,38 +26,43 @@ void csiSetModes(CSI csi, Terminal terminal) {
 
   final enabled = _isEnabled(csi.finalByte);
 
-  if (csi.params.length == 1) {
-    return csiSetMode(csi.params.first, enabled, terminal);
-  }
-
-  const decPrefix = '?';
-  final isDec = csi.params.first.startsWith(decPrefix);
+  const decPrefix = 63; // '?'
+  final isDec = csi.prefix == decPrefix;
 
   for (var mode in csi.params) {
-    if (isDec && !mode.startsWith(decPrefix)) {
-      mode = decPrefix + mode;
+    if (isDec) {
+      csiDecSetMode(mode, enabled, terminal);
+    } else {
+      csiSetMode(mode, enabled, terminal);
     }
-    csiSetMode(csi.params.first, enabled, terminal);
   }
 }
 
-void csiSetMode(String mode, bool enabled, Terminal terminal) {
+void csiSetMode(int mode, bool enabled, Terminal terminal) {
   switch (mode) {
-    case "4":
+    case 4:
       if (enabled) {
         terminal.setInsertMode();
       } else {
         terminal.setReplaceMode();
       }
       break;
-    case "20":
+    case 20:
       if (enabled) {
         terminal.setNewLineMode();
       } else {
         terminal.setLineFeedMode();
       }
       break;
-    case "?1":
+    default:
+      terminal.debug.onError('unsupported mode: $mode');
+      return;
+  }
+}
+
+void csiDecSetMode(int mode, bool enabled, Terminal terminal) {
+  switch (mode) {
+    case 1:
       terminal.setApplicationCursorKeys(enabled);
       break;
     // case "?3":
@@ -71,42 +76,42 @@ void csiSetMode(String mode, bool enabled, Terminal terminal) {
     // 	terminal.clear();
     // case "?4":
     // 	// DECSCLM
-    case "?5":
+    case 5:
       // DECSCNM
       terminal.setScreenMode(enabled);
       break;
-    case "?6":
+    case 6:
       // DECOM
       terminal.setOriginMode(enabled);
       break;
-    case "?7":
+    case 7:
       //DECAWM
       terminal.setAutoWrapMode(enabled);
       break;
-    case "?9":
+    case 9:
       if (enabled) {
         // terminal.setMouseMode(MouseMode.x10);
       } else {
         terminal.setMouseMode(MouseMode.none);
       }
       break;
-    case "?12":
-    case "?13":
+    case 12:
+    case 13:
       terminal.setBlinkingCursor(enabled);
       break;
-    case "?25":
+    case 25:
       terminal.setShowCursor(enabled);
       break;
-    case "?47":
-    case "?1047":
+    case 47:
+    case 1047:
       if (enabled) {
         terminal.useAltBuffer();
       } else {
         terminal.useMainBuffer();
       }
       break;
-    case "?1000":
-    case "?10061000":
+    case 1000:
+    case 10061000:
       // enable mouse tracking
       // 1000 refers to ext mode for extended mouse click area - otherwise only x <= 255-31
       if (enabled) {
@@ -115,7 +120,7 @@ void csiSetMode(String mode, bool enabled, Terminal terminal) {
         terminal.setMouseMode(MouseMode.none);
       }
       break;
-    case "?1002":
+    case 1002:
       // enable mouse tracking
       // 1000 refers to ext mode for extended mouse click area - otherwise only x <= 255-31
       if (enabled) {
@@ -124,35 +129,35 @@ void csiSetMode(String mode, bool enabled, Terminal terminal) {
         terminal.setMouseMode(MouseMode.none);
       }
       break;
-    case "?1003":
+    case 1003:
       if (enabled) {
         // terminal.setMouseMode(MouseMode.anyEvent);
       } else {
         terminal.setMouseMode(MouseMode.none);
       }
       break;
-    case "?1005":
+    case 1005:
       if (enabled) {
         // terminal.setMouseExtMode(MouseExt.utf);
       } else {
         // terminal.setMouseExtMode(MouseExt.none);
       }
       break;
-    case "?1006":
+    case 1006:
       if (enabled) {
         // terminal.setMouseExtMode(MouseExt.sgr);
       } else {
         // terminal.setMouseExtMode(MouseExt.none);
       }
       break;
-    case "?1048":
+    case 1048:
       if (enabled) {
         terminal.buffer.saveCursor();
       } else {
         terminal.buffer.restoreCursor();
       }
       break;
-    case "?1049":
+    case 1049:
       if (enabled) {
         terminal.useAltBuffer();
         terminal.buffer.clear();
@@ -160,7 +165,7 @@ void csiSetMode(String mode, bool enabled, Terminal terminal) {
         terminal.useMainBuffer();
       }
       break;
-    case "?2004":
+    case 2004:
       terminal.setBracketedPasteMode(enabled);
       break;
     default:

+ 32 - 6
lib/terminal/osc.dart

@@ -8,12 +8,24 @@ import 'package:xterm/terminal/terminal.dart';
 //   return terminator.contains(codePoint);
 // }
 
-List<String> _parseOsc(Queue<int> queue, Set<int> terminators) {
+List<String>? _parseOsc(Queue<int> queue, Set<int> terminators) {
+  // TODO: add tests for cases such as incomplete sequence.
+
   final params = <String>[];
   final param = StringBuffer();
 
-  while (queue.isNotEmpty) {
-    final char = queue.removeFirst();
+  // Keep track of how many characters should be taken from the queue.
+  var readOffset = 0;
+
+  while (true) {
+    // The sequence isn't completed, just ignore it.
+    if (queue.length <= readOffset) {
+      return null;
+    }
+
+    final char = queue.elementAt(readOffset++);
+
+    // final char = queue.removeFirst();
 
     if (terminators.contains(char)) {
       params.add(param.toString());
@@ -30,20 +42,32 @@ List<String> _parseOsc(Queue<int> queue, Set<int> terminators) {
     param.writeCharCode(char);
   }
 
+  // The sequence is complete. So we consume it from the queue.
+  for (var i = 0; i < readOffset; i++) {
+    queue.removeFirst();
+  }
+
   return params;
 }
 
-void oscHandler(Queue<int> queue, Terminal terminal) {
+/// OSC - Operating System Command: sequence starting with ESC ] (7bit) or OSC
+/// (\x9D, 8bit)
+bool oscHandler(Queue<int> queue, Terminal terminal) {
   final params = _parseOsc(queue, terminal.platform.oscTerminators);
+
+  if (params == null) {
+    return false;
+  }
+
   terminal.debug.onOsc(params);
 
   if (params.isEmpty) {
     terminal.debug.onError('osc with no params');
-    return;
+    return true;
   }
 
   if (params.length < 2) {
-    return;
+    return true;
   }
 
   final ps = params[0];
@@ -60,4 +84,6 @@ void oscHandler(Queue<int> queue, Terminal terminal) {
     default:
       terminal.debug.onError('unknown osc ps: $ps');
   }
+
+  return true;
 }

+ 133 - 154
lib/terminal/sgr.dart

@@ -1,15 +1,8 @@
+import 'package:xterm/buffer/cell_flags.dart';
 import 'package:xterm/theme/terminal_color.dart';
 import 'package:xterm/terminal/csi.dart';
 import 'package:xterm/terminal/terminal.dart';
-
-// reference to color
-class Cr implements TerminalColor {
-  Cr(this.getter);
-
-  final TerminalColor Function() getter;
-
-  int get value => getter().value;
-}
+import 'package:xterm/utli/lookup_table.dart';
 
 /// SGR selects one or more character attributes at the same time.
 /// Multiple params (up to 32) are applied from in order from left to right.
@@ -20,178 +13,171 @@ void sgrHandler(CSI csi, Terminal terminal) {
   final params = csi.params.toList();
 
   if (params.isEmpty) {
-    params.add('0');
+    params.add(0);
   }
 
   for (var i = 0; i < params.length; i++) {
     final param = params[i];
     switch (param) {
-      case '':
-      case '0':
-      case '00':
-        terminal.cellAttr.reset();
+      case 0:
+        terminal.cursor.fg = terminal.theme.foreground;
+        terminal.cursor.bg = TerminalColor.transparent;
+        terminal.cursor.flags = 0x00;
         break;
-      case '1':
-      case '01':
-        terminal.cellAttr.bold = true;
+      case 1:
+        terminal.cursor.fg |= CellFlags.bold;
         break;
-      case '2':
-      case '02':
-        terminal.cellAttr.faint = true;
+      case 2:
+        terminal.cursor.fg |= CellFlags.faint;
         break;
-      case '3':
-      case '03':
-        terminal.cellAttr.italic = true;
+      case 3:
+        terminal.cursor.fg |= CellFlags.italic;
         break;
-      case '4':
-      case '04':
-        terminal.cellAttr.underline = true;
+      case 4:
+        terminal.cursor.fg |= CellFlags.underline;
         break;
-      case '5':
-      case '05':
-        terminal.cellAttr.blink = true;
+      case 5:
+        terminal.cursor.fg |= CellFlags.blink;
         break;
-      case '7':
-      case '07':
-        terminal.cellAttr.inverse = true;
+      case 7:
+        terminal.cursor.fg |= CellFlags.inverse;
         break;
-      case '8':
-      case '08':
-        terminal.cellAttr.invisible = true;
+      case 8:
+        terminal.cursor.fg |= CellFlags.invisible;
         break;
-      case '21':
-        terminal.cellAttr.bold = false;
+      case 21:
+        terminal.cursor.fg &= ~CellFlags.bold;
         break;
-      case '22':
-        terminal.cellAttr.faint = false;
+      case 22:
+        terminal.cursor.fg &= ~CellFlags.faint;
         break;
-      case '23':
-        terminal.cellAttr.italic = false;
+      case 23:
+        terminal.cursor.fg &= ~CellFlags.italic;
         break;
-      case '24':
-        terminal.cellAttr.underline = false;
+      case 24:
+        terminal.cursor.fg &= ~CellFlags.underline;
         break;
-      case '25':
-        terminal.cellAttr.blink = false;
+      case 25:
+        terminal.cursor.fg &= ~CellFlags.blink;
         break;
-      case '27':
-        terminal.cellAttr.inverse = false;
+      case 27:
+        terminal.cursor.fg &= ~CellFlags.inverse;
         break;
-      case '28':
-        terminal.cellAttr.invisible = false;
+      case 28:
+        terminal.cursor.fg &= ~CellFlags.invisible;
         break;
-      case '29':
+      case 29:
         // not strikethrough
         break;
-      case '39':
-        terminal.cellAttr.fgColor = Cr(() => terminal.theme.foreground);
+      case 39:
+        terminal.cursor.fg = terminal.theme.foreground;
         break;
-      case '30':
-        terminal.cellAttr.fgColor = Cr(() => terminal.theme.black);
+      case 30:
+        terminal.cursor.fg = terminal.theme.black;
         break;
-      case '31':
-        terminal.cellAttr.fgColor = Cr(() => terminal.theme.red);
+      case 31:
+        terminal.cursor.fg = terminal.theme.red;
         break;
-      case '32':
-        terminal.cellAttr.fgColor = Cr(() => terminal.theme.green);
+      case 32:
+        terminal.cursor.fg = terminal.theme.green;
         break;
-      case '33':
-        terminal.cellAttr.fgColor = Cr(() => terminal.theme.yellow);
+      case 33:
+        terminal.cursor.fg = terminal.theme.yellow;
         break;
-      case '34':
-        terminal.cellAttr.fgColor = Cr(() => terminal.theme.blue);
+      case 34:
+        terminal.cursor.fg = terminal.theme.blue;
         break;
-      case '35':
-        terminal.cellAttr.fgColor = Cr(() => terminal.theme.magenta);
+      case 35:
+        terminal.cursor.fg = terminal.theme.magenta;
         break;
-      case '36':
-        terminal.cellAttr.fgColor = Cr(() => terminal.theme.cyan);
+      case 36:
+        terminal.cursor.fg = terminal.theme.cyan;
         break;
-      case '37':
-        terminal.cellAttr.fgColor = Cr(() => terminal.theme.white);
+      case 37:
+        terminal.cursor.fg = terminal.theme.white;
         break;
-      case '90':
-        terminal.cellAttr.fgColor = Cr(() => terminal.theme.brightBlack);
+      case 90:
+        terminal.cursor.fg = terminal.theme.brightBlack;
         break;
-      case '91':
-        terminal.cellAttr.fgColor = Cr(() => terminal.theme.brightRed);
+      case 91:
+        terminal.cursor.fg = terminal.theme.brightRed;
         break;
-      case '92':
-        terminal.cellAttr.fgColor = Cr(() => terminal.theme.brightGreen);
+      case 92:
+        terminal.cursor.fg = terminal.theme.brightGreen;
         break;
-      case '93':
-        terminal.cellAttr.fgColor = Cr(() => terminal.theme.brightYellow);
+      case 93:
+        terminal.cursor.fg = terminal.theme.brightYellow;
         break;
-      case '94':
-        terminal.cellAttr.fgColor = Cr(() => terminal.theme.brightBlue);
+      case 94:
+        terminal.cursor.fg = terminal.theme.brightBlue;
         break;
-      case '95':
-        terminal.cellAttr.fgColor = Cr(() => terminal.theme.brightMagenta);
+      case 95:
+        terminal.cursor.fg = terminal.theme.brightMagenta;
         break;
-      case '96':
-        terminal.cellAttr.fgColor = Cr(() => terminal.theme.brightCyan);
+      case 96:
+        terminal.cursor.fg = terminal.theme.brightCyan;
         break;
-      case '97':
-        terminal.cellAttr.fgColor = Cr(() => terminal.theme.brightWhite);
+      case 97:
+        terminal.cursor.fg = terminal.theme.brightWhite;
         break;
-      case '49':
-        terminal.cellAttr.bgColor = Cr(() => terminal.theme.background);
+      case 49:
+        terminal.cursor.bg = terminal.theme.background;
         break;
-      case '40':
-        terminal.cellAttr.bgColor = Cr(() => terminal.theme.black);
+      case 40:
+        terminal.cursor.bg = terminal.theme.black;
         break;
-      case '41':
-        terminal.cellAttr.bgColor = Cr(() => terminal.theme.red);
+      case 41:
+        terminal.cursor.bg = terminal.theme.red;
         break;
-      case '42':
-        terminal.cellAttr.bgColor = Cr(() => terminal.theme.green);
+      case 42:
+        terminal.cursor.bg = terminal.theme.green;
         break;
-      case '43':
-        terminal.cellAttr.bgColor = Cr(() => terminal.theme.yellow);
+      case 43:
+        terminal.cursor.bg = terminal.theme.yellow;
         break;
-      case '44':
-        terminal.cellAttr.bgColor = Cr(() => terminal.theme.blue);
+      case 44:
+        terminal.cursor.bg = terminal.theme.blue;
         break;
-      case '45':
-        terminal.cellAttr.bgColor = Cr(() => terminal.theme.magenta);
+      case 45:
+        terminal.cursor.bg = terminal.theme.magenta;
         break;
-      case '46':
-        terminal.cellAttr.bgColor = Cr(() => terminal.theme.cyan);
+      case 46:
+        terminal.cursor.bg = terminal.theme.cyan;
         break;
-      case '47':
-        terminal.cellAttr.bgColor = Cr(() => terminal.theme.white);
+      case 47:
+        terminal.cursor.bg = terminal.theme.white;
         break;
-      case '100':
-        terminal.cellAttr.bgColor = Cr(() => terminal.theme.brightBlack);
+      case 100:
+        terminal.cursor.bg = terminal.theme.brightBlack;
         break;
-      case '101':
-        terminal.cellAttr.bgColor = Cr(() => terminal.theme.brightRed);
+      case 101:
+        terminal.cursor.bg = terminal.theme.brightRed;
         break;
-      case '102':
-        terminal.cellAttr.bgColor = Cr(() => terminal.theme.brightGreen);
+      case 102:
+        terminal.cursor.bg = terminal.theme.brightGreen;
         break;
-      case '103':
-        terminal.cellAttr.bgColor = Cr(() => terminal.theme.brightYellow);
+      case 103:
+        terminal.cursor.bg = terminal.theme.brightYellow;
         break;
-      case '104':
-        terminal.cellAttr.bgColor = Cr(() => terminal.theme.brightBlue);
+      case 104:
+        terminal.cursor.bg = terminal.theme.brightBlue;
         break;
-      case '105':
-        terminal.cellAttr.bgColor = Cr(() => terminal.theme.brightMagenta);
+      case 105:
+        terminal.cursor.bg = terminal.theme.brightMagenta;
         break;
-      case '106':
-        terminal.cellAttr.bgColor = Cr(() => terminal.theme.brightCyan);
+      case 106:
+        terminal.cursor.bg = terminal.theme.brightCyan;
         break;
-      case '107':
-        terminal.cellAttr.bgColor = Cr(() => terminal.theme.brightWhite);
+      case 107:
+        terminal.cursor.bg = terminal.theme.brightWhite;
         break;
-      case '38': // set foreground
-        final color = parseAnsiColour(params.sublist(i), terminal);
-        terminal.cellAttr.fgColor = color;
+      case 38: // set foreground
+        final color = parseAnsiColour(params, i, terminal);
+        terminal.cursor.fg = color;
         return;
-      case '48': // set background
-        final color = parseAnsiColour(params.sublist(i), terminal);
-        terminal.cellAttr.bgColor = color;
+      case 48: // set background
+        final color = parseAnsiColour(params, i, terminal);
+        terminal.cursor.bg = color;
         return;
       default:
         terminal.debug.onError('unknown SGR: $param');
@@ -199,47 +185,40 @@ void sgrHandler(CSI csi, Terminal terminal) {
   }
 }
 
-TerminalColor parseAnsiColour(List<String> params, Terminal terminal) {
-  if (params.length > 2) {
-    switch (params[1]) {
-      case "5":
+/// parse a color from [params] starting from [offset].
+int parseAnsiColour(List<int> params, int offset, Terminal terminal) {
+  final length = params.length - offset;
+
+  if (length > 2) {
+    switch (params[offset + 1]) {
+      case 5:
         // 8 bit colour
-        final colNum = int.tryParse(params[2]);
+        final colNum = params[offset + 2];
 
-        if (colNum == null || colNum >= 256 || colNum < 0) {
+        if (colNum >= 256 || colNum < 0) {
           return TerminalColor.empty();
         }
 
         return parse8BitSgrColour(colNum, terminal);
 
-      case "2":
-        if (params.length < 4) {
+      case 2:
+        if (length < 4) {
           return TerminalColor.empty();
         }
 
         // 24 bit colour
-        if (params.length == 5) {
-          final r = int.tryParse(params[2]);
-          final g = int.tryParse(params[3]);
-          final b = int.tryParse(params[4]);
-
-          if (r == null || g == null || b == null) {
-            return TerminalColor.empty();
-          }
-
+        if (length == 5) {
+          final r = params[offset + 2];
+          final g = params[offset + 3];
+          final b = params[offset + 4];
           return TerminalColor.fromARGB(0xff, r, g, b);
         }
 
-        if (params.length > 5) {
+        if (length > 5) {
           // ISO/IEC International Standard 8613-6
-          final r = int.tryParse(params[3]);
-          final g = int.tryParse(params[4]);
-          final b = int.tryParse(params[5]);
-
-          if (r == null || g == null || b == null) {
-            return TerminalColor.empty();
-          }
-
+          final r = params[offset + 3];
+          final g = params[offset + 4];
+          final b = params[offset + 5];
           return TerminalColor.fromARGB(0xff, r, g, b);
         }
     }
@@ -248,7 +227,7 @@ TerminalColor parseAnsiColour(List<String> params, Terminal terminal) {
   return TerminalColor.empty();
 }
 
-const grayscaleColors = {
+final grayscaleColors = FastLookupTable({
   232: 0xff080808,
   233: 0xff121212,
   234: 0xff1c1c1c,
@@ -273,9 +252,9 @@ const grayscaleColors = {
   253: 0xffdadada,
   254: 0xffe4e4e4,
   255: 0xffeeeeee,
-};
+});
 
-TerminalColor parse8BitSgrColour(int colNum, Terminal terminal) {
+int parse8BitSgrColour(int colNum, Terminal terminal) {
   // https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
 
   switch (colNum) {
@@ -347,5 +326,5 @@ TerminalColor parse8BitSgrColour(int colNum, Terminal terminal) {
     return TerminalColor.fromARGB(0xff, r, g, b);
   }
 
-  return TerminalColor(grayscaleColors[colNum.clamp(232, 255)]!);
+  return grayscaleColors[colNum.clamp(232, 255)]!;
 }

+ 39 - 24
lib/terminal/terminal.dart

@@ -3,7 +3,6 @@ import 'dart:math' show max, min;
 
 import 'package:xterm/buffer/buffer.dart';
 import 'package:xterm/buffer/buffer_line.dart';
-import 'package:xterm/buffer/cell_attr.dart';
 import 'package:xterm/mouse/selection.dart';
 import 'package:xterm/input/keys.dart';
 import 'package:xterm/input/keytab/keytab.dart';
@@ -11,9 +10,11 @@ import 'package:xterm/input/keytab/keytab_escape.dart';
 import 'package:xterm/input/keytab/keytab_record.dart';
 import 'package:xterm/mouse/mouse_mode.dart';
 import 'package:xterm/terminal/ansi.dart';
+import 'package:xterm/terminal/cursor.dart';
 import 'package:xterm/terminal/platform.dart';
 import 'package:xterm/terminal/sbc.dart';
 import 'package:xterm/terminal/tabs.dart';
+import 'package:xterm/theme/terminal_color.dart';
 import 'package:xterm/theme/terminal_theme.dart';
 import 'package:xterm/theme/terminal_themes.dart';
 import 'package:xterm/utli/debug_handler.dart';
@@ -45,6 +46,12 @@ class Terminal with Observable {
     _altBuffer = Buffer(this);
     _buffer = _mainBuffer;
 
+    cursor = Cursor(
+      fg: theme.foreground,
+      bg: TerminalColor.transparent, // transparent
+      flags: 0x00, // no flags
+    );
+
     tabs.reset();
   }
 
@@ -168,7 +175,9 @@ class Terminal with Observable {
   MouseMode get mouseMode => _mouseMode;
 
   final TerminalTheme theme;
-  final cellAttr = CellAttrTemplate();
+
+  // final cellAttr = CellAttrTemplate();
+  late final Cursor cursor;
 
   final keytab = Keytab.defaultKeytab();
   final selection = Selection();
@@ -199,10 +208,7 @@ class Terminal with Observable {
     refresh();
   }
 
-  /// Writes data to the terminal. Special characters are interpreted. To write
-  /// terminal sequences, [Terminal.write] should be used instead. Writing a
-  /// terminal sequence by calling [Terminal.writeChar] multiple times is not
-  /// supported.
+  /// Writes data to the terminal. Special characters are interpreted.
   ///
   /// See also: [Buffer.writeChar]
   void writeChar(int codePoint) {
@@ -224,8 +230,16 @@ class Terminal with Observable {
       const esc = 0x1b;
       final char = _queue.removeFirst();
 
-      if (char == esc && _queue.isNotEmpty) {
-        ansiHandler(_queue, this);
+      if (char == esc) {
+        final finished = ansiHandler(_queue, this);
+
+        // Terminal sequence in the queue is not completed, and no charater is
+        // consumed.
+        if (!finished) {
+          _queue.addFirst(esc);
+          break;
+        }
+
         continue;
       }
 
@@ -325,17 +339,18 @@ class Terminal with Observable {
   }
 
   /// Resize the terminal screen. [newWidth] and [newHeight] should be greater
-  /// than 0.
+  /// than 0. Text reflow is currently not implemented and will be avaliable in
+  /// the future.
   void resize(int newWidth, int newHeight) {
-    final oldWidth = _viewWidth;
-    final oldHeight = _viewHeight;
+    newWidth = max(newWidth, 1);
+    newHeight = max(newHeight, 1);
 
-    _viewWidth = max(newWidth, 1);
-    _viewHeight = max(newHeight, 1);
+    buffer.resize(newWidth, newHeight);
 
-    _altBuffer.resize(_viewWidth, _viewHeight, oldWidth, oldHeight, false);
-    _mainBuffer.resize(_viewWidth, _viewHeight, oldWidth, oldHeight,
-        true /* might be a setting for the terminal */);
+    // maybe reflow should happen here.
+
+    _viewWidth = newWidth;
+    _viewHeight = newHeight;
 
     if (buffer == _altBuffer) {
       buffer.clearScrollback();
@@ -453,19 +468,17 @@ class Terminal with Observable {
       }
 
       for (var col = xStart; col <= xEnd; col++) {
-        if (col >= line.length) {
-          break;
-        }
+        // if (col >= line.length) {
+        //   break;
+        // }
 
-        final cell = line.getCell(col);
-
-        if (cell.width == 0) {
+        if (line.cellGetWidth(col) == 0) {
           continue;
         }
 
-        var char = line.getCell(col).codePoint;
+        var char = line.cellGetContent(col);
 
-        if (char == null || char == 0x00) {
+        if (char == 0x00) {
           const blank = 32;
           char = blank;
         }
@@ -485,6 +498,8 @@ class Terminal with Observable {
     onInput(data);
   }
 
+  void selectWord(int x, int y) {}
+
   int get _tabIndexFromCursor {
     var index = buffer.cursorX;
 

+ 11 - 10
lib/theme/terminal_color.dart

@@ -1,14 +1,15 @@
 class TerminalColor {
-  const TerminalColor(this.value);
+  static int empty() {
+    return 0xFF000000;
+  }
 
-  const TerminalColor.empty() : value = 0xFF000000;
+  static int transparent = 0x00000000;
 
-  const TerminalColor.fromARGB(int a, int r, int g, int b)
-      : value = (((a & 0xff) << 24) |
-                ((r & 0xff) << 16) |
-                ((g & 0xff) << 8) |
-                ((b & 0xff) << 0)) &
-            0xFFFFFFFF;
-
-  final int value;
+  static int fromARGB(int a, int r, int g, int b) {
+    return (((a & 0xff) << 24) |
+            ((r & 0xff) << 16) |
+            ((g & 0xff) << 8) |
+            ((b & 0xff) << 0)) &
+        0xFFFFFFFF;
+  }
 }

+ 0 - 9
lib/theme/terminal_color_ref.dart

@@ -1,9 +0,0 @@
-import 'package:xterm/theme/terminal_color.dart';
-
-class TerminalColorRef implements TerminalColor {
-  TerminalColorRef(this.getter);
-
-  final TerminalColor Function() getter;
-
-  int get value => getter().value;
-}

+ 20 - 22
lib/theme/terminal_theme.dart

@@ -1,5 +1,3 @@
-import 'package:xterm/theme/terminal_color.dart';
-
 class TerminalTheme {
   const TerminalTheme({
     required this.cursor,
@@ -24,26 +22,26 @@ class TerminalTheme {
     required this.brightWhite,
   });
 
-  final TerminalColor cursor;
-  final TerminalColor selection;
+  final int cursor;
+  final int selection;
 
-  final TerminalColor foreground;
-  final TerminalColor background;
-  final TerminalColor black;
-  final TerminalColor red;
-  final TerminalColor green;
-  final TerminalColor yellow;
-  final TerminalColor blue;
-  final TerminalColor magenta;
-  final TerminalColor cyan;
-  final TerminalColor white;
+  final int foreground;
+  final int background;
+  final int black;
+  final int red;
+  final int green;
+  final int yellow;
+  final int blue;
+  final int magenta;
+  final int cyan;
+  final int white;
 
-  final TerminalColor brightBlack;
-  final TerminalColor brightRed;
-  final TerminalColor brightGreen;
-  final TerminalColor brightYellow;
-  final TerminalColor brightBlue;
-  final TerminalColor brightMagenta;
-  final TerminalColor brightCyan;
-  final TerminalColor brightWhite;
+  final int brightBlack;
+  final int brightRed;
+  final int brightGreen;
+  final int brightYellow;
+  final int brightBlue;
+  final int brightMagenta;
+  final int brightCyan;
+  final int brightWhite;
 }

+ 40 - 41
lib/theme/terminal_themes.dart

@@ -1,50 +1,49 @@
-import 'package:xterm/theme/terminal_color.dart';
 import 'package:xterm/theme/terminal_theme.dart';
 
 class TerminalThemes {
   static const defaultTheme = TerminalTheme(
-    cursor: TerminalColor(0xffaeafad),
-    selection: TerminalColor(0xffffff40),
-    foreground: TerminalColor(0xffcccccc),
-    background: TerminalColor(0xff1e1e1e),
-    black: TerminalColor(0xff000000),
-    red: TerminalColor(0xffcd3131),
-    green: TerminalColor(0xff0dbc79),
-    yellow: TerminalColor(0xffe5e510),
-    blue: TerminalColor(0xff2472c8),
-    magenta: TerminalColor(0xffbc3fbc),
-    cyan: TerminalColor(0xff11a8cd),
-    white: TerminalColor(0xffe5e5e5),
-    brightBlack: TerminalColor(0xff666666),
-    brightRed: TerminalColor(0xfff14c4c),
-    brightGreen: TerminalColor(0xff23d18b),
-    brightYellow: TerminalColor(0xfff5f543),
-    brightBlue: TerminalColor(0xff3b8eea),
-    brightMagenta: TerminalColor(0xffd670d6),
-    brightCyan: TerminalColor(0xff29b8db),
-    brightWhite: TerminalColor(0xffffffff),
+    cursor: 0XFFAEAFAD,
+    selection: 0XFFFFFF40,
+    foreground: 0XFFCCCCCC,
+    background: 0XFF1E1E1E,
+    black: 0XFF000000,
+    red: 0XFFCD3131,
+    green: 0XFF0DBC79,
+    yellow: 0XFFE5E510,
+    blue: 0XFF2472C8,
+    magenta: 0XFFBC3FBC,
+    cyan: 0XFF11A8CD,
+    white: 0XFFE5E5E5,
+    brightBlack: 0XFF666666,
+    brightRed: 0XFFF14C4C,
+    brightGreen: 0XFF23D18B,
+    brightYellow: 0XFFF5F543,
+    brightBlue: 0XFF3B8EEA,
+    brightMagenta: 0XFFD670D6,
+    brightCyan: 0XFF29B8DB,
+    brightWhite: 0XFFFFFFFF,
   );
 
   static const whiteOnBlack = TerminalTheme(
-    cursor: TerminalColor(0xffaeafad),
-    selection: TerminalColor(0xffffff40),
-    foreground: TerminalColor(0xffffffff),
-    background: TerminalColor(0xff000000),
-    black: TerminalColor(0xff000000),
-    red: TerminalColor(0xffcd3131),
-    green: TerminalColor(0xff0dbc79),
-    yellow: TerminalColor(0xffe5e510),
-    blue: TerminalColor(0xff2472c8),
-    magenta: TerminalColor(0xffbc3fbc),
-    cyan: TerminalColor(0xff11a8cd),
-    white: TerminalColor(0xffe5e5e5),
-    brightBlack: TerminalColor(0xff666666),
-    brightRed: TerminalColor(0xfff14c4c),
-    brightGreen: TerminalColor(0xff23d18b),
-    brightYellow: TerminalColor(0xfff5f543),
-    brightBlue: TerminalColor(0xff3b8eea),
-    brightMagenta: TerminalColor(0xffd670d6),
-    brightCyan: TerminalColor(0xff29b8db),
-    brightWhite: TerminalColor(0xffffffff),
+    cursor: 0XFFAEAFAD,
+    selection: 0XFFFFFF40,
+    foreground: 0XFFFFFFFF,
+    background: 0XFF000000,
+    black: 0XFF000000,
+    red: 0XFFCD3131,
+    green: 0XFF0DBC79,
+    yellow: 0XFFE5E510,
+    blue: 0XFF2472C8,
+    magenta: 0XFFBC3FBC,
+    cyan: 0XFF11A8CD,
+    white: 0XFFE5E5E5,
+    brightBlack: 0XFF666666,
+    brightRed: 0XFFF14C4C,
+    brightGreen: 0XFF23D18B,
+    brightYellow: 0XFFF5F543,
+    brightBlue: 0XFF3B8EEA,
+    brightMagenta: 0XFFD670D6,
+    brightCyan: 0XFF29B8DB,
+    brightWhite: 0XFFFFFFFF,
   );
 }

+ 5 - 0
lib/utli/bit_flags.dart

@@ -0,0 +1,5 @@
+extension BitFlags on int {
+  bool hasFlag(int flag) {
+    return this & flag != 0;
+  }
+}

+ 32 - 0
lib/utli/lookup_table.dart

@@ -0,0 +1,32 @@
+/// Fixed-size list based lookup table, optimized for small positive integer
+/// keys.
+class FastLookupTable<T> {
+  FastLookupTable(Map<int, T> data) {
+    var maxIndex = data.keys.first;
+
+    for (var key in data.keys) {
+      if (key > maxIndex) {
+        maxIndex = key;
+      }
+    }
+
+    this._maxIndex = maxIndex;
+
+    _table = List<T?>.filled(maxIndex + 1, null);
+
+    for (var entry in data.entries) {
+      _table[entry.key] = entry.value;
+    }
+  }
+
+  late final List<T?> _table;
+  late final int _maxIndex;
+
+  T? operator [](int index) {
+    if (index > _maxIndex) {
+      return null;
+    }
+
+    return _table[index];
+  }
+}

+ 26 - 3
script/benchmark.dart

@@ -1,9 +1,12 @@
+import 'dart:io';
+
 import 'package:xterm/xterm.dart';
 
 void main() {
-  BenchmarkWrite().run();
-  BenchmarkWrite2().run();
-  BenchmarkWriteBuffer().run();
+  // BenchmarkWrite().run();
+  // BenchmarkWrite2().run();
+  // BenchmarkWriteBuffer().run();
+  BenchmarkWriteCMatrix().run();
 }
 
 abstract class Benchmark {
@@ -55,6 +58,26 @@ class BenchmarkWrite2 extends Benchmark {
   }
 }
 
+class BenchmarkWriteCMatrix extends Benchmark {
+  BenchmarkWriteCMatrix() {
+    data = File('script/cmatrix.txt').readAsStringSync();
+  }
+
+  static const cycle = 10;
+  late final String data;
+
+  String explain() {
+    return 'write ${data.length / 1024} kb CMatrix -r output to Terminal for $cycle time(s)';
+  }
+
+  void benchmark() {
+    final terminal = Terminal();
+    for (var i = 0; i < cycle; i++) {
+      terminal.write(data);
+    }
+  }
+}
+
 class BenchmarkWriteBuffer extends Benchmark {
   static const cycle = 1 << 20;
   static const data = 'hello world';

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
script/cmatrix.txt


Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor