Просмотр исходного кода

Replace reflow logic with the one from aminal (go terminal emulator)

devmil 4 лет назад
Родитель
Сommit
6454c889cb

+ 9 - 1
lib/buffer/buffer_line.dart

@@ -58,6 +58,14 @@ class BufferLine {
     insertN(index, 1);
   }
 
+  void removeN(int index, int count) {
+    final moveView = _cells.buffer.asInt8List((index + count) * _cellSize);
+    _cells.buffer.asInt8List().setAll(index * _cellSize, moveView);
+    final removedView =
+        _cells.buffer.asInt8List((index * _cellSize) + moveView.length);
+    removedView.fillRange(0, removedView.length, 0);
+  }
+
   void insertN(int index, int count) {
     //                       start
     // +--------------------------|-----------------------------------+
@@ -76,7 +84,7 @@ class BufferLine {
 
     // move data forward
     final cells = _cells.buffer.asInt8List();
-    for (var i = _cells.lengthInBytes - offset - 1; i >= start; i++) {
+    for (var i = _cells.lengthInBytes - offset - 1; i >= start; i--) {
       cells[i + offset] = cells[i];
     }
 

+ 27 - 202
lib/buffer/reflow_strategy_narrower.dart

@@ -1,5 +1,6 @@
 import 'dart:math';
 
+import 'package:flutter/material.dart';
 import 'package:xterm/buffer/buffer.dart';
 import 'package:xterm/buffer/buffer_line.dart';
 import 'package:xterm/buffer/reflow_strategy.dart';
@@ -10,215 +11,39 @@ class ReflowStrategyNarrower extends ReflowStrategy {
 
   @override
   void reflow(int newCols, int newRows, int oldCols, int oldRows) {
-    // Gather all BufferLines that need to be inserted into the Buffer here so that they can be
-    // batched up and only committed once
-    List<InsertionSet> toInsert = List<InsertionSet>.empty(growable: true);
-    var countToInsert = 0;
-
-    // Go backwards as many lines may be trimmed and this will avoid considering them
-    for (int y = buffer.lines.length - 1; y >= 0; y--) {
-      // Check whether this line is a problem or not, if not skip it
-      BufferLine nextLine = buffer.lines[y];
-      final lineLength = nextLine.getTrimmedLength(oldCols);
-      if (!nextLine.isWrapped && lineLength <= newCols) {
-        continue;
-      }
-
-      // Gather wrapped lines and adjust y to be the starting line
-      final wrappedLines = List<BufferLine>.empty(growable: true);
-      wrappedLines.add(nextLine);
-      while (nextLine.isWrapped && y > 0) {
-        nextLine = buffer.lines[--y];
-        wrappedLines.insert(0, nextLine);
-      }
-
-      // If these lines contain the cursor don't touch them, the program will handle fixing up
-      // wrapped lines with the cursor
-      final absoluteY = buffer.cursorY + buffer.scrollOffsetFromTop;
-
-      if (absoluteY >= y && absoluteY < y + wrappedLines.length) {
-        continue;
-      }
-
-      final lastLineLength = wrappedLines.last.getTrimmedLength(oldCols);
-      final destLineLengths =
-          _getNewLineLengths(wrappedLines, oldCols, newCols);
-      final linesToAdd = destLineLengths.length - wrappedLines.length;
-
-      // Add the new lines
-      final newLines = List<BufferLine>.empty(growable: true);
-      for (int i = 0; i < linesToAdd; i++) {
-        BufferLine newLine = BufferLine(isWrapped: true);
-        newLines.add(newLine);
-      }
-
-      if (newLines.length > 0) {
-        toInsert.add(InsertionSet()
-          ..start = y + wrappedLines.length + countToInsert
-          ..lines = List<BufferLine>.from(newLines));
-
-        countToInsert += newLines.length;
-      }
-
-      newLines.forEach((l) => wrappedLines.add(l));
-
-      // Copy buffer data to new locations, this needs to happen backwards to do in-place
-      var destLineIndex =
-          destLineLengths.length - 1; // Math.floor(cellsNeeded / newCols);
-      var destCol = destLineLengths[destLineIndex]; // cellsNeeded % newCols;
-      if (destCol == 0) {
-        destLineIndex--;
-        destCol = destLineLengths[destLineIndex];
-      }
-
-      var srcLineIndex = wrappedLines.length - linesToAdd - 1;
-      var srcCol = lastLineLength;
-      while (srcLineIndex >= 0) {
-        final cellsToCopy = min(srcCol, destCol);
-        wrappedLines[destLineIndex].copyCellsFrom(wrappedLines[srcLineIndex],
-            srcCol - cellsToCopy, destCol - cellsToCopy, cellsToCopy);
-        destCol -= cellsToCopy;
-        if (destCol == 0) {
-          destLineIndex--;
-          if (destLineIndex >= 0) {
-            destCol = destLineLengths[destLineIndex];
+    for (var i = 0; i < buffer.lines.length; i++) {
+      final line = buffer.lines[i];
+      final lineLength = line.getTrimmedLength(oldCols);
+      if (lineLength > newCols) {
+        final moveIndexStart = newCols;
+        final cellsToCopy = oldCols - newCols;
+
+        // we need to move cut cells to the next line
+        // if the next line is wrapped anyway, we can push them onto the beginning of that line
+        // otherwise, we need add a new wrapped line
+        if (i + 1 < buffer.lines.length) {
+          final nextLine = buffer.lines[i + 1];
+          if (nextLine.isWrapped) {
+            nextLine.ensure(oldCols + cellsToCopy);
+            nextLine.insertN(0, cellsToCopy);
+            nextLine.copyCellsFrom(line, moveIndexStart, 0, cellsToCopy);
+            line.erase(buffer.terminal.cursor, moveIndexStart, oldCols);
+            continue;
           }
         }
 
-        srcCol -= cellsToCopy;
-        if (srcCol == 0) {
-          srcLineIndex--;
-          final wrappedLinesIndex = max(srcLineIndex, 0);
-          srcCol = ReflowStrategy.getWrappedLineTrimmedLengthFromLines(
-              wrappedLines, wrappedLinesIndex, oldCols);
-        }
-      }
-
-      // Null out the end of the line ends if a wide character wrapped to the following line
-      for (int i = 0; i < wrappedLines.length; i++) {
-        if (destLineLengths[i] < newCols) {
-          wrappedLines[i].removeRange(destLineLengths[i], oldCols);
-        }
-      }
-
-      buffer.adjustSavedCursor(0, linesToAdd);
-    }
-
-    rearrange(toInsert, countToInsert);
-  }
-
-  void rearrange(List<InsertionSet> toInsert, int countToInsert) {
-    // Rearrange lines in the buffer if there are any insertions, this is done at the end rather
-    // than earlier so that it's a single O(n) pass through the buffer, instead of O(n^2) from many
-    // costly calls to CircularList.splice.
-    if (toInsert.length > 0) {
-      // Record original lines so they don't get overridden when we rearrange the list
-      final originalLines = CircularList<BufferLine>(buffer.lines.maxLength);
-      for (int i = 0; i < buffer.lines.length; i++) {
-        originalLines.push(buffer.lines[i]);
-      }
-
-      final originalLinesLength = buffer.lines.length;
+        final newLine = BufferLine(isWrapped: true);
+        newLine.ensure(newCols);
+        newLine.copyCellsFrom(line, moveIndexStart, 0, cellsToCopy);
+        line.erase(buffer.terminal.cursor, moveIndexStart, oldCols);
 
-      var originalLineIndex = originalLinesLength - 1;
-      var nextToInsertIndex = 0;
-      var nextToInsert = toInsert[nextToInsertIndex];
-      buffer.lines.length =
-          min(buffer.lines.maxLength, buffer.lines.length + countToInsert);
-
-      var countInsertedSoFar = 0;
-      for (var i = min(buffer.lines.maxLength - 1,
-              originalLinesLength + countToInsert - 1);
-          i >= 0;
-          i--) {
-        if (!nextToInsert.isNull &&
-            nextToInsert.start > originalLineIndex + countInsertedSoFar) {
-          // Insert extra lines here, adjusting i as needed
-          for (var nextI = nextToInsert.lines!.length - 1;
-              nextI >= 0;
-              nextI--) {
-            if (i < 0) {
-              // if we reflow and the content has to be scrolled back past the beginning
-              // of the buffer then we end up loosing those lines
-              break;
-            }
-
-            buffer.lines[i--] = nextToInsert.lines![nextI];
-          }
+        //TODO: aggregate and do at the end?
+        buffer.lines.insert(i + 1, newLine);
 
-          i++;
-
-          countInsertedSoFar += nextToInsert.lines!.length;
-          if (nextToInsertIndex < toInsert.length - 1) {
-            nextToInsert = toInsert[++nextToInsertIndex];
-          } else {
-            nextToInsert = InsertionSet.nul;
-          }
-        } else {
-          buffer.lines[i] = originalLines[originalLineIndex--];
+        if (i + 1 <= buffer.cursorY) {
+          buffer.moveCursorY(1);
         }
       }
     }
   }
-
-  /// <summary>
-  /// Gets the new line lengths for a given wrapped line. The purpose of this function it to pre-
-  /// compute the wrapping points since wide characters may need to be wrapped onto the following line.
-  /// This function will return an array of numbers of where each line wraps to, the resulting array
-  /// will only contain the values `newCols` (when the line does not end with a wide character) and
-  /// `newCols - 1` (when the line does end with a wide character), except for the last value which
-  /// will contain the remaining items to fill the line.
-  /// Calling this with a `newCols` value of `1` will lock up.
-  /// </summary>
-  List<int> _getNewLineLengths(
-      List<BufferLine> wrappedLines, int oldCols, int newCols) {
-    final newLineLengths = List<int>.empty(growable: true);
-
-    var cellsNeeded = 0;
-    for (int i = 0; i < wrappedLines.length; i++) {
-      cellsNeeded += ReflowStrategy.getWrappedLineTrimmedLengthFromLines(
-          wrappedLines, i, oldCols);
-    }
-
-    // Use srcCol and srcLine to find the new wrapping point, use that to get the cellsAvailable and
-    // linesNeeded
-    var srcCol = 0;
-    var srcLine = 0;
-    var cellsAvailable = 0;
-    while (cellsAvailable < cellsNeeded) {
-      if (cellsNeeded - cellsAvailable < newCols) {
-        // Add the final line and exit the loop
-        newLineLengths.add(cellsNeeded - cellsAvailable);
-        break;
-      }
-
-      srcCol += newCols;
-      final oldTrimmedLength =
-          ReflowStrategy.getWrappedLineTrimmedLengthFromLines(
-              wrappedLines, srcLine, oldCols);
-      if (srcCol > oldTrimmedLength) {
-        srcCol -= oldTrimmedLength;
-        srcLine++;
-      }
-
-      bool endsWithWide = wrappedLines[srcLine].cellGetWidth(srcCol - 1) == 2;
-      if (endsWithWide) {
-        srcCol--;
-      }
-
-      int lineLength = endsWithWide ? newCols - 1 : newCols;
-      newLineLengths.add(lineLength);
-      cellsAvailable += lineLength;
-    }
-
-    return newLineLengths;
-  }
-}
-
-class InsertionSet {
-  List<BufferLine>? lines;
-  int start = 0;
-  bool isNull = false;
-
-  static InsertionSet nul = InsertionSet()..isNull = true;
 }

+ 27 - 180
lib/buffer/reflow_strategy_wider.dart

@@ -10,193 +10,40 @@ class ReflowStrategyWider extends ReflowStrategy {
 
   @override
   void reflow(int newCols, int newRows, int oldCols, int oldRows) {
-    final toRemove = _getLinesToRemove(buffer.lines, oldCols, newCols);
-    if (toRemove.length > 0) {
-      final newLayoutResult = _createNewLayout(buffer.lines, toRemove);
-      _applyNewLayout(buffer.lines, newLayoutResult.layout);
-      _adjustViewport(newCols, newRows, newLayoutResult.removedCount);
-    }
-  }
-
-  /// <summary>
-  /// Evaluates and returns indexes to be removed after a reflow larger occurs. Lines will be removed
-  /// when a wrapped line unwraps.
-  /// </summary>
-  /// <param name="lines">The buffer lines</param>
-  /// <param name="oldCols">The columns before resize</param>
-  /// <param name="newCols">The columns after resize</param>
-  /// <param name="bufferAbsoluteY"></param>
-  /// <param name="nullCharacter"></param>
-  List<int> _getLinesToRemove(
-      CircularList<BufferLine> lines, int oldCols, int newCols) {
-    // Gather all BufferLines that need to be removed from the Buffer here so that they can be
-    // batched up and only committed once
-    final toRemove = List<int>.empty(growable: true);
-
-    for (var y = 0; y < lines.length - 1; y++) {
-      // Check if this row is wrapped
-      var i = y;
-      BufferLine nextLine = lines[++i];
-      if (!nextLine.isWrapped) {
-        continue;
-      }
-
-      // Check how many lines it's wrapped for
-      final wrappedLines = List<BufferLine>.empty(growable: true);
-      wrappedLines.add(lines[y]);
-      while (i < lines.length && nextLine.isWrapped) {
-        wrappedLines.add(nextLine);
-        nextLine = lines[++i];
-      }
-
-      final bufferAbsoluteY = buffer.cursorY + buffer.scrollOffsetFromTop;
-
-      // If these lines contain the cursor don't touch them, the program will handle fixing up wrapped
-      // lines with the cursor
-      if (bufferAbsoluteY >= y && bufferAbsoluteY < i) {
-        y += wrappedLines.length - 1;
-        continue;
-      }
-
-      // Copy buffer data to new locations
-      var destLineIndex = 0;
-      var destCol = ReflowStrategy.getWrappedLineTrimmedLengthFromCircularList(
-          buffer.lines, destLineIndex, oldCols);
-      var srcLineIndex = 1;
-      var srcCol = 0;
-      while (srcLineIndex < wrappedLines.length) {
-        final srcTrimmedTineLength =
-            ReflowStrategy.getWrappedLineTrimmedLengthFromLines(
-                wrappedLines, srcLineIndex, oldCols);
-        final srcRemainingCells = srcTrimmedTineLength - srcCol;
-        final destRemainingCells = newCols - destCol;
-        final cellsToCopy = min(srcRemainingCells, destRemainingCells);
-
-        wrappedLines[destLineIndex].copyCellsFrom(
-            wrappedLines[srcLineIndex], srcCol, destCol, cellsToCopy);
-
-        destCol += cellsToCopy;
-        if (destCol == newCols) {
-          destLineIndex++;
-          destCol = 0;
+    for (var i = 0; i < buffer.lines.length; i++) {
+      final line = buffer.lines[i];
+      for (var offset = 1; i + offset < buffer.lines.length; offset++) {
+        final nextLine = buffer.lines[i + offset];
+        if (!nextLine.isWrapped) {
+          break;
         }
-
-        srcCol += cellsToCopy;
-        if (srcCol == srcTrimmedTineLength) {
-          srcLineIndex++;
-          srcCol = 0;
+        final lineLength = line.getTrimmedLength(oldCols);
+        final spaceOnLine = newCols - lineLength;
+        if (spaceOnLine <= 0) {
+          // no more space to unwrap
+          break;
         }
-
-        // Make sure the last cell isn't wide, if it is copy it to the current dest
-        if (destCol == 0 && destLineIndex != 0) {
-          if (wrappedLines[destLineIndex - 1].cellGetWidth(newCols - 1) == 2) {
-            wrappedLines[destLineIndex].copyCellsFrom(
-                wrappedLines[destLineIndex - 1], newCols - 1, destCol++, 1);
-            // Null out the end of the last row
-            wrappedLines[destLineIndex - 1]
-                .erase(buffer.terminal.cursor, newCols - 1, newCols, false);
+        final nextLineLength = nextLine.getTrimmedLength(oldCols);
+        final moveCount = min(spaceOnLine, nextLineLength);
+        line.copyCellsFrom(nextLine, 0, lineLength, moveCount);
+        if (moveCount == nextLineLength) {
+          if (i + offset <= buffer.cursorY) {
+            //TODO: adapt scrolling
+            buffer.moveCursorY(-1);
           }
-        }
-      }
-
-      // Clear out remaining cells or fragments could remain;
-      wrappedLines[destLineIndex]
-          .erase(buffer.terminal.cursor, destCol, newCols);
-
-      // Work backwards and remove any rows at the end that only contain null cells
-      var countToRemove = 0;
-      for (int ix = wrappedLines.length - 1; ix > 0; ix--) {
-        if (ix > destLineIndex ||
-            wrappedLines[ix].getTrimmedLength(oldCols) == 0) {
-          countToRemove++;
+          // if we unwrapped all cells off the next line, delete it
+          buffer.lines.remove(i + offset);
+          offset--;
         } else {
-          break;
+          // otherwise just remove the characters we moved up a line
+          nextLine.removeN(0, moveCount);
         }
       }
-
-      if (countToRemove > 0) {
-        toRemove.add(y + wrappedLines.length - countToRemove); // index
-        toRemove.add(countToRemove);
-      }
-
-      y += wrappedLines.length - 1;
-    }
-
-    return toRemove;
-  }
-
-  LayoutResult _createNewLayout(
-      CircularList<BufferLine> lines, List<int> toRemove) {
-    var layout = new CircularList<int>(lines.length);
-
-    // First iterate through the list and get the actual indexes to use for rows
-    var nextToRemoveIndex = 0;
-    var nextToRemoveStart = toRemove[nextToRemoveIndex];
-    var countRemovedSoFar = 0;
-
-    for (int i = 0; i < lines.length; i++) {
-      if (nextToRemoveStart == i) {
-        int countToRemove = toRemove[++nextToRemoveIndex];
-
-        // Tell markers that there was a deletion
-        //lines.onDeleteEmitter.fire ({
-        //	index: i - countRemovedSoFar,
-        //	amount: countToRemove
-        //});
-
-        i += countToRemove - 1;
-        countRemovedSoFar += countToRemove;
-
-        nextToRemoveStart = lines.length + 1; //was: int.max
-        if (nextToRemoveIndex < toRemove.length - 1)
-          nextToRemoveStart = toRemove[++nextToRemoveIndex];
-      } else {
-        layout.push(i);
-      }
-    }
-
-    return new LayoutResult(layout, countRemovedSoFar);
-  }
-
-  void _applyNewLayout(
-      CircularList<BufferLine> lines, CircularList<int> newLayout) {
-    var newLayoutLines = CircularList<BufferLine>(lines.length);
-
-    for (int i = 0; i < newLayout.length; i++) {
-      newLayoutLines.push(lines[newLayout[i]]);
     }
-
-    // Rearrange the list
-    for (int i = 0; i < newLayoutLines.length; i++) {
-      lines[i] = newLayoutLines[i];
+    //buffer doesn't have enough lines
+    if (buffer.lines.length < buffer.terminal.viewHeight) {
+      // Add an extra row at the bottom of the viewport
+      buffer.lines.push(BufferLine());
     }
-
-    lines.length = newLayout.length;
   }
-
-  void _adjustViewport(int newCols, int newRows, int countRemoved) {
-    var viewportAdjustments = countRemoved;
-    while (viewportAdjustments-- > 0) {
-      if (buffer.lines.length <= buffer.terminal.viewHeight) {
-        //cursor is not at the top
-        if (buffer.cursorY > 0) {
-          buffer.moveCursorY(-1);
-        }
-        //buffer doesn't have enough lines
-        if (buffer.lines.length < buffer.terminal.viewHeight) {
-          // Add an extra row at the bottom of the viewport
-          buffer.lines.push(BufferLine());
-        }
-      }
-    }
-
-    buffer.adjustSavedCursor(0, -countRemoved);
-  }
-}
-
-class LayoutResult {
-  CircularList<int> layout;
-  int removedCount;
-
-  LayoutResult(this.layout, this.removedCount);
 }