reflow_strategy_narrower.dart 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. import 'dart:math';
  2. import 'package:xterm/buffer/buffer.dart';
  3. import 'package:xterm/buffer/buffer_line.dart';
  4. import 'package:xterm/buffer/reflow_strategy.dart';
  5. import 'package:xterm/utli/circular_list.dart';
  6. class ReflowStrategyNarrower extends ReflowStrategy {
  7. ReflowStrategyNarrower(Buffer buffer) : super(buffer);
  8. @override
  9. void reflow(int newCols, int newRows, int oldCols, int oldRows) {
  10. // Gather all BufferLines that need to be inserted into the Buffer here so that they can be
  11. // batched up and only committed once
  12. List<InsertionSet> toInsert = List<InsertionSet>.empty(growable: true);
  13. var countToInsert = 0;
  14. // Go backwards as many lines may be trimmed and this will avoid considering them
  15. for (int y = buffer.lines.length - 1; y >= 0; y--) {
  16. // Check whether this line is a problem or not, if not skip it
  17. BufferLine nextLine = buffer.lines[y];
  18. final lineLength = nextLine.getTrimmedLength(oldCols);
  19. if (!nextLine.isWrapped && lineLength <= newCols) {
  20. continue;
  21. }
  22. // Gather wrapped lines and adjust y to be the starting line
  23. final wrappedLines = List<BufferLine>.empty(growable: true);
  24. wrappedLines.add(nextLine);
  25. while (nextLine.isWrapped && y > 0) {
  26. nextLine = buffer.lines[--y];
  27. wrappedLines.insert(0, nextLine);
  28. }
  29. // If these lines contain the cursor don't touch them, the program will handle fixing up
  30. // wrapped lines with the cursor
  31. final absoluteY = buffer.cursorY + buffer.scrollOffsetFromTop;
  32. if (absoluteY >= y && absoluteY < y + wrappedLines.length) {
  33. continue;
  34. }
  35. final lastLineLength = wrappedLines.last.getTrimmedLength(oldCols);
  36. final destLineLengths =
  37. _getNewLineLengths(wrappedLines, oldCols, newCols);
  38. final linesToAdd = destLineLengths.length - wrappedLines.length;
  39. // Add the new lines
  40. final newLines = List<BufferLine>.empty(growable: true);
  41. for (int i = 0; i < linesToAdd; i++) {
  42. BufferLine newLine = BufferLine(isWrapped: true);
  43. newLines.add(newLine);
  44. }
  45. if (newLines.length > 0) {
  46. toInsert.add(InsertionSet()
  47. ..start = y + wrappedLines.length + countToInsert
  48. ..lines = List<BufferLine>.from(newLines));
  49. countToInsert += newLines.length;
  50. }
  51. newLines.forEach((l) => wrappedLines.add(l));
  52. // Copy buffer data to new locations, this needs to happen backwards to do in-place
  53. var destLineIndex =
  54. destLineLengths.length - 1; // Math.floor(cellsNeeded / newCols);
  55. var destCol = destLineLengths[destLineIndex]; // cellsNeeded % newCols;
  56. if (destCol == 0) {
  57. destLineIndex--;
  58. destCol = destLineLengths[destLineIndex];
  59. }
  60. var srcLineIndex = wrappedLines.length - linesToAdd - 1;
  61. var srcCol = lastLineLength;
  62. while (srcLineIndex >= 0) {
  63. final cellsToCopy = min(srcCol, destCol);
  64. wrappedLines[destLineIndex].copyCellsFrom(wrappedLines[srcLineIndex],
  65. srcCol - cellsToCopy, destCol - cellsToCopy, cellsToCopy);
  66. destCol -= cellsToCopy;
  67. if (destCol == 0) {
  68. destLineIndex--;
  69. if (destLineIndex >= 0) {
  70. destCol = destLineLengths[destLineIndex];
  71. }
  72. }
  73. srcCol -= cellsToCopy;
  74. if (srcCol == 0) {
  75. srcLineIndex--;
  76. final wrappedLinesIndex = max(srcLineIndex, 0);
  77. srcCol = ReflowStrategy.getWrappedLineTrimmedLengthFromLines(
  78. wrappedLines, wrappedLinesIndex, oldCols);
  79. }
  80. }
  81. // Null out the end of the line ends if a wide character wrapped to the following line
  82. for (int i = 0; i < wrappedLines.length; i++) {
  83. if (destLineLengths[i] < newCols) {
  84. wrappedLines[i].removeRange(destLineLengths[i], oldCols);
  85. }
  86. }
  87. buffer.adjustSavedCursor(0, linesToAdd);
  88. }
  89. rearrange(toInsert, countToInsert);
  90. }
  91. void rearrange(List<InsertionSet> toInsert, int countToInsert) {
  92. // Rearrange lines in the buffer if there are any insertions, this is done at the end rather
  93. // than earlier so that it's a single O(n) pass through the buffer, instead of O(n^2) from many
  94. // costly calls to CircularList.splice.
  95. if (toInsert.length > 0) {
  96. // Record original lines so they don't get overridden when we rearrange the list
  97. final originalLines = CircularList<BufferLine>(buffer.lines.maxLength);
  98. for (int i = 0; i < buffer.lines.length; i++) {
  99. originalLines.push(buffer.lines[i]);
  100. }
  101. final originalLinesLength = buffer.lines.length;
  102. var originalLineIndex = originalLinesLength - 1;
  103. var nextToInsertIndex = 0;
  104. var nextToInsert = toInsert[nextToInsertIndex];
  105. buffer.lines.length =
  106. min(buffer.lines.maxLength, buffer.lines.length + countToInsert);
  107. var countInsertedSoFar = 0;
  108. for (var i = min(buffer.lines.maxLength - 1,
  109. originalLinesLength + countToInsert - 1);
  110. i >= 0;
  111. i--) {
  112. if (!nextToInsert.isNull &&
  113. nextToInsert.start > originalLineIndex + countInsertedSoFar) {
  114. // Insert extra lines here, adjusting i as needed
  115. for (var nextI = nextToInsert.lines!.length - 1;
  116. nextI >= 0;
  117. nextI--) {
  118. if (i < 0) {
  119. // if we reflow and the content has to be scrolled back past the beginning
  120. // of the buffer then we end up loosing those lines
  121. break;
  122. }
  123. buffer.lines[i--] = nextToInsert.lines![nextI];
  124. }
  125. i++;
  126. countInsertedSoFar += nextToInsert.lines!.length;
  127. if (nextToInsertIndex < toInsert.length - 1) {
  128. nextToInsert = toInsert[++nextToInsertIndex];
  129. } else {
  130. nextToInsert = InsertionSet.nul;
  131. }
  132. } else {
  133. buffer.lines[i] = originalLines[originalLineIndex--];
  134. }
  135. }
  136. }
  137. }
  138. /// <summary>
  139. /// Gets the new line lengths for a given wrapped line. The purpose of this function it to pre-
  140. /// compute the wrapping points since wide characters may need to be wrapped onto the following line.
  141. /// This function will return an array of numbers of where each line wraps to, the resulting array
  142. /// will only contain the values `newCols` (when the line does not end with a wide character) and
  143. /// `newCols - 1` (when the line does end with a wide character), except for the last value which
  144. /// will contain the remaining items to fill the line.
  145. /// Calling this with a `newCols` value of `1` will lock up.
  146. /// </summary>
  147. List<int> _getNewLineLengths(
  148. List<BufferLine> wrappedLines, int oldCols, int newCols) {
  149. final newLineLengths = List<int>.empty(growable: true);
  150. var cellsNeeded = 0;
  151. for (int i = 0; i < wrappedLines.length; i++) {
  152. cellsNeeded += ReflowStrategy.getWrappedLineTrimmedLengthFromLines(
  153. wrappedLines, i, oldCols);
  154. }
  155. // Use srcCol and srcLine to find the new wrapping point, use that to get the cellsAvailable and
  156. // linesNeeded
  157. var srcCol = 0;
  158. var srcLine = 0;
  159. var cellsAvailable = 0;
  160. while (cellsAvailable < cellsNeeded) {
  161. if (cellsNeeded - cellsAvailable < newCols) {
  162. // Add the final line and exit the loop
  163. newLineLengths.add(cellsNeeded - cellsAvailable);
  164. break;
  165. }
  166. srcCol += newCols;
  167. final oldTrimmedLength =
  168. ReflowStrategy.getWrappedLineTrimmedLengthFromLines(
  169. wrappedLines, srcLine, oldCols);
  170. if (srcCol > oldTrimmedLength) {
  171. srcCol -= oldTrimmedLength;
  172. srcLine++;
  173. }
  174. bool endsWithWide = wrappedLines[srcLine].cellGetWidth(srcCol - 1) == 2;
  175. if (endsWithWide) {
  176. srcCol--;
  177. }
  178. int lineLength = endsWithWide ? newCols - 1 : newCols;
  179. newLineLengths.add(lineLength);
  180. cellsAvailable += lineLength;
  181. }
  182. return newLineLengths;
  183. }
  184. }
  185. class InsertionSet {
  186. List<BufferLine>? lines;
  187. int start = 0;
  188. bool isNull = false;
  189. static InsertionSet nul = InsertionSet()..isNull = true;
  190. }