render method

void render(
  1. Buffer backBuffer,
  2. StringSink out
)

Diffs backBuffer against _frontBuffer and writes minimal ANSI escape sequences to out.

Implementation

void render(Buffer backBuffer, StringSink out) {
  Tracer.record(_traceRenderId, Phase.begin);
  try {
    bool sizeChanged = _firstFrame;
    _firstFrame = false;

    if (mode == RenderingMode.inline) {
      if (_lastHeight > 0) {
        // Move cursor back up to the top-left of the inline block
        out.write('\x1b[${_lastHeight}F');
      } else {
        // First frame: move to the next line to avoid printing TUI inline on the command prompt line
        out.write('\n\r');
      }
      if (backBuffer.width != _frontBuffer.width ||
          backBuffer.height != _frontBuffer.height) {
        _frontBuffer.resize(backBuffer.width, backBuffer.height);
        sizeChanged = true;
      }
    } else {
      if (backBuffer.width != _frontBuffer.width ||
          backBuffer.height != _frontBuffer.height ||
          sizeChanged) {
        _frontBuffer.resize(backBuffer.width, backBuffer.height);
        sizeChanged = true;
        // Clear screen and reset cursor position to top-left
        out.write('\x1b[2J\x1b[H');
      }
    }

    Style activeStyle = Style.empty;
    int cursorX = 0;
    int cursorY = 0;

    final width = backBuffer.width;
    final height = backBuffer.height;
    final backCells = backBuffer.cells;
    final frontCells = _frontBuffer.cells;

    for (var y = 0; y < height; y++) {
      var x = 0;
      final rowOffset = y * width;
      while (x < width) {
        final idx = rowOffset + x;
        final backCell = backCells[idx];
        final frontCell = frontCells[idx];

        final changed =
            sizeChanged ||
            (backCell.char != frontCell.char ||
                !_styleEquals(backCell.style, frontCell.style));

        if (changed) {
          final runStart = x;
          var runEnd = x;
          // Find the end of the contiguous run of changed cells in the current row
          while (runEnd < width) {
            final cell = backCells[rowOffset + runEnd];
            final fCell = frontCells[rowOffset + runEnd];
            if (sizeChanged ||
                cell.char != fCell.char ||
                !_styleEquals(cell.style, fCell.style)) {
              runEnd++;
            } else {
              break;
            }
          }

          // Move cursor to (runStart, y) relative or absolute
          if (mode == RenderingMode.inline) {
            _moveCursorRelative(out, cursorX, cursorY, runStart, y);
            cursorX = runStart;
            cursorY = y;
          } else {
            out.write('\x1b[${y + 1};${runStart + 1}H');
          }

          if (activeStyle != Style.empty) {
            out.write('\x1b[0m');
            activeStyle = Style.empty;
          }

          // Render each cell in the run
          for (var rx = runStart; rx < runEnd; rx++) {
            final cell = backCells[rowOffset + rx];
            activeStyle = _writeStyleTransition(out, activeStyle, cell.style);
            out.write(cell.char);
            final fCell = frontCells[rowOffset + rx];
            fCell.char = cell.char;
            fCell.style = cell.style;
            if (mode == RenderingMode.inline) {
              cursorX++;
            }
          }
          x = runEnd;
        } else {
          x++;
        }
      }
    }

    if (activeStyle != Style.empty) {
      out.write('\x1b[0m');
    }

    if (mode == RenderingMode.inline) {
      // Position cursor at the beginning of the line immediately following the inline block
      _moveCursorRelative(out, cursorX, cursorY, 0, backBuffer.height);
      _lastHeight = backBuffer.height;
    }
  } finally {
    Tracer.record(_traceRenderId, Phase.end);
  }
}