소스 검색

Ported rest of the reflow logic

doesn't.work.at.all.

* Very poor performance. Reflow is happening directly in the UI thread and takes way too long
* Almost all terminal data is lost afterwards (blank lines)

Next steps:
* find out what is causing the data loss
* Profile the code to see where it is loosing all that performance
* throttle the resize events (but as the resolution is "cells" I don't expect much from that)
* think about handling the terminal logic in a separate Isolate
devmil 4 년 전
부모
커밋
fed3a10b36
4개의 변경된 파일383개의 추가작업 그리고 50개의 파일을 삭제
  1. 81 5
      lib/buffer/buffer.dart
  2. 15 6
      lib/buffer/buffer_line.dart
  3. 280 36
      lib/buffer/buffer_reflow.dart
  4. 7 3
      lib/terminal/terminal.dart

+ 81 - 5
lib/buffer/buffer.dart

@@ -5,9 +5,12 @@ 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/theme/terminal_color.dart';
 import 'package:xterm/utli/scroll_range.dart';
 import 'package:xterm/utli/unicode_v11.dart';
 
+import 'buffer_reflow.dart';
+
 class Buffer {
   Buffer(this.terminal) {
     resetVerticalMargins();
@@ -347,10 +350,10 @@ class Buffer {
   }
 
   void adjustSavedCursor(int diffX, int diffY) {
-    if(_savedCursorX != null) {
+    if (_savedCursorX != null) {
       _savedCursorX = _savedCursorX! + diffX;
     }
-    if(_savedCursorY != null) {
+    if (_savedCursorY != null) {
       _savedCursorY = _savedCursorY! + diffY;
     }
   }
@@ -473,8 +476,81 @@ class Buffer {
     lines.removeAt(index);
   }
 
-  void resize(int width, int height) {
-    
-  }
+  void resize(int width, int height, int oldWidth, int oldHeight) {
+    if (this.lines.length > 0) {
+      // Deal with columns increasing (reducing needs to happen after reflow)
+      if (oldWidth < width) {
+        lines.forEach((l) {
+          l.resize(width);
+        });
+      }
 
+      if (oldHeight < height) {
+        for (int y = oldHeight; y < height; y++) {
+          //as long as we can we will adjust the scrolling
+          if (scrollOffsetFromBottom > 0) {
+            _scrollLinesFromBottom--;
+          }
+        }
+      } else {
+        // (this._rows >= newRows)
+        //TODO: check if correct: I think we don't have anything to do here... things will correct automatically
+        // for (int y = rows; y > newRows; y--) {
+        //   if (lines.Length > newRows + YBase) {
+        //     if (lines.Length > YBase + this.y + 1) {
+        //       // The line is a blank line below the cursor, remove it
+        //       lines.Pop();
+        //     } else {
+        //       // The line is the cursor, scroll down
+        //       YBase++;
+        //       YDisp++;
+        //     }
+        //   }
+        // }
+      }
+
+      // // Reduce max length if needed after adjustments, this is done after as it
+      // // would otherwise cut data from the bottom of the buffer.
+      // if (newMaxLength < lines.MaxLength) {
+      //   // Trim from the top of the buffer and adjust ybase and ydisp.
+      //   int amountToTrim = lines.Length - newMaxLength;
+      //   if (amountToTrim > 0) {
+      //     lines.TrimStart(amountToTrim);
+      //     YBase = Math.Max(YBase - amountToTrim, 0);
+      //     YDisp = Math.Max(YDisp - amountToTrim, 0);
+      //     SavedY = Math.Max(SavedY - amountToTrim, 0);
+      //   }
+      //
+      //   lines.MaxLength = newMaxLength;
+      // }
+      //
+      // // Make sure that the cursor stays on screen
+      // X = Math.Min(X, newCols - 1);
+      // Y = Math.Min(Y, newRows - 1);
+      // if (addToY != 0) {
+      //   Y += addToY;
+      // }
+      //
+      // SavedX = Math.Min(SavedX, newCols - 1);
+
+      // ScrollTop = 0;
+    }
+
+    // ScrollBottom = newRows - 1;
+
+    if (/*IsReflowEnabled*/ true) {
+      final rf = BufferReflow(
+          this,
+          CellAttr(
+              fgColor: TerminalColor.empty(), bgColor: TerminalColor.empty()));
+      rf.doReflow(oldWidth, width);
+
+      // Trim the end of the line off if cols shrunk
+      if (oldWidth > width) {
+        lines.forEach((l) {
+          l.resize(width, erase: true);
+        });
+      }
+    }
+  }
 }

+ 15 - 6
lib/buffer/buffer_line.dart

@@ -6,7 +6,8 @@ class BufferLine {
   bool _isWrapped = false;
 
   BufferLine({int numOfCells = 0, attr}) {
-    _cells.addAll(List<Cell>.generate(numOfCells, (index) => Cell(codePoint: 0, attr: attr)));
+    _cells.addAll(List<Cell>.generate(
+        numOfCells, (index) => Cell(codePoint: 0, attr: attr)));
   }
 
   bool get isWrapped {
@@ -29,12 +30,11 @@ class BufferLine {
     _cells.clear();
   }
 
-  int getTrimmedLength () {
+  int getTrimmedLength() {
     for (int i = _cells.length - 1; i >= 0; --i)
       if (_cells[i].codePoint != 0) {
         int width = 0;
-        for (int j = 0; j <= i; j++)
-          width += _cells[i].width;
+        for (int j = 0; j <= i; j++) width += _cells[i].width;
         return width;
       }
     return 0;
@@ -61,8 +61,7 @@ class BufferLine {
     _cells.removeRange(start, end);
   }
 
-  void copyCellsFrom (BufferLine src, int srcCol, int dstCol, int len)
-  {
+  void copyCellsFrom(BufferLine src, int srcCol, int dstCol, int len) {
     List.copyRange(_cells, dstCol, src._cells, srcCol, srcCol + len);
   }
 
@@ -73,4 +72,14 @@ class BufferLine {
   bool hasContentAt(int col) {
     return _cells[col].codePoint != 0;
   }
+
+  void resize(int width, {bool erase = false}) {
+    var missing = width - _cells.length;
+    if (missing > 0) {
+      _cells.addAll(List<Cell>.generate(missing, (index) => Cell()));
+    }
+    if (missing < 0 && erase) {
+      _cells.removeRange(width, _cells.length - 1);
+    }
+  }
 }

+ 280 - 36
lib/buffer/buffer_reflow.dart

@@ -1,19 +1,27 @@
 import 'dart:math';
 
 import 'package:xterm/buffer/cell_attr.dart';
-import 'package:dart_numeric'
 
 import 'buffer.dart';
 import 'buffer_line.dart';
 
 class LayoutResult {
-
   LayoutResult(this.layout, this.removedCount);
 
   final List<int> layout;
   final int removedCount;
 }
 
+class InsertionSet {
+  InsertionSet({this.lines, this.start, this.isNull = false});
+
+  final List<BufferLine>? lines;
+  final int? start;
+  final bool isNull;
+
+  static InsertionSet nullValue = InsertionSet(isNull: true);
+}
+
 class BufferReflow {
   BufferReflow(this._buffer, this._emptyCellAttr);
 
@@ -21,11 +29,11 @@ class BufferReflow {
   final CellAttr _emptyCellAttr;
 
   void doReflow(int colsBefore, int colsAfter) {
-    if(colsBefore == colsAfter) {
+    if (colsBefore == colsAfter) {
       return;
     }
 
-    if(colsAfter > colsBefore) {
+    if (colsAfter > colsBefore) {
       //got larger
       _reflowLarger(colsBefore, colsAfter);
     } else {
@@ -37,17 +45,246 @@ class BufferReflow {
   void _reflowLarger(int colsBefore, int colsAfter) {
     var toRemove = _reflowLargerGetLinesToRemove(colsBefore, colsAfter);
     if (toRemove.length > 0) {
-      var newLayoutResult = _reflowLargerCreateNewLayout(_buffer.lines, toRemove);
+      var newLayoutResult =
+          _reflowLargerCreateNewLayout(_buffer.lines, toRemove);
       _reflowLargerApplyNewLayout(_buffer.lines, newLayoutResult.layout);
-      _reflowLargerAdjustViewport(colsBefore, colsAfter, newLayoutResult.removedCount);
+      _reflowLargerAdjustViewport(
+          colsBefore, colsAfter, newLayoutResult.removedCount);
     }
   }
 
   void _reflowSmaller(int colsBefore, int colsAfter) {
+    // 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 = [];
+    int 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];
+      int lineLength = nextLine.getTrimmedLength();
+      if (!nextLine.isWrapped && lineLength <= colsAfter) {
+        continue;
+      }
 
+      // Gather wrapped lines and adjust y to be the starting line
+      List<BufferLine> wrappedLines = [];
+      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.scrollOffsetFromBottom;
+
+      if (absoluteY >= y && absoluteY < y + wrappedLines.length) {
+        continue;
+      }
+
+      int lastLineLength = wrappedLines.last.getTrimmedLength();
+      List<int> destLineLengths =
+          _getNewLineLengths(wrappedLines, colsBefore, colsAfter);
+      int linesToAdd = destLineLengths.length - wrappedLines.length;
+
+      // Add the new lines
+      List<BufferLine> newLines = [];
+      for (int i = 0; i < linesToAdd; i++) {
+        BufferLine newLine = BufferLine(numOfCells: colsAfter);
+        newLines.add(newLine);
+      }
+
+      if (newLines.length > 0) {
+        toInsert.add(InsertionSet(
+            start: y + wrappedLines.length + countToInsert, lines: 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
+      int destLineIndex =
+          destLineLengths.length - 1; // Math.floor(cellsNeeded / newCols);
+      int destCol = destLineLengths[destLineIndex]; // cellsNeeded % newCols;
+      if (destCol == 0) {
+        destLineIndex--;
+        destCol = destLineLengths[destLineIndex];
+      }
+
+      int srcLineIndex = wrappedLines.length - linesToAdd - 1;
+      int srcCol = lastLineLength;
+      while (srcLineIndex >= 0) {
+        int 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];
+        }
+
+        srcCol -= cellsToCopy;
+        if (srcCol == 0) {
+          srcLineIndex--;
+          int wrappedLinesIndex = max(srcLineIndex, 0);
+          srcCol = _getWrappedLineTrimmedLengthRow(
+              wrappedLines, wrappedLinesIndex, colsBefore);
+        }
+      }
+
+      // 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] < colsAfter) {
+          wrappedLines[i].removeRange(destLineLengths[i]);
+        }
+      }
+
+      // Adjust viewport as needed
+      //TODO: probably nothing to do here because of the way the ViewPort is handled compared to the xterm.js project
+      // int viewportAdjustments = linesToAdd;
+      // while (viewportAdjustments-- > 0) {
+      //   if (Buffer.YBase == 0) {
+      //     if (Buffer.Y < newRows - 1) {
+      //       Buffer.Y++;
+      //       Buffer.Lines.Pop();
+      //     } else {
+      //       Buffer.YBase++;
+      //       Buffer.YDisp++;
+      //     }
+      //   } else {
+      //     // Ensure ybase does not exceed its maximum value
+      //     if (Buffer.YBase <
+      //         Math.Min(Buffer.Lines.MaxLength,
+      //                 Buffer.Lines.Length + countToInsert) -
+      //             newRows) {
+      //       if (Buffer.YBase == Buffer.YDisp) {
+      //         Buffer.YDisp++;
+      //       }
+      //
+      //       Buffer.YBase++;
+      //     }
+      //   }
+      // }
+
+      _buffer.adjustSavedCursor(0, linesToAdd);
+      //TODO: maybe row count has to be handled here?
+    }
+
+    _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 buffer insert events and then play them back backwards so that the indexes are
+      // correct
+      List<int> insertEvents = [];
+
+      // Record original lines so they don't get overridden when we rearrange the list
+      List<BufferLine> originalLines = List<BufferLine>.from(_buffer.lines);
+      _buffer.lines.addAll(List<BufferLine>.generate(countToInsert,
+          (index) => BufferLine(numOfCells: _buffer.terminal.viewWidth)));
+
+      int originalLinesLength = originalLines.length;
+
+      int originalLineIndex = originalLinesLength - 1;
+      int nextToInsertIndex = 0;
+      InsertionSet nextToInsert = toInsert[nextToInsertIndex];
+
+      //TODO: remove rows that now are "too much"
+
+      int countInsertedSoFar = 0;
+      for (int i = originalLinesLength + countToInsert - 1; i >= 0; i--) {
+        if (!nextToInsert.isNull &&
+            nextToInsert.start != null &&
+            nextToInsert.lines != null &&
+            nextToInsert.start! > originalLineIndex + countInsertedSoFar) {
+          // Insert extra lines here, adjusting i as needed
+          for (int 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];
+          }
+
+          i++;
+
+          countInsertedSoFar += nextToInsert.lines!.length;
+          if (nextToInsertIndex < toInsert.length - 1) {
+            nextToInsert = toInsert[++nextToInsertIndex];
+          } else {
+            nextToInsert = InsertionSet.nullValue;
+          }
+        } else {
+          _buffer.lines[i] = originalLines[originalLineIndex--];
+        }
+      }
+    }
+  }
+
+  /// <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) {
+    List<int> newLineLengths = [];
+
+    int cellsNeeded = 0;
+    for (int i = 0; i < wrappedLines.length; i++) {
+      cellsNeeded += _getWrappedLineTrimmedLengthRow(wrappedLines, i, oldCols);
+    }
+
+    // Use srcCol and srcLine to find the new wrapping point, use that to get the cellsAvailable and
+    // linesNeeded
+    int srcCol = 0;
+    int srcLine = 0;
+    int cellsAvailable = 0;
+    while (cellsAvailable < cellsNeeded) {
+      if (cellsNeeded - cellsAvailable < newCols) {
+        // Add the final line and exit the loop
+        newLineLengths.add(cellsNeeded - cellsAvailable);
+        break;
+      }
+
+      srcCol += newCols;
+      int oldTrimmedLength =
+          _getWrappedLineTrimmedLengthRow(wrappedLines, srcLine, oldCols);
+      if (srcCol > oldTrimmedLength) {
+        srcCol -= oldTrimmedLength;
+        srcLine++;
+      }
+
+      bool endsWithWide = wrappedLines[srcLine].getWidthAt(srcCol - 1) == 2;
+      if (endsWithWide) {
+        srcCol--;
+      }
+
+      int lineLength = endsWithWide ? newCols - 1 : newCols;
+      newLineLengths.add(lineLength);
+      cellsAvailable += lineLength;
+    }
+
+    return newLineLengths;
   }
 
-  void _reflowLargerAdjustViewport(int colsBefore, int colsAfter, int countRemoved) {
+  void _reflowLargerAdjustViewport(
+      int colsBefore, int colsAfter, int countRemoved) {
     // Adjust viewport based on number of items removed
     var viewportAdjustments = countRemoved;
     while (viewportAdjustments-- > 0) {
@@ -60,7 +297,8 @@ class BufferReflow {
         //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.add(new BufferLine(numOfCells: colsAfter, attr: _emptyCellAttr));
+          _buffer.lines
+              .add(new BufferLine(numOfCells: colsAfter, attr: _emptyCellAttr));
         }
       } else {
         //Nothing to do here due to the way scrolling is handled
@@ -78,11 +316,12 @@ class BufferReflow {
     _buffer.adjustSavedCursor(0, -countRemoved);
   }
 
-  void _reflowLargerApplyNewLayout(List<BufferLine> lines, List<int> newLayout) {
+  void _reflowLargerApplyNewLayout(
+      List<BufferLine> lines, List<int> newLayout) {
     var newLayoutLines = List<BufferLine>.empty();
 
     for (int i = 0; i < newLayout.length; i++) {
-      newLayoutLines.add(lines[newLayout [i]]);
+      newLayoutLines.add(lines[newLayout[i]]);
     }
 
     // Rearrange the list
@@ -93,17 +332,18 @@ class BufferReflow {
     lines.removeRange(newLayoutLines.length, lines.length - 1);
   }
 
-  LayoutResult _reflowLargerCreateNewLayout(List<BufferLine> lines, List<int> toRemove) {
+  LayoutResult _reflowLargerCreateNewLayout(
+      List<BufferLine> lines, List<int> toRemove) {
     var layout = List<int>.empty();
 
     // First iterate through the list and get the actual indexes to use for rows
     int nextToRemoveIndex = 0;
-    int nextToRemoveStart = toRemove [nextToRemoveIndex];
+    int nextToRemoveStart = toRemove[nextToRemoveIndex];
     int countRemovedSoFar = 0;
 
     for (int i = 0; i < lines.length; i++) {
       if (nextToRemoveStart == i) {
-        int countToRemove = toRemove [++nextToRemoveIndex];
+        int countToRemove = toRemove[++nextToRemoveIndex];
 
         // Tell markers that there was a deletion
         //lines.onDeleteEmitter.fire ({
@@ -116,20 +356,18 @@ class BufferReflow {
 
         nextToRemoveStart = lines.length + 1;
         if (nextToRemoveIndex < toRemove.length - 1)
-          nextToRemoveStart = toRemove [++nextToRemoveIndex];
+          nextToRemoveStart = toRemove[++nextToRemoveIndex];
       } else {
         layout.add(i);
       }
     }
 
-    return LayoutResult (layout, countRemovedSoFar);
+    return LayoutResult(layout, countRemovedSoFar);
   }
 
   List<int> _reflowLargerGetLinesToRemove(int colsBefore, int colsAfter) {
     List<int> toRemove = List<int>.empty();
 
-    
-
     for (int y = 0; y < _buffer.lines.length - 1; y++) {
       // Check if this row is wrapped
       int i = y;
@@ -157,16 +395,19 @@ class BufferReflow {
 
       // Copy buffer data to new locations
       int destLineIndex = 0;
-      int destCol = _getWrappedLineTrimmedLengthRow(_buffer.lines, destLineIndex, colsBefore);
+      int destCol = _getWrappedLineTrimmedLengthRow(
+          _buffer.lines, destLineIndex, colsBefore);
       int srcLineIndex = 1;
       int srcCol = 0;
       while (srcLineIndex < wrappedLines.length) {
-        int srcTrimmedTineLength = _getWrappedLineTrimmedLengthRow(wrappedLines, srcLineIndex, colsBefore);
+        int srcTrimmedTineLength = _getWrappedLineTrimmedLengthRow(
+            wrappedLines, srcLineIndex, colsBefore);
         int srcRemainingCells = srcTrimmedTineLength - srcCol;
         int destRemainingCells = colsAfter - destCol;
         int cellsToCopy = min(srcRemainingCells, destRemainingCells);
 
-        wrappedLines [destLineIndex].copyCellsFrom (wrappedLines [srcLineIndex], srcCol, destCol, cellsToCopy);
+        wrappedLines[destLineIndex].copyCellsFrom(
+            wrappedLines[srcLineIndex], srcCol, destCol, cellsToCopy);
 
         destCol += cellsToCopy;
         if (destCol == colsAfter) {
@@ -182,21 +423,23 @@ class BufferReflow {
 
         // 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].getWidthAt(colsAfter - 1) == 2) {
-            wrappedLines [destLineIndex].copyCellsFrom (wrappedLines [destLineIndex - 1], colsAfter - 1, destCol++, 1);
+          if (wrappedLines[destLineIndex - 1].getWidthAt(colsAfter - 1) == 2) {
+            wrappedLines[destLineIndex].copyCellsFrom(
+                wrappedLines[destLineIndex - 1], colsAfter - 1, destCol++, 1);
             // Null out the end of the last row
-            wrappedLines [destLineIndex - 1].erase (_emptyCellAttr, colsAfter - 1, colsAfter);
+            wrappedLines[destLineIndex - 1]
+                .erase(_emptyCellAttr, colsAfter - 1, colsAfter);
           }
         }
       }
 
       // Clear out remaining cells or fragments could remain;
-      wrappedLines [destLineIndex].erase(_emptyCellAttr, destCol, colsAfter);
+      wrappedLines[destLineIndex].erase(_emptyCellAttr, destCol, colsAfter);
 
       // Work backwards and remove any rows at the end that only contain null cells
       int countToRemove = 0;
       for (int ix = wrappedLines.length - 1; ix > 0; ix--) {
-        if (ix > destLineIndex || wrappedLines [ix].getTrimmedLength () == 0) {
+        if (ix > destLineIndex || wrappedLines[ix].getTrimmedLength() == 0) {
           countToRemove++;
         } else {
           break;
@@ -204,8 +447,8 @@ class BufferReflow {
       }
 
       if (countToRemove > 0) {
-        toRemove.add (y + wrappedLines.length - countToRemove); // index
-        toRemove.add (countToRemove);
+        toRemove.add(y + wrappedLines.length - countToRemove); // index
+        toRemove.add(countToRemove);
       }
 
       y += wrappedLines.length - 1;
@@ -214,23 +457,24 @@ class BufferReflow {
     return toRemove;
   }
 
-
-  int _getWrappedLineTrimmedLengthRow(List<BufferLine> lines, int row, int cols)
-  {
-    return _getWrappedLineTrimmedLength (lines[row], row == lines.length - 1 ? null : lines[row + 1], cols);
+  int _getWrappedLineTrimmedLengthRow(
+      List<BufferLine> lines, int row, int cols) {
+    return _getWrappedLineTrimmedLength(
+        lines[row], row == lines.length - 1 ? null : lines[row + 1], cols);
   }
 
-  int _getWrappedLineTrimmedLength (BufferLine line, BufferLine? nextLine, int cols)
-  {
+  int _getWrappedLineTrimmedLength(
+      BufferLine line, BufferLine? nextLine, int cols) {
     // If this is the last row in the wrapped line, get the actual trimmed length
     if (nextLine == null) {
-      return line.getTrimmedLength ();
+      return line.getTrimmedLength();
     }
 
     // Detect whether the following line starts with a wide character and the end of the current line
     // is null, if so then we can be pretty sure the null character should be excluded from the line
     // length]
-    bool endsInNull = !(line.hasContentAt(cols - 1)) && line.getWidthAt(cols - 1) == 1;
+    bool endsInNull =
+        !(line.hasContentAt(cols - 1)) && line.getWidthAt(cols - 1) == 1;
     bool followingLineStartsWithWide = nextLine.getWidthAt(0) == 2;
 
     if (endsInNull && followingLineStartsWithWide) {
@@ -239,4 +483,4 @@ class BufferReflow {
 
     return cols;
   }
-}
+}

+ 7 - 3
lib/terminal/terminal.dart

@@ -188,7 +188,7 @@ class Terminal with Observable {
       const esc = 0x1b;
       final char = _queue.removeFirst();
 
-      if (char == esc) {
+      if (char == esc && _queue.isNotEmpty) {
         ansiHandler(_queue, this);
         refresh();
         continue;
@@ -287,13 +287,17 @@ class Terminal with Observable {
   }
 
   void resize(int width, int height) {
-    _altBuffer.resize(width, height);
-    _mainBuffer.resize(width, height);
     final cursorY = buffer.convertViewLineToRawLine(buffer.cursorY);
 
+    final oldWidth = _viewWidth;
+    final oldHeight = _viewHeight;
+
     _viewWidth = max(width, 1);
     _viewHeight = max(height, 1);
 
+    //_altBuffer.resize(width, height, oldWidth, oldHeight);
+    _mainBuffer.resize(width, height, oldWidth, oldHeight);
+
     buffer.setCursorY(buffer.convertRawLineToViewLine(cursorY));
 
     if (buffer == _altBuffer) {