Ver Fonte

BufferLine backed by ByteData

xuty há 4 anos atrás
pai
commit
fe8fb8f15b

+ 65 - 69
lib/buffer/buffer.dart

@@ -1,8 +1,6 @@
 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';
@@ -11,7 +9,11 @@ import 'package:xterm/utli/unicode_v11.dart';
 class Buffer {
   Buffer(this.terminal) {
     resetVerticalMargins();
-    lines = List.generate(terminal.viewHeight, (_) => BufferLine());
+
+    lines = List.generate(
+      terminal.viewHeight,
+      (_) => _newEmptyLine(),
+    );
   }
 
   final Terminal terminal;
@@ -23,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.
@@ -83,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++;
@@ -101,16 +105,10 @@ 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;
-    }
-
-    // while (index >= lines.length) {
-    //   final newLine = BufferLine();
-    //   lines.add(newLine);
-    // }
-
+    index = index.clamp(0, terminal.viewHeight - 1);
     return lines[convertViewLineToRawLine(index)];
   }
 
@@ -181,7 +179,7 @@ class Buffer {
     eraseLineFromCursor();
 
     for (var i = _cursorY + 1; i < terminal.viewHeight; i++) {
-      getViewLine(i).erase(terminal.cellAttr.value, 0, terminal.viewWidth);
+      getViewLine(i).erase(terminal.cursor, 0, terminal.viewWidth);
     }
   }
 
@@ -189,38 +187,32 @@ class Buffer {
     eraseLineToCursor();
 
     for (var i = 0; i < _cursorY; i++) {
-      getViewLine(i).erase(terminal.cellAttr.value, 0, terminal.viewWidth);
+      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);
+      line.erase(terminal.cursor, 0, terminal.viewWidth);
     }
   }
 
   void eraseLineFromCursor() {
-    currentLine.erase(terminal.cellAttr.value, _cursorX, terminal.viewWidth);
+    currentLine.erase(terminal.cursor, _cursorX, terminal.viewWidth);
   }
 
   void eraseLineToCursor() {
-    currentLine.erase(terminal.cellAttr.value, 0, _cursorX);
+    currentLine.erase(terminal.cursor, 0, _cursorX);
   }
 
   void eraseLine() {
-    currentLine.erase(terminal.cellAttr.value, 0, terminal.viewWidth);
+    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() {
@@ -240,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();
       }
     }
   }
@@ -252,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();
       }
     }
   }
@@ -277,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) {
@@ -304,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);
   }
@@ -384,15 +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 restoreCursor() {
-    if (_savedCellAttr != null) {
-      terminal.cellAttr.use(_savedCellAttr!);
+    if (_savedCellFlags != null) {
+      terminal.cursor.flags = _savedCellFlags!;
+    }
+
+    if (_savedCellFgColor != null) {
+      terminal.cursor.fg = _savedCellFgColor!;
+    }
+
+    if (_savedCellBgColor != null) {
+      terminal.cursor.bg = _savedCellBgColor!;
     }
 
     if (_savedCursorX != null) {
@@ -429,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);
   }
 
@@ -444,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);
     }
   }
 
@@ -469,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;
@@ -482,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;
     }
   }
@@ -510,11 +494,17 @@ class Buffer {
   }
 
   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(BufferLine());
+          lines.add(_newEmptyLine());
         } else {
           _cursorY++;
         }
@@ -534,4 +524,10 @@ class Buffer {
     _cursorX = _cursorX.clamp(0, newWidth - 1);
     _cursorY = _cursorY.clamp(0, newHeight - 1);
   }
+
+  BufferLine _newEmptyLine() {
+    final line = BufferLine();
+    line.ensure(terminal.viewWidth);
+    return line;
+  }
 }

+ 141 - 26
lib/buffer/buffer_line.dart

@@ -1,48 +1,163 @@
-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;
 
