Kaynağa Gözat

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

devmil 4 yıl önce
ebeveyn
işleme
4cde3e808c

+ 1 - 1
.github/workflows/ci.yml

@@ -19,7 +19,7 @@ jobs:
 
       - uses: subosito/flutter-action@v1
         with:
-          flutter-version: '2.0.4'
+          flutter-version: '2.2.x'
 
       - name: Install dependencies
         run: flutter pub get

+ 9 - 0
CHANGELOG.md

@@ -1,3 +1,12 @@
+## [2.3.1-pre] - 2021-6-1
+* Export `theme/terminal_style.dart`
+
+## [2.3.0-pre] - 2021-6-1
+* Add `import 'package:xterm/isolate.dart';`
+
+## [2.2.1-pre] - 2021-6-1
+* Make BufferLine work on web.
+
 ## [2.2.0-pre] - 2021-4-12
 
 ## [2.1.0-pre] - 2021-3-20

BIN
example/fonts/CascadiaMonoPL.ttf


+ 115 - 0
example/lib/isolate.dart

@@ -0,0 +1,115 @@
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:xterm/flutter.dart';
+import 'package:xterm/xterm.dart';
+import 'package:xterm/isolate.dart';
+
+void main() {
+  runApp(MyApp());
+}
+
+class MyApp extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      title: 'xterm.dart demo',
+      theme: ThemeData(
+        primarySwatch: Colors.blue,
+        visualDensity: VisualDensity.adaptivePlatformDensity,
+      ),
+      home: MyHomePage(),
+    );
+  }
+}
+
+class MyHomePage extends StatefulWidget {
+  MyHomePage({Key key}) : super(key: key);
+
+  @override
+  _MyHomePageState createState() => _MyHomePageState();
+}
+
+class FakeTerminalBackend implements TerminalBackend {
+  Completer<int> _exitCodeCompleter;
+  // ignore: close_sinks
+  StreamController<String> _outStream;
+
+  FakeTerminalBackend();
+
+  @override
+  Future<int> get exitCode => _exitCodeCompleter.future;
+
+  @override
+  void init() {
+    _exitCodeCompleter = Completer<int>();
+    _outStream = StreamController<String>();
+    _outStream.sink.add('xterm.dart demo');
+    _outStream.sink.add('\r\n');
+    _outStream.sink.add('\$ ');
+  }
+
+  @override
+  Stream<String> get out => _outStream.stream;
+
+  @override
+  void resize(int width, int height) {
+    // NOOP
+  }
+
+  @override
+  void write(String input) {
+    if (input.length <= 0) {
+      return;
+    }
+    // in a "real" terminal emulation you would connect onInput to the backend
+    // (like a pty or ssh connection) that then handles the changes in the
+    // terminal.
+    // As we don't have a connected backend here we simulate the changes by
+    // directly writing to the terminal.
+    if (input == '\r') {
+      _outStream.sink.add('\r\n');
+      _outStream.sink.add('\$ ');
+    } else if (input.codeUnitAt(0) == 127) {
+      // Backspace handling
+      _outStream.sink.add('\b \b');
+    } else {
+      _outStream.sink.add(input);
+    }
+  }
+
+  @override
+  void terminate() {
+    //NOOP
+  }
+
+  @override
+  void ackProcessed() {
+    //NOOP
+  }
+}
+
+class _MyHomePageState extends State<MyHomePage> {
+  TerminalIsolate terminal;
+
+  @override
+  void initState() {
+    super.initState();
+    terminal = TerminalIsolate(
+      backend: FakeTerminalBackend(),
+      maxLines: 10000,
+    );
+    terminal.start();
+  }
+
+  void onInput(String input) {}
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      body: SafeArea(
+        child: TerminalView(terminal: terminal),
+      ),
+    );
+  }
+}

+ 6 - 4
example/lib/main.dart

