render method

void render(
  1. Buffer newbuf
)

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();
}