reflow_strategy_narrower.dart 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  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 buffer insert events and then play them back backwards so that the indexes are
  95. // correct
  96. List<int> insertEvents = List<int>.empty(growable: true);
  97. // Record original lines so they don't get overridden when we rearrange the list
  98. CircularList<BufferLine> originalLines =
  99. new CircularList<BufferLine>(buffer.lines.maxLength);
  100. for (int i = 0; i < buffer.lines.length; i++) {
  101. originalLines.push(buffer.lines[i]!);
  102. }
  103. int originalLinesLength = buffer.lines.length;
  104. int originalLineIndex = originalLinesLength - 1;
  105. int nextToInsertIndex = 0;
  106. var nextToInsert = toInsert[nextToInsertIndex];
  107. buffer.lines.length =
  108. min(buffer.lines.maxLength, buffer.lines.length + countToInsert);
  109. int countInsertedSoFar = 0;
  110. for (int i = min(buffer.lines.maxLength - 1,
  111. originalLinesLength + countToInsert - 1);
  112. i >= 0;
  113. i--) {
  114. if (!nextToInsert.isNull &&
  115. nextToInsert.start > originalLineIndex + countInsertedSoFar) {
  116. // Insert extra lines here, adjusting i as needed
  117. for (int nextI = nextToInsert.lines!.length - 1;
  118. nextI >= 0;
  119. nextI--) {
  120. if (i < 0) {
  121. // if we reflow and the content has to be scrolled back past the beginning
  122. // of the buffer then we end up loosing those lines
  123. break;
  124. }
  125. buffer.lines[i--] = nextToInsert.lines![nextI];
  126. }
  127. i++;
  128. // Create insert events for later
  129. //insertEvents.Add ({
  130. // index: originalLineIndex + 1,
  131. // amount: nextToInsert.newLines.length
  132. //});
  133. countInsertedSoFar += nextToInsert.lines!.length;
  134. if (nextToInsertIndex < toInsert.length - 1) {
  135. nextToInsert = toInsert[++nextToInsertIndex];
  136. } else {
  137. nextToInsert = InsertionSet.nul;
  138. }
  139. } else {
  140. buffer.lines[i] = originalLines[originalLineIndex--];
  141. }
  142. }
  143. /*
  144. // Update markers
  145. let insertCountEmitted = 0;
  146. for (let i = insertEvents.length - 1; i >= 0; i--) {
  147. insertEvents [i].index += insertCountEmitted;
  148. this.lines.onInsertEmitter.fire (insertEvents [i]);
  149. insertCountEmitted += insertEvents [i].amount;
  150. }
  151. const amountToTrim = Math.max (0, originalLinesLength + countToInsert - this.lines.maxLength);
  152. if (amountToTrim > 0) {
  153. this.lines.onTrimEmitter.fire (amountToTrim);
  154. }
  155. */
  156. }
  157. }
  158. /// <summary>
  159. /// Gets the new line lengths for a given wrapped line. The purpose of this function it to pre-
  160. /// compute the wrapping points since wide characters may need to be wrapped onto the following line.
  161. /// This function will return an array of numbers of where each line wraps to, the resulting array
  162. /// will only contain the values `newCols` (when the line does not end with a wide character) and
  163. /// `newCols - 1` (when the line does end with a wide character), except for the last value which
  164. /// will contain the remaining items to fill the line.
  165. /// Calling this with a `newCols` value of `1` will lock up.
  166. /// </summary>
  167. List<int> _getNewLineLengths(
  168. List<BufferLine> wrappedLines, int oldCols, int newCols) {
  169. final newLineLengths = List<int>.empty(growable: true);
  170. int cellsNeeded = 0;
  171. for (int i = 0; i < wrappedLines.length; i++) {
  172. cellsNeeded += ReflowStrategy.getWrappedLineTrimmedLengthFromLines(
  173. wrappedLines, i, oldCols);
  174. }
  175. // Use srcCol and srcLine to find the new wrapping point, use that to get the cellsAvailable and
  176. // linesNeeded
  177. int srcCol = 0;
  178. int srcLine = 0;
  179. int cellsAvailable = 0;
  180. while (cellsAvailable < cellsNeeded) {
  181. if (cellsNeeded - cellsAvailable < newCols) {
  182. // Add the final line and exit the loop
  183. newLineLengths.add(cellsNeeded - cellsAvailable);
  184. break;
  185. }
  186. srcCol += newCols;
  187. int oldTrimmedLength =
  188. ReflowStrategy.getWrappedLineTrimmedLengthFromLines(
  189. wrappedLines, srcLine, oldCols);
  190. if (srcCol > oldTrimmedLength) {
  191. srcCol -= oldTrimmedLength;
  192. srcLine++;
  193. }
  194. bool endsWithWide = wrappedLines[srcLine].cellGetWidth(srcCol - 1) == 2;
  195. if (endsWithWide) {
  196. srcCol--;
  197. }
  198. int lineLength = endsWithWide ? newCols - 1 : newCols;
  199. newLineLengths.add(lineLength);
  200. cellsAvailable += lineLength;
  201. }
  202. return newLineLengths;
  203. }
  204. }
  205. class InsertionSet {
  206. List<BufferLine>? lines;
  207. int start = 0;
  208. bool isNull = false;
  209. static InsertionSet nul = InsertionSet()..isNull = true;
  210. }