render method

  1. @override
void render(
  1. Object view
)
override

Renders the view to the terminal.

view is the string representation of the current UI state, or a View object containing metadata.

Implementation

@override
void render(Object view) {
  _metrics.beginFrame();

  final String content = switch (view) {
    String s => s,
    View v => v.content,
    _ => view.toString(),
  };

  // Frame rate limiting using Stopwatch (immune to clock adjustments)
  if (_frameStopwatch.isRunning) {
    if (_frameStopwatch.elapsed < _options.frameTime) {
      _metrics.endFrame(skipped: true);
      return;
    }
  }

  // Count new lines
  int newLineCount;
  if (content.isEmpty) {
    newLineCount = 0;
  } else {
    final segments = content.split('\n');
    newLineCount = segments.length - (content.endsWith('\n') ? 1 : 0);
  }

  // Build the entire frame in a single buffer to prevent flashing.
  // Multiple terminal.write() calls allow the terminal to flush between
  // writes, causing visible cursor movement before content appears.
  final frameBuffer = StringBuffer();

  // Hide cursor during render to prevent cursor flicker
  if (terminal.supportsAnsi) {
    frameBuffer.write('\x1b[?25l'); // Hide cursor
  }

  // Move cursor back to start position if we've rendered before
  if (_hasRendered && _lastLineCount > 0 && terminal.supportsAnsi) {
    if (_lastLineCount > 0) {
      frameBuffer.write('\x1b[${_lastLineCount}A'); // Move up N lines
    }
    frameBuffer.write('\r'); // Return to column 1
  }

  // Write content with clear-to-end-of-line after each line
  final output = _options.ansiCompress ? compressAnsi(content) : content;
  _appendContentWithClearToEol(frameBuffer, output);

  // If new content has fewer lines, clear the extra old lines
  if (_hasRendered &&
      newLineCount < _lastLineCount &&
      terminal.supportsAnsi) {
    final extraLines = _lastLineCount - newLineCount;
    for (var i = 0; i < extraLines; i++) {
      frameBuffer.write('\x1b[K'); // Clear line from cursor
      frameBuffer.write('\n'); // Move to next line
    }
    // Move back up to where content ended
    if (extraLines > 0) {
      frameBuffer.write('\x1b[${extraLines}A');
    }
  }

  // Show cursor after render (unless hideCursor option is set)
  if (terminal.supportsAnsi && !_options.hideCursor) {
    frameBuffer.write('\x1b[?25h'); // Show cursor
  }

  // Single atomic write to terminal - prevents flashing
  terminal.write(frameBuffer.toString());

  _lastLineCount = newLineCount;
  // Reset and start the stopwatch for next frame timing
  _frameStopwatch.reset();
  _frameStopwatch.start();
  _hasRendered = true;
  _metrics.endFrame();
}