Explorar o código

Merge branch 'feature/improve_line_manipulation_performance' into feature/reflow_with_line_perf

# Conflicts:
#	lib/buffer/buffer.dart
#	lib/terminal/terminal.dart
devmil %!s(int64=4) %!d(string=hai) anos
pai
achega
12df625928

+ 22 - 37
lib/buffer/buffer.dart

@@ -4,6 +4,7 @@ import 'package:xterm/buffer/buffer_line.dart';
 import 'package:xterm/buffer/buffer_reflow.dart';
 import 'package:xterm/terminal/charset.dart';
 import 'package:xterm/terminal/terminal.dart';
+import 'package:xterm/utli/circular_list.dart';
 import 'package:xterm/utli/scroll_range.dart';
 import 'package:xterm/utli/unicode_v11.dart';
 
@@ -11,10 +12,12 @@ class Buffer {
   Buffer(this.terminal) {
     resetVerticalMargins();
 
-    lines = List.generate(
-      terminal.viewHeight,
-      (_) => _newEmptyLine(),
+    lines = CircularList(
+      terminal.maxLines,
     );
+    for (int i = 0; i < terminal.viewHeight; i++) {
+      lines.push(_newEmptyLine());
+    }
   }
 
   final Terminal terminal;
@@ -22,7 +25,7 @@ class Buffer {
 
   /// lines of the buffer. the length of [lines] should always be equal or
   /// greater than [Terminal.viewHeight].
-  late final List<BufferLine> lines;
+  late final CircularList<BufferLine> lines;
 
   int? _savedCursorX;
   int? _savedCursorY;
@@ -113,7 +116,7 @@ class Buffer {
   /// [Terminal.viewHeight].
   BufferLine getViewLine(int index) {
     index = index.clamp(0, terminal.viewHeight - 1);
-    return lines[convertViewLineToRawLine(index)];
+    return lines[convertViewLineToRawLine(index)]!;
   }
 
   BufferLine get currentLine {
@@ -172,7 +175,7 @@ class Buffer {
     for (var i = height - terminal.viewHeight; i < height; i++) {
       final y = i - scrollOffsetFromBottom;
       if (y >= 0 && y < height) {
-        result.add(lines[y]);
+        result.add(lines[y]!);
       }
     }
 
@@ -273,18 +276,12 @@ 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(_newEmptyLine());
+      lines.push(_newEmptyLine());
 
       // keep viewport from moving if user is scrolling.
       if (isUserScrolling) {
         _scrollOffsetFromBottom++;
       }
-
-      // clean extra lines if needed.
-      final maxLines = terminal.maxLines;
-      if (maxLines != null && lines.length > maxLines) {
-        lines.removeRange(0, lines.length - maxLines);
-      }
     } else {
       // there're still lines so we simply move cursor down.
       moveCursorY(1);
@@ -423,16 +420,14 @@ class Buffer {
       return;
     }
 
-    lines.removeRange(0, lines.length - terminal.viewHeight);
+    lines.trimStart(lines.length - terminal.viewHeight);
   }
 
   void clear() {
     lines.clear();
-
-    lines.addAll(List.generate(
-      terminal.viewHeight,
-      (_) => _newEmptyLine(),
-    ));
+    for (int i = 0; i < terminal.viewHeight; i++) {
+      lines.push(_newEmptyLine());
+    }
   }
 
   void insertBlankCharacters(int count) {
@@ -458,20 +453,10 @@ class Buffer {
     if (!isInScrollableRegion) {
       final index = convertViewLineToRawLine(_cursorX);
       final newLine = _newEmptyLine();
-      lines.insert(index, newLine);
-
-      final maxLines = terminal.maxLines;
-      if (maxLines != null && lines.length > maxLines) {
-        lines.removeRange(0, lines.length - maxLines);
-      }
+      lines.splice(index, 0, [newLine]);
     } else {
-      final bottom = convertViewLineToRawLine(marginBottom);
-
-      final movedLines = lines.getRange(_cursorY, bottom - 1);
-      lines.setRange(_cursorY + 1, bottom, movedLines);
-
       final newLine = _newEmptyLine();
-      lines[_cursorY] = newLine;
+      lines.splice(_cursorY, 0, [newLine]);
     }
   }
 
@@ -494,21 +479,21 @@ class Buffer {
       return;
     }
 
-    lines.removeAt(index);
+    lines.splice(index, 1, []);
   }
 
   void resize(int oldWidth, int oldHeight, int newWidth, int newHeight) {
     if (newWidth > oldWidth) {
-      for (var line in lines) {
-        line.ensure(newWidth);
-      }
+      lines.forEach((item, index) {
+        item?.ensure(newWidth);
+      }, true);
     }
 
     if (newHeight > oldHeight) {
       // Grow larger
       for (var i = 0; i < newHeight - oldHeight; i++) {
         if (_cursorY < oldHeight - 1) {
-          lines.add(_newEmptyLine());
+          lines.push(_newEmptyLine());
         } else {
           _cursorY++;
         }
@@ -517,7 +502,7 @@ class Buffer {
       // Shrink smaller
       for (var i = 0; i < oldHeight - newHeight; i++) {
         if (_cursorY < oldHeight - 1) {
-          lines.removeLast();
+          lines.pop();
         } else {
           _cursorY++;
         }

+ 1 - 1
lib/buffer/buffer_line.dart

@@ -87,7 +87,7 @@ class BufferLine {
   }
 
   void clear() {
-    _cells.buffer.asInt64List().clear();
+    removeRange(0, (_cells.lengthInBytes / _cellSize).floor());
   }
 
   void erase(Cursor cursor, int start, int end, [bool resetIsWrapped = false]) {

+ 22 - 7
lib/frontend/terminal_view.dart

@@ -97,20 +97,32 @@ class _TerminalViewState extends State<TerminalView> {
     return widget.focusNode.hasFocus;
   }
 
-  int? _lastTerminalWidth;
-  int? _lastTerminalHeight;
-
   late CellSize _cellSize;
 
   void onTerminalChange() {
+    if (!mounted) {
+      return;
+    }
+
     final currentScrollExtent =
         _cellSize.cellHeight * widget.terminal.buffer.scrollOffsetFromTop;
 
-    widget.scrollController.jumpTo(currentScrollExtent);
-
-    if (mounted) {
-      setState(() {});
+    final maxScrollExtent = widget.scrollController.position.maxScrollExtent;
+
+    if (currentScrollExtent > maxScrollExtent) {
+      /// Ensure [maxScrollExtent] is larger than [currentScrollExtent] so
+      /// [currentScrollExtent] won't be limited.
+      ///
+      /// Calling [applyContentDimensions] has unnecessary cost, and the most
+      /// ideal way is to set [scrollController.position._maxScrollExtend]
+      /// directly, however this requires modifying flutter code.
+      widget.scrollController.position
+          .applyContentDimensions(0.0, currentScrollExtent);
     }
+
+    widget.scrollController.position.correctPixels(currentScrollExtent);
+
+    setState(() {});
   }
 
   // listen to oscillator to update mouse blink etc.
@@ -253,6 +265,9 @@ class _TerminalViewState extends State<TerminalView> {
     return Position(x, y);
   }
 
+  int? _lastTerminalWidth;
+  int? _lastTerminalHeight;
+
   void onResize(double width, double height) {
     final termWidth = (width / _cellSize.cellWidth).floor();
     final termHeight = (height / _cellSize.cellHeight).floor();

+ 6 - 9
lib/terminal/terminal.dart

@@ -38,10 +38,8 @@ class Terminal with Observable {
     this.onIconChange = _defaultIconHandler,
     this.platform = PlatformBehaviors.unix,
     this.theme = TerminalThemes.defaultTheme,
-    int maxLines = 10000,
-  }) {
-    _maxLines = maxLines;
-
+    required int maxLines,
+  }) : _maxLines = maxLines {
     _mainBuffer = Buffer(this);
     _altBuffer = Buffer(this);
     _buffer = _mainBuffer;
@@ -65,10 +63,9 @@ class Terminal with Observable {
     }
   }
 
-  int? _maxLines;
-  int? get maxLines {
-    if (_maxLines == null) return null;
-    return max(viewHeight, _maxLines!);
+  int _maxLines;
+  int get maxLines {
+    return max(viewHeight, _maxLines);
   }
 
   int _viewWidth = 80;
@@ -454,7 +451,7 @@ class Terminal with Observable {
         break;
       }
 
-      final line = buffer.lines[row];
+      final line = buffer.lines[row]!;
 
       var xStart = 0;
       var xEnd = viewWidth - 1;

+ 156 - 0
lib/utli/circular_list.dart

@@ -0,0 +1,156 @@
+class CircularList<T> {
+  List<T?> _array;
+  int _length = 0;
+  int _startIndex = 0;
+
+  Function(int num)? onTrimmed;
+
+  CircularList(int maxLength) : _array = List<T?>.filled(maxLength, null);
+
+  // Gets the cyclic index for the specified regular index. The cyclic index can then be used on the
+  // backing array to get the element associated with the regular index.
+  int _getCyclicIndex(int index) {
+    return (_startIndex + index) % _array.length;
+  }
+
+  int get maxLength {
+    return _array.length;
+  }
+
+  set maxLength(int value) {
+    if (value <= 0)
+      throw ArgumentError.value(
+          value, 'value', 'maxLength can\'t be negative!');
+
+    if (value == _array.length) return;
+
+    // Reconstruct array, starting at index 0. Only transfer values from the
+    // indexes 0 to length.
+    final newArray = List<T?>.generate(
+        value,
+        (index) =>
+            index < _array.length ? _array[_getCyclicIndex(index)] : null);
+    _startIndex = 0;
+    _array = newArray;
+  }
+
+  int get length {
+    return _length;
+  }
+
+  set length(int value) {
+    if (value > _length) {
+      for (int i = length; i < value; i++) {
+        _array[i] = null;
+      }
+    }
+    _length = value;
+  }
+
+  void forEach(void Function(T? item, int index) callback,
+      [bool includeBuffer = false]) {
+    final len = includeBuffer ? _array.length : _length;
+    for (int i = 0; i < len; i++) {
+      callback(_array[_getCyclicIndex(i)], i);
+    }
+  }
+
+  T? operator [](int index) {
+    return _array[_getCyclicIndex(index)];
+  }
+
+  operator []=(int index, T? value) {
+    _array[_getCyclicIndex(index)] = value;
+  }
+
+  void clear() {
+    _startIndex = 0;
+    _length = 0;
+  }
+
+  void push(T value) {
+    _array[_getCyclicIndex(_length)] = value;
+    if (_length == _array.length) {
+      _startIndex++;
+      if (_startIndex == _array.length) {
+        _startIndex = 0;
+      }
+      onTrimmed?.call(1);
+    } else {
+      _length++;
+    }
+  }
+
+  /// Removes and returns the last value on the list
+  T pop() {
+    return _array[_getCyclicIndex(_length-- - 1)]!;
+  }
+
+  /// Deletes and/or inserts items at a particular index (in that order).
+  void splice(int start, int deleteCount, List<T> items) {
+    // delete items
+    if (deleteCount > 0) {
+      for (int i = start; i < _length - deleteCount; i++)
+        _array[_getCyclicIndex(i)] = _array[_getCyclicIndex(i + deleteCount)];
+      length -= deleteCount;
+    }
+    if (items.length != 0) {
+      // add items
+      for (int i = _length - 1; i >= start; i--)
+        _array[_getCyclicIndex(i + items.length)] = _array[_getCyclicIndex(i)];
+      for (int i = 0; i < items.length; i++)
+        _array[_getCyclicIndex(start + i)] = items[i];
+    }
+
+    // Adjust length as needed
+    if (_length + items.length > _array.length) {
+      int countToTrim = _length + items.length - _array.length;
+      _startIndex += countToTrim;
+      length = _array.length;
+      onTrimmed?.call(countToTrim);
+    } else {
+      _length += items.length;
+    }
+  }
+
+  void trimStart(int count) {
+    if (count > _length) count = _length;
+
+    // TODO: perhaps bug in original code, this does not clamp the value of startIndex
+    _startIndex += count;
+    _length -= count;
+    onTrimmed?.call(count);
+  }
+
+  void shiftElements(int start, int count, int offset) {
+    if (count < 0) return;
+    if (start < 0 || start >= _length)
+      throw Exception('Start argument is out of range');
+    if (start + offset < 0)
+      throw Exception('Can not shift elements in list beyond index 0');
+    if (offset > 0) {
+      for (var i = count - 1; i >= 0; i--) {
+        this[start + i + offset] = this[start + i];
+      }
+      var expandListBy = (start + count + offset) - _length;
+      if (expandListBy > 0) {
+        _length += expandListBy;
+        while (_length > _array.length) {
+          length--;
+          _startIndex++;
+          onTrimmed?.call(1);
+        }
+      }
+    } else {
+      for (var i = 0; i < count; i++) {
+        this[start + i + offset] = this[start + i];
+      }
+    }
+  }
+
+  bool get isFull => length == maxLength;
+
+  List<T> toList() {
+    return List<T>.generate(length, (index) => this[index]!);
+  }
+}

+ 3 - 3
script/benchmark.dart

@@ -35,7 +35,7 @@ class BenchmarkWrite extends Benchmark {
   }
 
   void benchmark() {
-    final terminal = Terminal();
+    final terminal = Terminal(maxLines: 40000);
     for (var i = 0; i < cycle; i++) {
       terminal.write(data);
     }
@@ -51,7 +51,7 @@ class BenchmarkWrite2 extends Benchmark {
   }
 
   void benchmark() {
-    final terminal = Terminal();
+    final terminal = Terminal(maxLines: 40000);
     for (var i = 0; i < cycle; i++) {
       terminal.write(data);
     }
@@ -71,7 +71,7 @@ class BenchmarkWriteCMatrix extends Benchmark {
   }
 
   void benchmark() {
-    final terminal = Terminal();
+    final terminal = Terminal(maxLines: 40000);
     for (var i = 0; i < cycle; i++) {
       terminal.write(data);
     }