mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 17:00:59 +00:00
- Create Terminal interface abstracting stdin/stdout operations for dependency injection - Implement ProcessTerminal for production use with process.stdin/stdout - Implement VirtualTerminal using @xterm/headless for accurate terminal emulation in tests - Fix TypeScript imports for @xterm/headless module - Move all component files to src/components/ directory for better organization - Add comprehensive test suite with async/await patterns for proper render timing - Fix critical TUI differential rendering bug when components grow in height - Issue: Old content wasn't properly cleared when component line count increased - Solution: Clear each old line individually before redrawing, ensure cursor at line start - Add test verifying terminal content preservation and text editor growth behavior - Update tsconfig.json to include test files in type checking - Add benchmark test comparing single vs double buffer performance The implementation successfully reduces flicker by only updating changed lines rather than clearing entire sections. Both TUI implementations maintain the same interface for backward compatibility.
115 lines
No EOL
3.4 KiB
TypeScript
115 lines
No EOL
3.4 KiB
TypeScript
#!/usr/bin/env npx tsx
|
|
import {
|
|
Container,
|
|
LoadingAnimation,
|
|
TextComponent,
|
|
TextEditor,
|
|
TUI,
|
|
WhitespaceComponent,
|
|
} from "../src/index.js";
|
|
import chalk from "chalk";
|
|
|
|
|
|
|
|
/**
|
|
* Test the new smart double-buffered TUI implementation
|
|
*/
|
|
async function main() {
|
|
const ui = new TUI();
|
|
|
|
// Track render timings
|
|
let renderCount = 0;
|
|
let totalRenderTime = 0n;
|
|
const renderTimings: bigint[] = [];
|
|
|
|
// Monkey-patch requestRender to measure performance
|
|
const originalRequestRender = ui.requestRender.bind(ui);
|
|
ui.requestRender = function() {
|
|
const startTime = process.hrtime.bigint();
|
|
originalRequestRender();
|
|
process.nextTick(() => {
|
|
const endTime = process.hrtime.bigint();
|
|
const duration = endTime - startTime;
|
|
renderTimings.push(duration);
|
|
totalRenderTime += duration;
|
|
renderCount++;
|
|
});
|
|
};
|
|
|
|
// Add header
|
|
const header = new TextComponent(
|
|
chalk.bold.green("Smart Double Buffer TUI Test") + "\n" +
|
|
chalk.dim("Testing new implementation with component-level caching and smart diffing") + "\n" +
|
|
chalk.dim("Press CTRL+C to exit"),
|
|
{ bottom: 1 }
|
|
);
|
|
ui.addChild(header);
|
|
|
|
// Add container for animation and editor
|
|
const container = new Container();
|
|
|
|
// Add loading animation (should NOT cause flicker with smart diffing)
|
|
const animation = new LoadingAnimation(ui);
|
|
container.addChild(animation);
|
|
|
|
// Add some spacing
|
|
container.addChild(new WhitespaceComponent(1));
|
|
|
|
// Add text editor
|
|
const editor = new TextEditor();
|
|
editor.setText("Type here to test the text editor.\n\nWith smart diffing, only changed lines are redrawn!\n\nThe animation above updates every 80ms but the editor stays perfectly still.");
|
|
container.addChild(editor);
|
|
|
|
// Add the container to UI
|
|
ui.addChild(container);
|
|
|
|
// Add performance stats display
|
|
const statsComponent = new TextComponent("", { top: 1 });
|
|
ui.addChild(statsComponent);
|
|
|
|
// Update stats every second
|
|
const statsInterval = setInterval(() => {
|
|
if (renderCount > 0) {
|
|
const avgRenderTime = Number(totalRenderTime / BigInt(renderCount)) / 1_000_000; // Convert to ms
|
|
const lastRenderTime = renderTimings.length > 0
|
|
? Number(renderTimings[renderTimings.length - 1]) / 1_000_000
|
|
: 0;
|
|
const avgLinesRedrawn = ui.getAverageLinesRedrawn();
|
|
|
|
statsComponent.setText(
|
|
chalk.yellow(`Performance Stats:`) + "\n" +
|
|
chalk.dim(`Renders: ${renderCount} | Avg Time: ${avgRenderTime.toFixed(2)}ms | Last: ${lastRenderTime.toFixed(2)}ms`) + "\n" +
|
|
chalk.dim(`Lines Redrawn: ${ui.getLinesRedrawn()} total | Avg per render: ${avgLinesRedrawn.toFixed(1)}`)
|
|
);
|
|
}
|
|
}, 1000);
|
|
|
|
// Set focus to the editor
|
|
ui.setFocus(editor);
|
|
|
|
// Handle global keypresses
|
|
ui.onGlobalKeyPress = (data: string) => {
|
|
// CTRL+C to exit
|
|
if (data === "\x03") {
|
|
animation.stop();
|
|
clearInterval(statsInterval);
|
|
ui.stop();
|
|
console.log("\n" + chalk.green("Exited double-buffer test"));
|
|
console.log(chalk.dim(`Total renders: ${renderCount}`));
|
|
console.log(chalk.dim(`Average render time: ${renderCount > 0 ? (Number(totalRenderTime / BigInt(renderCount)) / 1_000_000).toFixed(2) : 0}ms`));
|
|
console.log(chalk.dim(`Total lines redrawn: ${ui.getLinesRedrawn()}`));
|
|
console.log(chalk.dim(`Average lines redrawn per render: ${ui.getAverageLinesRedrawn().toFixed(1)}`));
|
|
process.exit(0);
|
|
}
|
|
return true; // Forward other keys to focused component
|
|
};
|
|
|
|
// Start the UI
|
|
ui.start();
|
|
}
|
|
|
|
// Run the test
|
|
main().catch((error) => {
|
|
console.error("Error:", error);
|
|
process.exit(1);
|
|
}); |