-  bool get isWrapped {
-    return _isWrapped;
-  }
+  void ensure(int length) {
+    final expectedLengthInBytes = length * _cellSize;
+
+    if (expectedLengthInBytes < _cells.lengthInBytes) {
+      return;
+    }
 
-  int get length {
-    return _cells.length;
+    var newLengthInBytes = _cells.lengthInBytes;
+    while (newLengthInBytes < expectedLengthInBytes) {
+      newLengthInBytes *= 2;
+    }
+
+    final newCells = ByteData(newLengthInBytes);
+    newCells.buffer.asInt64List().setAll(0, _cells.buffer.asInt64List());
+    _cells = newCells;
   }
 
-  void add(Cell cell) {
-    _cells.add(cell);
+  void insert(int index) {
+    // _cells.insert(index, cell);
   }
 
-  void insert(int index, Cell cell) {
-    _cells.insert(index, cell);
+  void insertN(int index, int count) {
+    // _cells.insert(index, cell);
   }
 
   void clear() {
-    _cells.clear();
+    // _cells.clear();
   }
 
-  void erase(CellAttr attr, int start, int end) {
+  void erase(Cursor cursor, int start, int end) {
+    ensure(end);
     for (var i = start; i < end; i++) {
-      if (i >= length) {
-        add(Cell(attr: attr));
-      } else {
-        getCell(i).erase(attr);
-      }
+      cellErase(i, cursor);
     }
   }
 
-  Cell getCell(int index) {
-    return _cells[index];
+  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 cellGetContent(int index) {
+    return _cells.getInt32(index * _cellSize + _cellContent);
+  }
+
+  void cellSetContent(int index, int content) {
+    return _cells.setInt32(index * _cellSize + _cellContent, content);
   }
 
-  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);
+  int cellGetFgColor(int index) {
+    return _cells.getInt32(index * _cellSize + _cellFgColor);
+  }
+
+  void cellSetFgColor(int index, int color) {
+    _cells.setInt32(index * _cellSize + _cellFgColor, color);
+  }
+
+  int cellGetBgColor(int index) {
+    return _cells.getInt32(index * _cellSize + _cellBgColor);
+  }
+
+  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);
+  }
+
+  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);
+    }
   }
 }

+ 0 - 36
lib/buffer/cell.dart

@@ -1,36 +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)';
-  }
-}

+ 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?.value,
-          bgColor?.value,
-          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;
-  }
-}

+ 12 - 0
lib/buffer/cell_flags.dart

@@ -0,0 +1,12 @@
+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;
+
+  /// whether the cell required two cells.
+  static const wide = 1 << 8;
+}

+ 86 - 54
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();
@@ -230,8 +235,8 @@ class _TerminalViewState extends State<TerminalView> {
             charSize: _cellSize,
           ),
         ),
-        color: Color(widget.terminal.theme.background.value)
-            .withOpacity(widget.opacity),
+        color:
+            Color(widget.terminal.theme.background).withOpacity(widget.opacity),
       ),
     );
   }
@@ -334,35 +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 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 col = 0; col < cellCount; col++) {
+        final cellWidth = line.cellGetWidth(col);
+        if (cellWidth == 0) {
+          continue;
+        }
 
-      for (var i = 0; i < cellCount; i++) {
-        final cell = line.getCell(i);
-        final attr = cell.attr;
+        final cellFgColor = line.cellGetFgColor(col);
+        final cellBgColor = line.cellGetBgColor(col);
+        final effectBgColor = line.cellHasFlag(col, CellFlags.inverse)
+            ? cellFgColor
+            : cellBgColor;
 
-        if (attr == null || cell.width == 0) {
+        if (effectBgColor == 0x00) {
           continue;
         }
 
-        final offsetX = i * charSize.cellWidth;
-        final effectWidth = charSize.cellWidth * cell.width + 1;
+        // 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 bgColor = attr.inverse
-            ? attr.fgColor ?? terminal.theme.foreground
-            : attr.bgColor;
-
-        if (bgColor == null) {
-          continue;
-        }
 
-        final paint = Paint()..color = Color(bgColor.value);
+        final paint = Paint()..color = Color(effectBgColor);
         canvas.drawRect(
           Rect.fromLTWH(offsetX, offsetY, effectWidth, effectHeight),
           paint,
@@ -372,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) -
@@ -394,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,
@@ -408,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.inverse != 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,
     );
@@ -488,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);
 
     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);
   }

+ 3 - 3
lib/terminal/ansi.dart

@@ -20,9 +20,9 @@ bool ansiHandler(Queue<int> queue, Terminal terminal) {
 
   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) {

+ 95 - 49
lib/terminal/csi.dart

@@ -1,4 +1,5 @@
 import 'dart:collection';
+import 'dart:convert';
 
 import 'package:xterm/terminal/modes.dart';
 import 'package:xterm/terminal/sgr.dart';
@@ -41,12 +42,13 @@ 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() {
@@ -54,15 +56,55 @@ 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) {
@@ -72,13 +114,21 @@ CSI? _parseCsi(Queue<int> queue) {
     // final char = queue.removeFirst();
     final char = queue.elementAt(readOffset++);
 
-    if (char >= 0x30 && char <= 0x3F) {
-      paramBuffer.writeCharCode(char);
+    if (char == _semicolon) {
+      _csi.params.add(param);
+      param = 0;
+      continue;
+    }
+
+    // '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;
     }
 
@@ -91,12 +141,10 @@ CSI? _parseCsi(Queue<int> queue) {
         queue.removeFirst();
       }
 
-      final params = paramBuffer.toString().split(';');
-      return CSI(
-        params: params,
-        finalByte: char,
-        intermediates: intermediates,
-      );
+      // final params = paramBuffer.toString().split(';');
+      _csi.params.add(param);
+      _csi.finalByte = char;
+      return _csi;
     }
   }
 }
