render method
Renders the screen buffer to the terminal.
Diffs newbuf against the previously rendered buffer and emits the
minimal ANSI escape sequences needed to update the terminal. Skipped
frames (no dirty lines and no pending clear) are recorded as such in
metrics.
Implementation
void render(Buffer newbuf) {
metrics.beginFrame();
final touchedLines = _dirtyTouched(newbuf);
if (!_clear && touchedLines == 0) {
metrics.endFrame(skipped: true);
return;
}
final useSync = synchronizedOutput();
if (useSync) {
_buf.write(UvAnsi.beginSynchronizedUpdate);
}
_curbuf ??= Buffer.create(newbuf.width(), newbuf.height());
final newWidth = newbuf.width();
final newHeight = newbuf.height();
final curWidth = _curbuf!.width();
final curHeight = _curbuf!.height();
final sameSize = curWidth == newWidth && curHeight == newHeight;
if (!sameSize) {
_oldhash = const [];
_newhash = const [];
}
final partialClear =
!fullscreen() &&
_cur.x != -1 &&
_cur.y != -1 &&
curWidth == newWidth &&
curHeight > 0 &&
curHeight > newHeight;
if (!_clear && partialClear) {
_clearBelow(newbuf, _clearBlank(), newHeight - 1);
}
if (_clear) {
_clearUpdate(newbuf);
_clear = false;
} else if (touchedLines > 0) {
if ((_flags & _Flag.scrollOptim) != 0 &&
fullscreen() &&
sameSize &&
!Platform.isWindows) {
_scrollOptimize(newbuf);
}
var nonEmpty = fullscreen()
? (curHeight < newHeight ? curHeight : newHeight)
: newHeight;
nonEmpty = _clearBottom(newbuf, nonEmpty);
for (var i = 0; i < nonEmpty && i < newHeight; i++) {
final ld = (newbuf.touched.isEmpty || i >= newbuf.touched.length)
? null
: newbuf.touched[i];
final shouldTransform =
newbuf.touched.isEmpty ||
i >= newbuf.touched.length ||
(ld != null && (ld.firstCell != -1 || ld.lastCell != -1));
if (shouldTransform) {
_transformLine(newbuf, i);
}
if (i < newbuf.touched.length) {
newbuf.touched[i] = const LineData(firstCell: -1, lastCell: -1);
}
if (i < _curbuf!.touched.length) {
_curbuf!.touched[i] = const LineData(firstCell: -1, lastCell: -1);
}
}
}
if (!fullscreen() && _scrollHeight < newHeight - 1) {
_move(newbuf, 0, newHeight - 1);
}
// Sync touched markers.
newbuf.touched = List<LineData?>.generate(
newHeight,
(_) => const LineData(firstCell: -1, lastCell: -1),
);
_curbuf!.touched = List<LineData?>.generate(
_curbuf!.height(),
(_) => const LineData(firstCell: -1, lastCell: -1),
);
if (curWidth != newWidth || curHeight != newHeight) {
_curbuf!.resize(newWidth, newHeight);
final start = curHeight <= 0 ? 0 : curHeight - 1;
for (var i = start; i < newHeight; i++) {
final srcLine = newbuf.line(i);
final dstLine = _curbuf!.line(i);
if (srcLine != null && dstLine != null) {
final src = srcLine.cells;
final dst = dstLine.cells;
for (var x = 0; x < dst.length && x < src.length; x++) {
dst[x] = src[x].clone();
}
}
}
}
// Reset pen after rendering to avoid style/link bleed.
_updatePen(null);
if (useSync) {
_buf.write(UvAnsi.endSynchronizedUpdate);
}
metrics.endFrame();
}