render method

  1. @override
void render(
  1. Buffer newbuf
)
override

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

@override
void render(Buffer newbuf) {
  metrics.beginFrame();
  _arena.reset();
  _deferredRetainedGraphics.clear();
  _deferredDisplayPayloads.clear();

  _curbuf ??= Buffer.create(newbuf.width(), newbuf.height());

  if (_bufferContainsSixelDisplay(newbuf) ||
      (_curbuf != null && _bufferContainsSixelDisplay(_curbuf!))) {
    erase();
  }

  // Detect stale content: cells that exist in _curbuf from a previous frame
  // but are not marked dirty in newbuf. This happens when content (e.g. an
  // overlay) is removed — the new buffer has default empty cells at those
  // positions but never explicitly wrote them, so they lack dirty bits.
  // Without this pass the tile-based diff would skip those cells, leaving
  // artifacts on screen.
  _markStaleCells(newbuf);

  final touchedLines = _dirtyTouched(newbuf);
  if (!_clear && touchedLines == 0) {
    metrics.endFrame(skipped: true);
    return;
  }

  final useSync = synchronizedOutput();
  if (useSync) {
    _buf.write(UvAnsi.beginSynchronizedUpdate);
  }

  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 &&
        !_isWindows) {
      _scrollOptimize(newbuf);
    }

    var nonEmpty = fullscreen()
        ? (curHeight < newHeight ? curHeight : newHeight)
        : newHeight;
    nonEmpty = _clearBottom(newbuf, nonEmpty);
    final density = _tileDensityForTransform(newbuf, touchedLines, nonEmpty);
    if (density != null) {
      _transformDirtyTiles(newbuf, nonEmpty, density);
    } else {
      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?.isDirty ?? false) ||
            (i < newbuf.dirtyRows.length && newbuf.dirtyRows[i]);
        if (shouldTransform) {
          _transformLine(newbuf, i);
        }
        newbuf.clearDirtyLine(i);
        _curbuf!.clearDirtyLine(i);
      }
    }
  }

  if (!fullscreen() && _scrollHeight < newHeight - 1) {
    _move(newbuf, 0, newHeight - 1);
  }

  // Sync dirty markers.
  newbuf.clearDirtyTracking();
  _curbuf!.clearDirtyTracking();

  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;
        for (var x = 0; x < dstLine.length && x < src.length; x++) {
          dstLine.replaceWithClone(x, src[x]);
        }
      }
    }
  }

  // Reset pen after rendering to avoid style/link bleed.
  _updatePen(null);
  _flushDeferredRetainedGraphics(newbuf);
  _flushDeferredDisplayPayloads();
  if (useSync) {
    _buf.write(UvAnsi.endSynchronizedUpdate);
  }

  metrics.endFrame();
}