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 layout; final int removedCount; } class BufferReflow { BufferReflow(this._buffer, this._emptyCellAttr); final Buffer _buffer; final CellAttr _emptyCellAttr; void doReflow(int colsBefore, int colsAfter) { if(colsBefore == colsAfter) { return; } if(colsAfter > colsBefore) { //got larger _reflowLarger(colsBefore, colsAfter); } else { //got smaller _reflowSmaller(colsBefore, colsAfter); } } void _reflowLarger(int colsBefore, int colsAfter) { var toRemove = _reflowLargerGetLinesToRemove(colsBefore, colsAfter); if (toRemove.length > 0) { var newLayoutResult = _reflowLargerCreateNewLayout(_buffer.lines, toRemove); _reflowLargerApplyNewLayout(_buffer.lines, newLayoutResult.layout); _reflowLargerAdjustViewport(colsBefore, colsAfter, newLayoutResult.removedCount); } } void _reflowSmaller(int colsBefore, int colsAfter) { } void _reflowLargerAdjustViewport(int colsBefore, int colsAfter, int countRemoved) { // Adjust viewport based on number of items removed var viewportAdjustments = countRemoved; while (viewportAdjustments-- > 0) { //viewport is at the top 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.add(new BufferLine(numOfCells: colsAfter, attr: _emptyCellAttr)); } } else { //Nothing to do here due to the way scrolling is handled // //user didn't scroll // if (this.ydisp === this.ybase) { // //scroll viewport according to... // this.ydisp--; // } // //base window // this.ybase--; } } //TODO: adjust buffer content to max length _buffer.adjustSavedCursor(0, -countRemoved); } void _reflowLargerApplyNewLayout(List lines, List newLayout) { var newLayoutLines = List.empty(); for (int i = 0; i < newLayout.length; i++) { newLayoutLines.add(lines[newLayout [i]]); } // Rearrange the list for (int i = 0; i < newLayoutLines.length; i++) { lines[i] = newLayoutLines[i]; } lines.removeRange(newLayoutLines.length, lines.length - 1); } LayoutResult _reflowLargerCreateNewLayout(List lines, List toRemove) { var layout = List.empty(); // First iterate through the list and get the actual indexes to use for rows int nextToRemoveIndex = 0; int nextToRemoveStart = toRemove [nextToRemoveIndex]; int 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; if (nextToRemoveIndex < toRemove.length - 1) nextToRemoveStart = toRemove [++nextToRemoveIndex]; } else { layout.add(i); } } return LayoutResult (layout, countRemovedSoFar); } List _reflowLargerGetLinesToRemove(int colsBefore, int colsAfter) { List toRemove = List.empty(); for (int y = 0; y < _buffer.lines.length - 1; y++) { // Check if this row is wrapped int i = y; BufferLine nextLine = _buffer.lines[++i]; if (!nextLine.isWrapped) { continue; } // Check how many lines it's wrapped for List wrappedLines = List.empty(); wrappedLines.add(_buffer.lines[y]); while (i < _buffer.lines.length && nextLine.isWrapped) { wrappedLines.add(nextLine); nextLine = _buffer.lines[++i]; } final bufferAbsoluteY = _buffer.cursorY - _buffer.scrollOffsetFromBottom; // 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 int destLineIndex = 0; int destCol = _getWrappedLineTrimmedLengthRow(_buffer.lines, destLineIndex, colsBefore); int srcLineIndex = 1; int srcCol = 0; while (srcLineIndex < wrappedLines.length) { 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); destCol += cellsToCopy; if (destCol == colsAfter) { destLineIndex++; destCol = 0; } srcCol += cellsToCopy; if (srcCol == srcTrimmedTineLength) { srcLineIndex++; srcCol = 0; } // 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); // Null out the end of the last row wrappedLines [destLineIndex - 1].erase (_emptyCellAttr, colsAfter - 1, colsAfter); } } } // Clear out remaining cells or fragments could remain; 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) { countToRemove++; } else { break; } } if (countToRemove > 0) { toRemove.add (y + wrappedLines.length - countToRemove); // index toRemove.add (countToRemove); } y += wrappedLines.length - 1; } return toRemove; } int _getWrappedLineTrimmedLengthRow(List 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) { // If this is the last row in the wrapped line, get the actual trimmed length if (nextLine == null) { 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 followingLineStartsWithWide = nextLine.getWidthAt(0) == 2; if (endsInNull && followingLineStartsWithWide) { return cols - 1; } return cols; } }