mirror of
https://github.com/earendil-works/pi.git
synced 2026-06-18 15:54:04 +08:00
- Remove old TUI implementation and components (LoadingAnimation, MarkdownComponent, TextComponent, TextEditor, WhitespaceComponent) - Rename components-new to components with new API (Loader, Markdown, Text, Editor, Spacer) - Move Text and Input components to separate files in src/components/ - Add render caching to Text component (similar to Markdown) - Add proper ANSI code handling in Text component using stripVTControlCharacters - Update coding-agent to use new TUI API (requires ProcessTerminal, uses custom Editor subclass for key handling) - Remove old test files, keep only chat-simple.ts and virtual-terminal.ts - Update README.md with new minimal API documentation - Switch from tsc to tsgo for type checking - Update package dependencies across monorepo
114 lines
3.0 KiB
TypeScript
114 lines
3.0 KiB
TypeScript
import { stripVTControlCharacters } from "node:util";
|
|
import type { Component } from "../tui.js";
|
|
|
|
/**
|
|
* Text component - displays multi-line text with word wrapping
|
|
*/
|
|
export class Text implements Component {
|
|
private text: string;
|
|
private paddingX: number; // Left/right padding
|
|
private paddingY: number; // Top/bottom padding
|
|
|
|
// Cache for rendered output
|
|
private cachedText?: string;
|
|
private cachedWidth?: number;
|
|
private cachedLines?: string[];
|
|
|
|
constructor(text: string = "", paddingX: number = 1, paddingY: number = 1) {
|
|
this.text = text;
|
|
this.paddingX = paddingX;
|
|
this.paddingY = paddingY;
|
|
}
|
|
|
|
setText(text: string): void {
|
|
this.text = text;
|
|
// Invalidate cache when text changes
|
|
this.cachedText = undefined;
|
|
this.cachedWidth = undefined;
|
|
this.cachedLines = undefined;
|
|
}
|
|
|
|
render(width: number): string[] {
|
|
// Check cache
|
|
if (this.cachedLines && this.cachedText === this.text && this.cachedWidth === width) {
|
|
return this.cachedLines;
|
|
}
|
|
|
|
// Calculate available width for content (subtract horizontal padding)
|
|
const contentWidth = Math.max(1, width - this.paddingX * 2);
|
|
|
|
if (!this.text) {
|
|
const result = [""];
|
|
// Update cache
|
|
this.cachedText = this.text;
|
|
this.cachedWidth = width;
|
|
this.cachedLines = result;
|
|
return result;
|
|
}
|
|
|
|
const lines: string[] = [];
|
|
const textLines = this.text.split("\n");
|
|
|
|
for (const line of textLines) {
|
|
if (line.length <= contentWidth) {
|
|
lines.push(line);
|
|
} else {
|
|
// Word wrap
|
|
const words = line.split(" ");
|
|
let currentLine = "";
|
|
|
|
for (const word of words) {
|
|
if (currentLine.length === 0) {
|
|
currentLine = word;
|
|
} else if (currentLine.length + 1 + word.length <= contentWidth) {
|
|
currentLine += " " + word;
|
|
} else {
|
|
lines.push(currentLine);
|
|
currentLine = word;
|
|
}
|
|
}
|
|
|
|
if (currentLine.length > 0) {
|
|
lines.push(currentLine);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add padding to each line
|
|
const leftPad = " ".repeat(this.paddingX);
|
|
const paddedLines: string[] = [];
|
|
|
|
for (const line of lines) {
|
|
// Calculate visible length (strip ANSI codes)
|
|
const visibleLength = stripVTControlCharacters(line).length;
|
|
// Right padding to fill to width (accounting for left padding and content)
|
|
const rightPadLength = Math.max(0, width - this.paddingX - visibleLength);
|
|
const rightPad = " ".repeat(rightPadLength);
|
|
paddedLines.push(leftPad + line + rightPad);
|
|
}
|
|
|
|
// Add top padding (empty lines)
|
|
const emptyLine = " ".repeat(width);
|
|
const topPadding: string[] = [];
|
|
for (let i = 0; i < this.paddingY; i++) {
|
|
topPadding.push(emptyLine);
|
|
}
|
|
|
|
// Add bottom padding (empty lines)
|
|
const bottomPadding: string[] = [];
|
|
for (let i = 0; i < this.paddingY; i++) {
|
|
bottomPadding.push(emptyLine);
|
|
}
|
|
|
|
// Combine top padding, content, and bottom padding
|
|
const result = [...topPadding, ...paddedLines, ...bottomPadding];
|
|
|
|
// Update cache
|
|
this.cachedText = this.text;
|
|
this.cachedWidth = width;
|
|
this.cachedLines = result;
|
|
|
|
return result.length > 0 ? result : [""];
|
|
}
|
|
}
|