reflow_strategy_narrower.dart 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  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. int 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. int lastLineLength = wrappedLines.last.getTrimmedLength(oldCols);
  36. final destLineLengths =
  37. _getNewLineLengths(wrappedLines, oldCols, newCols);
  38. int 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. int destLineIndex =
  54. destLineLengths.length - 1; // Math.floor(cellsNeeded / newCols);
  55. int destCol = destLineLengths[destLineIndex]; // cellsNeeded % newCols;
  56. if (destCol == 0) {
  57. destLineIndex--;
  58. destCol = destLineLengths[destLineIndex];
  59. }
  60. int srcLineIndex = wrappedLines.length - linesToAdd - 1;
  61. int srcCol = lastLineLength;
  62. while (srcLineIndex >= 0) {
  63. int 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) destCol = destLineLengths[destLineIndex];
  70. }
  71. srcCol -= cellsToCopy;
  72. if (srcCol == 0) {
  73. srcLineIndex--;
  74. int wrappedLinesIndex = max(srcLineIndex, 0);
  75. srcCol = ReflowStrategy.getWrappedLineTrimmedLengthFromLines(
  76. wrappedLines, wrappedLinesIndex, oldCols);
  77. }
  78. }
  79. // Null out the end of the line ends if a wide character wrapped to the following line
  80. for (int i = 0; i < wrappedLines.length; i++) {
  81. if (destLineLengths[i] < newCols) {
  82. wrappedLines[i].removeRange(destLineLengths[i], oldCols);
  83. }
  84. }
  85. buffer.adjustSavedCursor(0, linesToAdd);
  86. }
  87. rearrange(toInsert, countToInsert);
  88. }
  89. void rearrange(List<InsertionSet> toInsert, int countToInsert) {
  90. // Rearrange lines in the buffer if there are any insertions, this is done at the end rather
  91. // than earlier so that it's a single O(n) pass through the buffer, instead of O(n^2) from many
  92. // costly calls to CircularList.splice.
  93. if (toInsert.length > 0) {
  94. // Record original lines so they don't get overridden when we rearrange the list
  95. CircularList<BufferLine> originalLines =
  96. new CircularList<BufferLine>(buffer.lines.maxLength);
  97. for (int i = 0; i < buffer.lines.length; i++) {
  98. originalLines.push(buffer.lines[i]);
  99. }
  100. int originalLinesLength = buffer.lines.length;
  101. int originalLineIndex = originalLinesLength - 1;
  102. int nextToInsertIndex = 0;
  103. var nextToInsert = toInsert[nextToInsertIndex];
  104. buffer.lines.length =
  105. min(buffer.lines.maxLength, buffer.lines.length + countToInsert);
  106. int countInsertedSoFar = 0;
  107. for (int i = min(buffer.lines.maxLength - 1,
  108. originalLinesLength + countToInsert - 1);
  109. i >= 0;
  110. i--) {
  111. if (!nextToInsert.isNull &&
  112. nextToInsert.start > originalLineIndex + countInsertedSoFar) {
  113. // Insert extra lines here, adjusting i as needed
  114. for (int nextI = nextToInsert.lines!.length - 1;
  115. nextI >= 0;
  116. nextI--) {
  117. if (i < 0) {
  118. // if we reflow and the content has to be scrolled back past the beginning
  119. // of the buffer then we end up loosing those lines
  120. break;
  121. }
  122. buffer.lines[i--] = nextToInsert.lines![nextI];
  123. }
  124. i++;
  125. countInsertedSoFar += nextToInsert.lines!.length;
  126. if (nextToInsertIndex < toInsert.length - 1) {
  127. nextToInsert = toInsert[++nextToInsertIndex];
  128. } else {
  129. nextToInsert = InsertionSet.nul;
  130. }
  131. } else {
  132. buffer.lines[i] = originalLines[originalLineIndex--];
  133. }
  134. }
  135. }
  136. }
  137. /// <summary>
  138. /// Gets the new line lengths for a given wrapped line. The purpose of this function it to pre-
  139. /// compute the wrapping points since wide characters may need to be wrapped onto the following line.
  140. /// This function will return an array of numbers of where each line wraps to, the resulting array
  141. /// will only contain the values `newCols` (when the line does not end with a wide character) and
  142. /// `newCols - 1` (when the line does end with a wide character), except for the last value which
  143. /// will contain the remaining items to fill the line.
  144. /// Calling this with a `newCols` value of `1` will lock up.
  145. /// </summary>
  146. List<int> _getNewLineLengths(
  147. List<BufferLine> wrappedLines, int oldCols, int newCols) {
  148. final newLineLengths = List<int>.empty(growable: true);
  149. int cellsNeeded = 0;
  150. for (int i = 0; i < wrappedLines.length; i++) {
  151. cellsNeeded += ReflowStrategy.getWrappedLineTrimmedLengthFromLines(
  152. wrappedLines, i, oldCols);
  153. }
  154. // Use srcCol and srcLine to find the new wrapping point, use that to get the cellsAvailable and
  155. // linesNeeded
  156. int srcCol = 0;
  157. int srcLine = 0;
  158. int cellsAvailable = 0;
  159. while (cellsAvailable < cellsNeeded) {
  160. if (cellsNeeded - cellsAvailable < newCols) {
  161. // Add the final line and exit the loop
  162. newLineLengths.add(cellsNeeded - cellsAvailable);
  163. break;
  164. }
  165. srcCol += newCols;
  166. int oldTrimmedLength =
  167. ReflowStrategy.getWrappedLineTrimmedLengthFromLines(
  168. wrappedLines, srcLine, oldCols);
  169. if (srcCol > oldTrimmedLength) {
  170. srcCol -= oldTrimmedLength;
  171. srcLine++;
  172. }
  173. bool endsWithWide = wrappedLines[srcLine].cellGetWidth(srcCol - 1) == 2;
  174. if (endsWithWide) {
  175. srcCol--;
  176. }
  177. int lineLength = endsWithWide ? newCols - 1 : newCols;
  178. newLineLengths.add(lineLength);
  179. cellsAvailable += lineLength;
  180. }
  181. return newLineLengths;
  182. }
  183. }
  184. class InsertionSet {
  185. List<BufferLine>? lines;
  186. int start = 0;
  187. bool isNull = false;
  188. static InsertionSet nul = InsertionSet()..isNull = true;
  189. }