@@ -110,7 +158,7 @@ bool csiHandler(Queue<int> queue, Terminal terminal) {
     return false;
   }
 
-  terminal.debug.onCsi(csi);
+  // terminal.debug.onCsi(csi);
 
   final handler = _csiHandlers[csi.finalByte];
 
@@ -135,22 +183,21 @@ bool 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:
@@ -159,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:
@@ -187,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);
@@ -198,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);
@@ -208,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);
@@ -218,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);
@@ -228,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);
@@ -238,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);
@@ -253,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:
@@ -269,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';
   }
 
@@ -280,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);
@@ -290,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);
@@ -316,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];
     }
   }
 
@@ -331,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) {
@@ -353,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) {
@@ -368,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) {
@@ -383,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) {
@@ -397,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) {
@@ -411,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) {
@@ -425,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) {
@@ -439,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:

+ 128 - 143
lib/terminal/sgr.dart

@@ -1,15 +1,17 @@
+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';
+import 'package:xterm/utli/lookup_table.dart';
 
 // reference to color
-class Cr implements TerminalColor {
-  Cr(this.getter);
+// class Cr implements TerminalColor {
+//   Cr(this.getter);
 
-  final TerminalColor Function() getter;
+//   final TerminalColor Function() getter;
 
-  int get value => getter().value;
-}
+//   int get value => getter().value;
+// }
 
 /// 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 +22,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 = 0x00000000;
+        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
+      case 38: // set foreground
         final color = parseAnsiColour(params.sublist(i), terminal);
-        terminal.cellAttr.fgColor = color;
+        terminal.cursor.fg = color;
         return;
-      case '48': // set background
+      case 48: // set background
         final color = parseAnsiColour(params.sublist(i), terminal);
-        terminal.cellAttr.bgColor = color;
+        terminal.cursor.bg = color;
         return;
       default:
         terminal.debug.onError('unknown SGR: $param');
@@ -199,47 +194,37 @@ void sgrHandler(CSI csi, Terminal terminal) {
   }
 }
 
-TerminalColor parseAnsiColour(List<String> params, Terminal terminal) {
+int parseAnsiColour(List<int> params, Terminal terminal) {
   if (params.length > 2) {
     switch (params[1]) {
-      case "5":
+      case 5:
         // 8 bit colour
-        final colNum = int.tryParse(params[2]);
+        final colNum = params[2];
 
-        if (colNum == null || colNum >= 256 || colNum < 0) {
+        if (colNum >= 256 || colNum < 0) {
           return TerminalColor.empty();
         }
 
         return parse8BitSgrColour(colNum, terminal);
 
-      case "2":
+      case 2:
         if (params.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();
-          }
-
+          final r = params[2];
+          final g = params[3];
+          final b = params[4];
           return TerminalColor.fromARGB(0xff, r, g, b);
         }
 
         if (params.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[3];
+          final g = params[4];
+          final b = params[5];
           return TerminalColor.fromARGB(0xff, r, g, b);
         }
     }
@@ -248,7 +233,7 @@ TerminalColor parseAnsiColour(List<String> params, Terminal terminal) {
   return TerminalColor.empty();
 }
 
-const grayscaleColors = {
+final grayscaleColors = FastLookupTable({
   232: 0xff080808,
   233: 0xff121212,
   234: 0xff1c1c1c,
@@ -273,9 +258,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 +332,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)]!;
 }

+ 18 - 10
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,6 +10,7 @@ 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';
@@ -45,6 +45,12 @@ class Terminal with Observable {
     _altBuffer = Buffer(this);
     _buffer = _mainBuffer;
 
+    cursor = Cursor(
+      fg: theme.foreground,
+      bg: 0x00000000, // transparent
+      flags: 0x00, // no flags
+    );
+
     tabs.reset();
   }
 
@@ -168,7 +174,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();
@@ -459,19 +467,17 @@ class Terminal with Observable {
       }
 
       for (var col = xStart; col <= xEnd; col++) {
-        if (col >= line.length) {
-          break;
-        }
-
-        final cell = line.getCell(col);
+        // if (col >= line.length) {
+        //   break;
+        // }
 
-        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;
         }
@@ -491,6 +497,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;
+  }
+}