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