@@ -89,16 +89,15 @@ class FakeTerminalBackend implements TerminalBackend {
 }
 
 class _MyHomePageState extends State<MyHomePage> {
-  TerminalIsolate terminal;
+  Terminal terminal;
 
   @override
   void initState() {
     super.initState();
-    terminal = TerminalIsolate(
+    terminal = Terminal(
       backend: FakeTerminalBackend(),
       maxLines: 10000,
     );
-    terminal.start();
   }
 
   void onInput(String input) {}
@@ -107,7 +106,10 @@ class _MyHomePageState extends State<MyHomePage> {
   Widget build(BuildContext context) {
     return Scaffold(
       body: SafeArea(
-        child: TerminalView(terminal: terminal),
+        child: TerminalView(
+          terminal: terminal,
+          style: TerminalStyle(fontFamily: ['Cascadia Mono']),
+        ),
       ),
     );
   }

+ 4 - 4
example/pubspec.lock

@@ -14,7 +14,7 @@ packages:
       name: async
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.5.0"
+    version: "2.6.1"
   boolean_selector:
     dependency: transitive
     description:
@@ -171,7 +171,7 @@ packages:
       name: source_span
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.8.0"
+    version: "1.8.1"
   stack_trace:
     dependency: transitive
     description:
@@ -206,7 +206,7 @@ packages:
       name: test_api
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.2.19"
+    version: "0.3.0"
   tweetnacl:
     dependency: transitive
     description:
@@ -243,7 +243,7 @@ packages:
       path: ".."
       relative: true
     source: path
-    version: "2.0.0"
+    version: "2.3.0-pre"
 sdks:
   dart: ">=2.12.0 <3.0.0"
   flutter: ">=2.0.0"

+ 4 - 0
example/pubspec.yaml

@@ -81,3 +81,7 @@ flutter:
   #
   # For details regarding fonts from package dependencies,
   # see https://flutter.dev/custom-fonts/#from-packages
+  fonts:
+    - family: Cascadia Mono
+      fonts:
+        - asset: fonts/CascadiaMonoPL.ttf

+ 1 - 1
lib/buffer/buffer.dart

@@ -1,6 +1,6 @@
 import 'dart:math' show max, min;
 
-import 'package:xterm/buffer/buffer_line.dart';
+import 'package:xterm/buffer/line/line.dart';
 import 'package:xterm/buffer/reflow_strategy_narrower.dart';
 import 'package:xterm/buffer/reflow_strategy_wider.dart';
 import 'package:xterm/terminal/charset.dart';

+ 98 - 0
lib/buffer/line/line.dart

@@ -0,0 +1,98 @@
+import 'dart:math';
+
+import 'package:xterm/buffer/line/line_bytedata.dart';
+import 'package:xterm/buffer/line/line_list.dart';
+import 'package:xterm/terminal/cursor.dart';
+import 'package:xterm/util/constants.dart';
+
+abstract class BufferLine {
+  factory BufferLine({int length = 64, bool isWrapped = false}) {
+    if (kIsWeb) {
+      return ListBufferLine(length, isWrapped);
+    }
+
+    return ByteDataBufferLine(length, isWrapped);
+  }
+
+  bool get isWrapped;
+
+  set isWrapped(bool value);
+
+  void ensure(int length);
+
+  void insert(int index);
+
+  void insertN(int index, int count);
+
+  void removeN(int index, int count);
+
+  void clear();
+
+  void erase(Cursor cursor, int start, int end, [bool resetIsWrapped = false]);
+
+  void cellClear(int index);
+
+  void cellInitialize(
+    int index, {
+    required int content,
+    required int width,
+    required Cursor cursor,
+  });
+
+  bool cellHasContent(int index);
+
+  int cellGetContent(int index);
+
+  void cellSetContent(int index, int content);
+
+  int cellGetFgColor(int index);
+
+  void cellSetFgColor(int index, int color);
+
+  int cellGetBgColor(int index);
+
+  void cellSetBgColor(int index, int color);
+
+  int cellGetFlags(int index);
+
+  void cellSetFlags(int index, int flags);
+
+  int cellGetWidth(int index);
+
+  void cellSetWidth(int index, int width);
+
+  void cellClearFlags(int index);
+
+  bool cellHasFlag(int index, int flag);
+
+  void cellSetFlag(int index, int flag);
+
+  void cellErase(int index, Cursor cursor);
+
+  int getTrimmedLength([int? cols]);
+
+  void copyCellsFrom(covariant BufferLine src, int srcCol, int dstCol, int len);
+
+  // int cellGetHash(int index);
+
+  void removeRange(int start, int end);
+
+  void clearRange(int start, int end);
+
+  String toDebugString(int cols) {
+    final result = StringBuffer();
+    final length = getTrimmedLength();
+    for (int i = 0; i < max(cols, length); i++) {
+      var code = cellGetContent(i);
+      if (code == 0) {
+        if (cellGetWidth(i) == 0) {
+          code = '_'.runes.first;
+        } else {
+          code = cellGetWidth(i).toString().runes.first;
+        }
+      }
+      result.writeCharCode(code);
+    }
+    return result.toString();
+  }
+}

+ 6 - 20
lib/buffer/buffer_line.dart → lib/buffer/line/line_bytedata.dart

@@ -1,6 +1,7 @@
 import 'dart:math';
 import 'dart:typed_data';
 
+import 'package:xterm/buffer/line/line.dart';
 import 'package:xterm/terminal/cursor.dart';
 
 /// Line layout:
@@ -34,8 +35,10 @@ int _nextLength(int lengthRequirement) {
   return nextLength;
 }
 
-class BufferLine {
-  BufferLine({int length = 64, this.isWrapped = false}) {
+/// [ByteData] based [BufferLine], used in non-web platforms to minimize memory
+/// footprint,
+class ByteDataBufferLine with BufferLine {
+  ByteDataBufferLine(int length, this.isWrapped) {
     _maxCols = _nextLength(length);
     _cells = ByteData(_maxCols * _cellSize);
   }
@@ -241,7 +244,7 @@ class BufferLine {
     return 0;
   }
 
-  void copyCellsFrom(BufferLine src, int srcCol, int dstCol, int len) {
+  void copyCellsFrom(ByteDataBufferLine src, int srcCol, int dstCol, int len) {
     ensure(dstCol + len);
 
     final intsToCopy = len * _cellSize64Bit;
@@ -286,21 +289,4 @@ class BufferLine {
     }
     return result.toString();
   }
-
-  String toDebugString(int cols) {
-    final result = StringBuffer();
-    final length = getTrimmedLength();
-    for (int i = 0; i < max(cols, length); i++) {
-      var code = cellGetContent(i);
-      if (code == 0) {
-        if (cellGetWidth(i) == 0) {
-          code = '_'.runes.first;
-        } else {
-          code = cellGetWidth(i).toString().runes.first;
-        }
-      }
-      result.writeCharCode(code);
-    }
-    return result.toString();
-  }
 }

+ 267 - 0
lib/buffer/line/line_list.dart

@@ -0,0 +1,267 @@
+import 'dart:math';
+
+import 'package:xterm/buffer/line/line.dart';
+import 'package:xterm/terminal/cursor.dart';
+
+/// Line layout:
+///   |  cell  |  cell  |  cell  |  cell  | ...
+///   (4 ints per cell)
+///
+/// Cell layout:
+///   | code point |  fg color  |  bg color  | attributes |
+///       1 int        1 int        1 int        1 int
+///
+/// Attributes layout:
+///   |  width  |  flags  | reserved | reserved |
+///      1byte     1byte     1byte      1byte
+
+const _cellSize = 4;
+
+const _cellContent = 0;
+const _cellFgColor = 1;
+const _cellBgColor = 2;
+const _cellAttributes = 3;
+
+const _cellWidth = 0;
+const _cellFlags = 8;
+
+int _nextLength(int lengthRequirement) {
+  var nextLength = 2;
+  while (nextLength < lengthRequirement) {
+    nextLength *= 2;
+  }
+  return nextLength;
+}
+
+/// [List] based [BufferLine], used in browser where ByteData is not avaliable.
+class ListBufferLine with BufferLine {
+  ListBufferLine(int length, this.isWrapped) {
+    _maxCols = _nextLength(length);
+    _cells = List.filled(_maxCols * _cellSize, 0);
+  }
+
+  late List<int> _cells;
+
+  bool isWrapped;
+
+  int _maxCols = 64;
+
+  void ensure(int length) {
+    if (length <= _maxCols) {
+      return;
+    }
+
+    final nextLength = _nextLength(length);
+    final newCells = List.filled(nextLength * _cellSize, 0);
+    newCells.setAll(0, _cells);
+    _cells = newCells;
+    _maxCols = nextLength;
+  }
+
+  void insert(int index) {
+    insertN(index, 1);
+  }
+
+  void removeN(int index, int count) {
+    final moveStart = index * _cellSize;
+    final moveOffset = count * _cellSize;
+    final moveEnd = (_maxCols - count) * _cellSize;
+    final bufferEnd = _maxCols * _cellSize;
+
+    // move data backward
+    for (var i = moveStart; i < moveEnd; i++) {
+      _cells[i] = _cells[i + moveOffset];
+    }
+
+    // set empty cells to 0
+    for (var i = moveEnd; i < bufferEnd; i++) {
+      _cells[i] = 0x00;
+    }
+  }
+
+  void insertN(int index, int count) {
+    final moveStart = index * _cellSize;
+    final moveOffset = count * _cellSize;
+    final bufferEnd = _maxCols * _cellSize;
+
+    // move data forward
+    for (var i = bufferEnd - moveOffset - 1; i >= moveStart; i--) {
+      _cells[i + moveOffset] = _cells[i];
+    }
+
+    // set inserted cells to 0
+    for (var i = moveStart; i < moveStart + moveOffset; i++) {
+      _cells[i] = 0x00;
+    }
+  }
+
+  void clear() {
+    clearRange(0, _cells.length ~/ _cellSize);
+  }
+
+  void erase(Cursor cursor, int start, int end, [bool resetIsWrapped = false]) {
+    ensure(end);
+    for (var i = start; i < end; i++) {
+      cellErase(i, cursor);
+    }
+    if (resetIsWrapped) {
+      isWrapped = false;
+    }
+  }
+
+  void cellClear(int index) {
+    _cells.fillRange(index * _cellSize, index * _cellSize + _cellSize, 0);
+  }
+
+  void cellInitialize(
+    int index, {
+    required int content,
+    required int width,
+    required Cursor cursor,
+  }) {
+    final cell = index * _cellSize;
+    _cells[cell + _cellContent] = content;
+    _cells[cell + _cellFgColor] = cursor.fg;
+    _cells[cell + _cellBgColor] = cursor.bg;
+    _cells[cell + _cellAttributes] =
+        (width << _cellWidth) + (cursor.flags << _cellFlags);
+  }
+
+  bool cellHasContent(int index) {
+    return cellGetContent(index) != 0;
+  }
+
+  int cellGetContent(int index) {
+    if (index >= _maxCols) return 0;
+    return _cells[index * _cellSize + _cellContent];
+  }
+
+  void cellSetContent(int index, int content) {
+    _cells[index * _cellSize + _cellContent] = content;
+  }
+
+  int cellGetFgColor(int index) {
+    if (index >= _maxCols) return 0;
+    return _cells[index * _cellSize + _cellFgColor];
+  }
+
+  void cellSetFgColor(int index, int color) {
+    _cells[index * _cellSize + _cellFgColor] = color;
+  }
+
+  int cellGetBgColor(int index) {
+    if (index >= _maxCols) return 0;
+    return _cells[index * _cellSize + _cellBgColor];
+  }
+
+  void cellSetBgColor(int index, int color) {
+    _cells[index * _cellSize + _cellBgColor] = color;
+  }
+
+  int cellGetFlags(int index) {
+    if (index >= _maxCols) return 0;
+    final offset = index * _cellSize + _cellAttributes;
+    return (_cells[offset] >> _cellFlags) & 0xFF;
+  }
+
+  void cellSetFlags(int index, int flags) {
+    final offset = index * _cellSize + _cellAttributes;
+    var result = _cells[offset];
+    result |= 0xFF << _cellFlags;
+    result &= flags << _cellFlags;
+    _cells[offset] = result;
+  }
+
+  int cellGetWidth(int index) {
+    if (index >= _maxCols) return 0;
+    final offset = index * _cellSize + _cellAttributes;
+    return (_cells[offset] >> _cellWidth) & 0xFF;
+  }
+
+  void cellSetWidth(int index, int width) {
+    final offset = index * _cellSize + _cellAttributes;
+    var result = _cells[offset];
+    result |= 0xFF << _cellWidth;
+    result &= width << _cellWidth;
+    _cells[offset] = result;
+  }
+
+  void cellClearFlags(int index) {
+    cellSetFlags(index, 0);
+  }
+
+  bool cellHasFlag(int index, int flag) {
+    if (index >= _maxCols) {
+      return false;
+    }
+    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);
+    cellSetWidth(index, 0);
+  }
+
+  int getTrimmedLength([int? cols]) {
+    if (cols == null) {
+      cols = _maxCols;
+    }
+    for (var i = cols - 1; i >= 0; i--) {
+      if (cellGetContent(i) != 0) {
+        // we are at the last cell in this line that has content.
+        // the length of this line is the index of this cell + 1
+        // the only exception is that if that last cell is wider
+        // than 1 then we have to add the diff
+        final lastCellWidth = cellGetWidth(i);
+        return i + lastCellWidth;
+      }
+    }
+    return 0;
+  }
+
+  void copyCellsFrom(ListBufferLine src, int srcCol, int dstCol, int len) {
+    ensure(dstCol + len);
+
+    final intsToCopy = len * _cellSize;
+    final srcStart = srcCol * _cellSize;
+    final dstStart = dstCol * _cellSize;
+
+    final cells = _cells;
+    final srcCells = src._cells;
+    for (var i = 0; i < intsToCopy; i++) {
+      cells[dstStart + i] = srcCells[srcStart + i];
+    }
+  }
+
+  void removeRange(int start, int end) {
+    end = min(end, _maxCols);
+    this.removeN(start, end - start);
+  }
+
+  void clearRange(int start, int end) {
+    end = min(end, _maxCols);
+    for (var index = start; index < end; index++) {
+      cellClear(index);
+    }
+  }
+
+  @override
+  String toString() {
+    final result = StringBuffer();
+    for (int i = 0; i < _maxCols; i++) {
+      final code = cellGetContent(i);
+      if (code == 0) {
+        continue;
+      }
+      result.writeCharCode(code);
+    }
+    return result.toString();
+  }
+}

+ 1 - 1
lib/buffer/reflow_strategy.dart

@@ -1,5 +1,5 @@
 import 'package:xterm/buffer/buffer.dart';
-import 'package:xterm/buffer/buffer_line.dart';
+import 'package:xterm/buffer/line/line.dart';
 import 'package:xterm/util/circular_list.dart';
 
 abstract class ReflowStrategy {

+ 1 - 1
lib/buffer/reflow_strategy_narrower.dart

@@ -1,7 +1,7 @@
 import 'dart:math';
 
 import 'package:xterm/buffer/buffer.dart';
-import 'package:xterm/buffer/buffer_line.dart';
+import 'package:xterm/buffer/line/line.dart';
 import 'package:xterm/buffer/reflow_strategy.dart';
 
 class ReflowStrategyNarrower extends ReflowStrategy {

+ 1 - 1
lib/buffer/reflow_strategy_wider.dart

@@ -1,7 +1,7 @@
 import 'dart:math';
 
 import 'package:xterm/buffer/buffer.dart';
-import 'package:xterm/buffer/buffer_line.dart';
+import 'package:xterm/buffer/line/line.dart';
 import 'package:xterm/buffer/reflow_strategy.dart';
 
 class ReflowStrategyWider extends ReflowStrategy {

+ 37 - 37
lib/frontend/input_map.dart

@@ -8,15 +8,15 @@ final _idKeyMap = {
   LogicalKeyboardKey.fnLock.keyId: TerminalKey.fnLock,
   LogicalKeyboardKey.suspend.keyId: TerminalKey.suspend,
   LogicalKeyboardKey.resume.keyId: TerminalKey.resume,
-  LogicalKeyboardKey.turbo.keyId: TerminalKey.turbo,
-  LogicalKeyboardKey.privacyScreenToggle.keyId: TerminalKey.privacyScreenToggle,
+  // LogicalKeyboardKey.turbo.keyId: TerminalKey.turbo,
+  // LogicalKeyboardKey.privacyScreenToggle.keyId: TerminalKey.privacyScreenToggle,
   LogicalKeyboardKey.sleep.keyId: TerminalKey.sleep,
   LogicalKeyboardKey.wakeUp.keyId: TerminalKey.wakeUp,
-  LogicalKeyboardKey.displayToggleIntExt.keyId: TerminalKey.displayToggleIntExt,
-  LogicalKeyboardKey.usbReserved.keyId: TerminalKey.usbReserved,
-  LogicalKeyboardKey.usbErrorRollOver.keyId: TerminalKey.usbErrorRollOver,
-  LogicalKeyboardKey.usbPostFail.keyId: TerminalKey.usbPostFail,
-  LogicalKeyboardKey.usbErrorUndefined.keyId: TerminalKey.usbErrorUndefined,
+  // LogicalKeyboardKey.displayToggleIntExt.keyId: TerminalKey.displayToggleIntExt,
+  // LogicalKeyboardKey.usbReserved.keyId: TerminalKey.usbReserved,
+  // LogicalKeyboardKey.usbErrorRollOver.keyId: TerminalKey.usbErrorRollOver,
+  // LogicalKeyboardKey.usbPostFail.keyId: TerminalKey.usbPostFail,
+  // LogicalKeyboardKey.usbErrorUndefined.keyId: TerminalKey.usbErrorUndefined,
   LogicalKeyboardKey.keyA.keyId: TerminalKey.keyA,
   LogicalKeyboardKey.keyB.keyId: TerminalKey.keyB,
   LogicalKeyboardKey.keyC.keyId: TerminalKey.keyC,
@@ -155,16 +155,16 @@ final _idKeyMap = {
   LogicalKeyboardKey.props.keyId: TerminalKey.props,
   LogicalKeyboardKey.numpadParenLeft.keyId: TerminalKey.numpadParenLeft,
   LogicalKeyboardKey.numpadParenRight.keyId: TerminalKey.numpadParenRight,
-  LogicalKeyboardKey.numpadBackspace.keyId: TerminalKey.numpadBackspace,
-  LogicalKeyboardKey.numpadMemoryStore.keyId: TerminalKey.numpadMemoryStore,
-  LogicalKeyboardKey.numpadMemoryRecall.keyId: TerminalKey.numpadMemoryRecall,
-  LogicalKeyboardKey.numpadMemoryClear.keyId: TerminalKey.numpadMemoryClear,
-  LogicalKeyboardKey.numpadMemoryAdd.keyId: TerminalKey.numpadMemoryAdd,
-  LogicalKeyboardKey.numpadMemorySubtract.keyId:
-      TerminalKey.numpadMemorySubtract,
-  LogicalKeyboardKey.numpadSignChange.keyId: TerminalKey.numpadSignChange,
-  LogicalKeyboardKey.numpadClear.keyId: TerminalKey.numpadClear,
-  LogicalKeyboardKey.numpadClearEntry.keyId: TerminalKey.numpadClearEntry,
+  // LogicalKeyboardKey.numpadBackspace.keyId: TerminalKey.numpadBackspace,
+  // LogicalKeyboardKey.numpadMemoryStore.keyId: TerminalKey.numpadMemoryStore,
+  // LogicalKeyboardKey.numpadMemoryRecall.keyId: TerminalKey.numpadMemoryRecall,
+  // LogicalKeyboardKey.numpadMemoryClear.keyId: TerminalKey.numpadMemoryClear,
+  // LogicalKeyboardKey.numpadMemoryAdd.keyId: TerminalKey.numpadMemoryAdd,
+  // LogicalKeyboardKey.numpadMemorySubtract.keyId:
+  //     TerminalKey.numpadMemorySubtract,
+  // LogicalKeyboardKey.numpadSignChange.keyId: TerminalKey.numpadSignChange,
+  // LogicalKeyboardKey.numpadClear.keyId: TerminalKey.numpadClear,
+  // LogicalKeyboardKey.numpadClearEntry.keyId: TerminalKey.numpadClearEntry,
   LogicalKeyboardKey.controlLeft.keyId: TerminalKey.controlLeft,
   LogicalKeyboardKey.shiftLeft.keyId: TerminalKey.shiftLeft,
   LogicalKeyboardKey.altLeft.keyId: TerminalKey.altLeft,
@@ -177,13 +177,13 @@ final _idKeyMap = {
   LogicalKeyboardKey.closedCaptionToggle.keyId: TerminalKey.closedCaptionToggle,
   LogicalKeyboardKey.brightnessUp.keyId: TerminalKey.brightnessUp,
   LogicalKeyboardKey.brightnessDown.keyId: TerminalKey.brightnessDown,
-  LogicalKeyboardKey.brightnessToggle.keyId: TerminalKey.brightnessToggle,
-  LogicalKeyboardKey.brightnessMinimum.keyId: TerminalKey.brightnessMinimum,
-  LogicalKeyboardKey.brightnessMaximum.keyId: TerminalKey.brightnessMaximum,
-  LogicalKeyboardKey.brightnessAuto.keyId: TerminalKey.brightnessAuto,
+  // LogicalKeyboardKey.brightnessToggle.keyId: TerminalKey.brightnessToggle,
+  // LogicalKeyboardKey.brightnessMinimum.keyId: TerminalKey.brightnessMinimum,
+  // LogicalKeyboardKey.brightnessMaximum.keyId: TerminalKey.brightnessMaximum,
+  // LogicalKeyboardKey.brightnessAuto.keyId: TerminalKey.brightnessAuto,
   LogicalKeyboardKey.mediaLast.keyId: TerminalKey.mediaLast,
   LogicalKeyboardKey.launchPhone.keyId: TerminalKey.launchPhone,
-  LogicalKeyboardKey.programGuide.keyId: TerminalKey.programGuide,
+  // LogicalKeyboardKey.programGuide.keyId: TerminalKey.programGuide,
   LogicalKeyboardKey.exit.keyId: TerminalKey.exit,
   LogicalKeyboardKey.channelUp.keyId: TerminalKey.channelUp,
   LogicalKeyboardKey.channelDown.keyId: TerminalKey.channelDown,
@@ -198,28 +198,28 @@ final _idKeyMap = {
   LogicalKeyboardKey.eject.keyId: TerminalKey.eject,
   LogicalKeyboardKey.mediaPlayPause.keyId: TerminalKey.mediaPlayPause,
   LogicalKeyboardKey.speechInputToggle.keyId: TerminalKey.speechInputToggle,
-  LogicalKeyboardKey.bassBoost.keyId: TerminalKey.bassBoost,
-  LogicalKeyboardKey.mediaSelect.keyId: TerminalKey.mediaSelect,
+  // LogicalKeyboardKey.bassBoost.keyId: TerminalKey.bassBoost,
+  // LogicalKeyboardKey.mediaSelect.keyId: TerminalKey.mediaSelect,
   LogicalKeyboardKey.launchWordProcessor.keyId: TerminalKey.launchWordProcessor,
   LogicalKeyboardKey.launchSpreadsheet.keyId: TerminalKey.launchSpreadsheet,
   LogicalKeyboardKey.launchMail.keyId: TerminalKey.launchMail,
   LogicalKeyboardKey.launchContacts.keyId: TerminalKey.launchContacts,
   LogicalKeyboardKey.launchCalendar.keyId: TerminalKey.launchCalendar,
-  LogicalKeyboardKey.launchApp2.keyId: TerminalKey.launchApp2,
-  LogicalKeyboardKey.launchApp1.keyId: TerminalKey.launchApp1,
-  LogicalKeyboardKey.launchInternetBrowser.keyId:
-      TerminalKey.launchInternetBrowser,
+  // LogicalKeyboardKey.launchApp2.keyId: TerminalKey.launchApp2,
+  // LogicalKeyboardKey.launchApp1.keyId: TerminalKey.launchApp1,
+  // LogicalKeyboardKey.launchInternetBrowser.keyId:
+  // TerminalKey.launchInternetBrowser,
   LogicalKeyboardKey.logOff.keyId: TerminalKey.logOff,
-  LogicalKeyboardKey.lockScreen.keyId: TerminalKey.lockScreen,
+  // LogicalKeyboardKey.lockScreen.keyId: TerminalKey.lockScreen,
   LogicalKeyboardKey.launchControlPanel.keyId: TerminalKey.launchControlPanel,
-  LogicalKeyboardKey.selectTask.keyId: TerminalKey.selectTask,
-  LogicalKeyboardKey.launchDocuments.keyId: TerminalKey.launchDocuments,
+  // LogicalKeyboardKey.selectTask.keyId: TerminalKey.selectTask,
+  // LogicalKeyboardKey.launchDocuments.keyId: TerminalKey.launchDocuments,
   LogicalKeyboardKey.spellCheck.keyId: TerminalKey.spellCheck,
-  LogicalKeyboardKey.launchKeyboardLayout.keyId:
-      TerminalKey.launchKeyboardLayout,
+  // LogicalKeyboardKey.launchKeyboardLayout.keyId:
+  // TerminalKey.launchKeyboardLayout,
   LogicalKeyboardKey.launchScreenSaver.keyId: TerminalKey.launchScreenSaver,
   LogicalKeyboardKey.launchAssistant.keyId: TerminalKey.launchAssistant,
-  LogicalKeyboardKey.launchAudioBrowser.keyId: TerminalKey.launchAudioBrowser,
+  // LogicalKeyboardKey.launchAudioBrowser.keyId: TerminalKey.launchAudioBrowser,
   LogicalKeyboardKey.newKey.keyId: TerminalKey.newKey,
   LogicalKeyboardKey.close.keyId: TerminalKey.close,
   LogicalKeyboardKey.save.keyId: TerminalKey.save,
@@ -238,9 +238,9 @@ final _idKeyMap = {
   LogicalKeyboardKey.mailReply.keyId: TerminalKey.mailReply,
   LogicalKeyboardKey.mailForward.keyId: TerminalKey.mailForward,
   LogicalKeyboardKey.mailSend.keyId: TerminalKey.mailSend,
-  LogicalKeyboardKey.keyboardLayoutSelect.keyId:
-      TerminalKey.keyboardLayoutSelect,
-  LogicalKeyboardKey.showAllWindows.keyId: TerminalKey.showAllWindows,
+  // LogicalKeyboardKey.keyboardLayoutSelect.keyId:
+  // TerminalKey.keyboardLayoutSelect,
+  // LogicalKeyboardKey.showAllWindows.keyId: TerminalKey.showAllWindows,
   LogicalKeyboardKey.gameButton1.keyId: TerminalKey.gameButton1,
   LogicalKeyboardKey.gameButton2.keyId: TerminalKey.gameButton2,
   LogicalKeyboardKey.gameButton3.keyId: TerminalKey.gameButton3,

+ 1 - 1
lib/frontend/terminal_view.dart

@@ -7,7 +7,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter/rendering.dart';
 import 'package:flutter/scheduler.dart';
 import 'package:flutter/services.dart';
-import 'package:xterm/buffer/buffer_line.dart';
+import 'package:xterm/buffer/line/line.dart';
 import 'package:xterm/buffer/cell_flags.dart';
 import 'package:xterm/frontend/char_size.dart';
 import 'package:xterm/frontend/helpers.dart';

+ 1 - 0
lib/isolate.dart

@@ -0,0 +1 @@
+export 'terminal/terminal_isolate.dart';

+ 1 - 1
lib/terminal/terminal.dart

@@ -3,7 +3,7 @@ import 'dart:collection';
 import 'dart:math' show max, min;
 
 import 'package:xterm/buffer/buffer.dart';
-import 'package:xterm/buffer/buffer_line.dart';
+import 'package:xterm/buffer/line/line.dart';
 import 'package:xterm/mouse/position.dart';
 import 'package:xterm/mouse/selection.dart';
 import 'package:xterm/input/keys.dart';

+ 0 - 2
lib/terminal/terminal_backend.dart

@@ -1,5 +1,3 @@
-import 'package:xterm/terminal/terminal_isolate.dart';
-
 /// interface for every Terminal backend
 abstract class TerminalBackend {
   /// initializes the backend

+ 2 - 2
lib/terminal/terminal_isolate.dart

@@ -1,7 +1,7 @@
 import 'dart:async';
 import 'dart:isolate';
 
-import 'package:xterm/buffer/buffer_line.dart';
+import 'package:xterm/buffer/line/line.dart';
 import 'package:xterm/input/keys.dart';
 import 'package:xterm/mouse/position.dart';
 import 'package:xterm/mouse/selection.dart';
@@ -255,7 +255,7 @@ class TerminalIsolate with Observable implements TerminalUiInteraction {
     this.theme = TerminalThemes.defaultTheme,
     this.minRefreshDelay = const Duration(milliseconds: 16),
     required this.maxLines,
-  })   : _platform = platform,
+  })  : _platform = platform,
         _refreshEventDebouncer = EventDebouncer(minRefreshDelay);
 
   @override

+ 1 - 1
lib/terminal/terminal_ui_interaction.dart

@@ -1,4 +1,4 @@
-import 'package:xterm/buffer/buffer_line.dart';
+import 'package:xterm/buffer/line/line.dart';
 import 'package:xterm/input/keys.dart';
 import 'package:xterm/mouse/position.dart';
 import 'package:xterm/mouse/selection.dart';

+ 13 - 0
lib/util/constants.dart

@@ -0,0 +1,13 @@
+const kReleaseMode = bool.fromEnvironment(
+  'dart.vm.product',
+  defaultValue: false,
+);
+
+const kProfileMode = bool.fromEnvironment(
+  'dart.vm.profile',
+  defaultValue: false,
+);
+
+const kDebugMode = !kReleaseMode && !kProfileMode;
+
+const kIsWeb = identical(0, 0.0);

+ 1 - 1
lib/xterm.dart

@@ -1,7 +1,7 @@
 library xterm;
 
 export 'terminal/terminal.dart';
-export 'terminal/terminal_isolate.dart';
 export 'terminal/terminal_backend.dart';
 export 'terminal/terminal_ui_interaction.dart';
 export 'terminal/platform.dart';
+export 'theme/terminal_style.dart';

+ 3 - 3
pubspec.lock

@@ -7,7 +7,7 @@ packages:
       name: async
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "2.5.0"
+    version: "2.6.1"
   boolean_selector:
     dependency: transitive
     description:
@@ -113,7 +113,7 @@ packages:
       name: source_span
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.8.0"
+    version: "1.8.1"
   stack_trace:
     dependency: transitive
     description:
@@ -148,7 +148,7 @@ packages:
       name: test_api
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.2.19"
+    version: "0.3.0"
   typed_data:
     dependency: transitive
     description:

+ 1 - 1
pubspec.yaml

@@ -1,6 +1,6 @@
 name: xterm
 description: xterm.dart is a fast and fully-featured terminal emulator for Flutter applications, with support for mobile and desktop platforms.
-version: 2.2.0-pre
+version: 2.3.1-pre
 homepage: https://github.com/TerminalStudio/xterm.dart
 
 environment:

+ 251 - 0
test/buffer/buffer_line_test.dart

@@ -0,0 +1,251 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:xterm/buffer/line/line.dart';
+import 'package:xterm/terminal/cursor.dart';
+
+void main() {
+  group("BufferLine Tests", () {
+    test("creation test", () {
+      final line = BufferLine();
+      expect(line, isNotNull);
+    });
+
+    test("set isWrapped", () {
+      final line = BufferLine(isWrapped: false);
+      expect(line.isWrapped, isFalse);
+
+      line.isWrapped = true;
+      expect(line.isWrapped, isTrue);
+
+      line.isWrapped = false;
+      expect(line.isWrapped, isFalse);
+    });
+
+    test("ensure() works", () {
+      final line = BufferLine(length: 10);
+      expect(() => line.cellSetContent(1000, 65), throwsRangeError);
+
+      line.ensure(1000);
+      line.cellSetContent(1000, 65);
+    });
+
+    test("insert() works", () {
+      final line = BufferLine(length: 10);
+      line.cellSetContent(0, 65);
+      line.cellSetContent(1, 66);
+      line.cellSetContent(2, 67);
+
+      line.insert(1);
+
+      final result = [
+        line.cellGetContent(0),
+        line.cellGetContent(1),
+        line.cellGetContent(2),
+        line.cellGetContent(3),
+      ];
+
+      expect(result, equals([65, 0, 66, 67]));
+    });
+
+    test("insertN() works", () {
+      final line = BufferLine(length: 10);
+      line.cellSetContent(0, 65);
+      line.cellSetContent(1, 66);
+      line.cellSetContent(2, 67);
+
+      line.insertN(1, 2);
+
+      final result = [
+        line.cellGetContent(0),
+        line.cellGetContent(1),
+        line.cellGetContent(2),
+        line.cellGetContent(3),
+        line.cellGetContent(4),
+      ];
+
+      expect(result, equals([65, 0, 0, 66, 67]));
+    });
+
+    test("removeN() works", () {
+      final line = BufferLine(length: 10);
+      line.cellSetContent(0, 65);
+      line.cellSetContent(1, 66);
+      line.cellSetContent(2, 67);
+      line.cellSetContent(3, 68);
+      line.cellSetContent(4, 69);
+
+      line.removeN(1, 2);
+
+      final result = [
+        line.cellGetContent(0),
+        line.cellGetContent(1),
+        line.cellGetContent(2),
+        line.cellGetContent(3),
+        line.cellGetContent(4),
+      ];
+
+      expect(result, equals([65, 68, 69, 0, 0]));
+    });
+
+    test("clear() works", () {
+      final line = BufferLine(length: 10);
+      line.cellSetContent(1, 65);
+      line.cellSetContent(2, 66);
+      line.cellSetContent(3, 67);
+      line.cellSetContent(4, 68);
+      line.cellSetContent(5, 69);
+
+      line.clear();
+
+      final result = [
+        line.cellGetContent(1),
+        line.cellGetContent(2),
+        line.cellGetContent(3),
+        line.cellGetContent(4),
+        line.cellGetContent(5),
+      ];
+
+      expect(result, equals([0, 0, 0, 0, 0]));
+    });
+
+    test("cellInitialize() works", () {
+      final line = BufferLine(length: 10);
+      line.cellInitialize(
+        0,
+        content: 0x01,
+        width: 0x02,
+        cursor: Cursor(fg: 0x03, bg: 0x04, flags: 0x05),
+      );
+
+      final result = [
+        line.cellGetContent(0),
+        line.cellGetWidth(0),
+        line.cellGetFgColor(0),
+        line.cellGetBgColor(0),
+        line.cellGetFlags(0),
+      ];
+
+      expect(result, equals([0x01, 0x02, 0x03, 0x04, 0x05]));
+    });
+
+    test("cellHasContent() works", () {
+      final line = BufferLine(length: 10);
+
+      line.cellSetContent(0, 0x01);
+      expect(line.cellHasContent(0), isTrue);
+
+      line.cellSetContent(0, 0x00);
+      expect(line.cellHasContent(0), isFalse);
+    });
+
+    test("cellGetContent() and cellSetContent() works", () {
+      final line = BufferLine(length: 10);
+      final content = 0x01;
+      line.cellSetContent(0, content);
+      expect(line.cellGetContent(0), equals(content));
+    });
+
+    test("cellGetFgColor() and cellSetFgColor() works", () {
+      final line = BufferLine(length: 10);
+      final content = 0x01;
+      line.cellSetFgColor(0, content);
+      expect(line.cellGetFgColor(0), equals(content));
+    });
+
+    test("cellGetBgColor() and cellSetBgColor() works", () {
+      final line = BufferLine(length: 10);
+      final content = 0x01;
+      line.cellSetBgColor(0, content);
+      expect(line.cellGetBgColor(0), equals(content));
+    });
+
+    test("cellHasFlag() and cellSetFlag() works", () {
+      final line = BufferLine(length: 10);
+      final flag = 0x03;
+      line.cellSetFlag(0, flag);
+      expect(line.cellHasFlag(0, flag), isTrue);
+    });
+
+    test("cellGetFlags() and cellSetFlags() works", () {
+      final line = BufferLine(length: 10);
+      final content = 0x01;
+      line.cellSetFlags(0, content);
+      expect(line.cellGetFlags(0), equals(content));
+    });
+
+    test("cellGetWidth() and cellSetWidth() works", () {
+      final line = BufferLine(length: 10);
+      final content = 0x01;
+      line.cellSetWidth(0, content);
+      expect(line.cellGetWidth(0), equals(content));
+    });
+
+    test("getTrimmedLength() works", () {
+      final line = BufferLine(length: 10);
+      expect(line.getTrimmedLength(), equals(0));
+
+      line.cellSetContent(5, 0x01);
+      expect(line.getTrimmedLength(), equals(5));
+
+      line.clear();
+      expect(line.getTrimmedLength(), equals(0));
+    });
+
+    test("copyCellsFrom() works", () {
+      final line1 = BufferLine(length: 10);
+      final line2 = BufferLine(length: 10);
+
+      line1.cellSetContent(0, 123);
+      line1.cellSetContent(1, 124);
+      line1.cellSetContent(2, 125);
+
+      line2.copyCellsFrom(line1, 1, 3, 2);
+
+      expect(line2.cellGetContent(2), equals(0));
+      expect(line2.cellGetContent(3), equals(124));
+      expect(line2.cellGetContent(4), equals(125));
+      expect(line2.cellGetContent(5), equals(0));
+    });
+
+    test("removeRange() works", () {
+      final line = BufferLine(length: 10);
+      line.cellSetContent(0, 65);
+      line.cellSetContent(1, 66);
+      line.cellSetContent(2, 67);
+      line.cellSetContent(3, 68);
+      line.cellSetContent(4, 69);
+
+      line.removeRange(1, 3);
+
+      final result = [
+        line.cellGetContent(0),
+        line.cellGetContent(1),
+        line.cellGetContent(2),
+        line.cellGetContent(3),
+        line.cellGetContent(4),
+      ];
+
+      expect(result, equals([65, 68, 69, 0, 0]));
+    });
+
+    test("clearRange() works", () {
+      final line = BufferLine(length: 10);
+      line.cellSetContent(0, 65);
+      line.cellSetContent(1, 66);
+      line.cellSetContent(2, 67);
+      line.cellSetContent(3, 68);
+      line.cellSetContent(4, 69);
+
+      line.clearRange(1, 3);
+
+      final result = [
+        line.cellGetContent(0),
+        line.cellGetContent(1),
+        line.cellGetContent(2),
+        line.cellGetContent(3),
+        line.cellGetContent(4),
+      ];
+
+      expect(result, equals([65, 0, 0, 68, 69]));
+    });
+  });
+}