mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-16 03:01:56 +00:00
106 lines
3.2 KiB
TypeScript
106 lines
3.2 KiB
TypeScript
import type { Component } from "../tui.js";
|
|
import { applyBackgroundToLine, visibleWidth, wrapTextWithAnsi } from "../utils.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
|
|
private customBgFn?: (text: string) => string;
|
|
|
|
// Cache for rendered output
|
|
private cachedText?: string;
|
|
private cachedWidth?: number;
|
|
private cachedLines?: string[];
|
|
|
|
constructor(text: string = "", paddingX: number = 1, paddingY: number = 1, customBgFn?: (text: string) => string) {
|
|
this.text = text;
|
|
this.paddingX = paddingX;
|
|
this.paddingY = paddingY;
|
|
this.customBgFn = customBgFn;
|
|
}
|
|
|
|
setText(text: string): void {
|
|
this.text = text;
|
|
this.cachedText = undefined;
|
|
this.cachedWidth = undefined;
|
|
this.cachedLines = undefined;
|
|
}
|
|
|
|
setCustomBgFn(customBgFn?: (text: string) => string): void {
|
|
this.customBgFn = customBgFn;
|
|
this.cachedText = undefined;
|
|
this.cachedWidth = undefined;
|
|
this.cachedLines = undefined;
|
|
}
|
|
|
|
invalidate(): void {
|
|
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;
|
|
}
|
|
|
|
// Don't render anything if there's no actual text
|
|
if (!this.text || this.text.trim() === "") {
|
|
const result: string[] = [];
|
|
this.cachedText = this.text;
|
|
this.cachedWidth = width;
|
|
this.cachedLines = result;
|
|
return result;
|
|
}
|
|
|
|
// Replace tabs with 3 spaces
|
|
const normalizedText = this.text.replace(/\t/g, " ");
|
|
|
|
// Calculate content width (subtract left/right margins)
|
|
const contentWidth = Math.max(1, width - this.paddingX * 2);
|
|
|
|
// Wrap text (this preserves ANSI codes but does NOT pad)
|
|
const wrappedLines = wrapTextWithAnsi(normalizedText, contentWidth);
|
|
|
|
// Add margins and background to each line
|
|
const leftMargin = " ".repeat(this.paddingX);
|
|
const rightMargin = " ".repeat(this.paddingX);
|
|
const contentLines: string[] = [];
|
|
|
|
for (const line of wrappedLines) {
|
|
// Add margins
|
|
const lineWithMargins = leftMargin + line + rightMargin;
|
|
|
|
// Apply background if specified (this also pads to full width)
|
|
if (this.customBgFn) {
|
|
contentLines.push(applyBackgroundToLine(lineWithMargins, width, this.customBgFn));
|
|
} else {
|
|
// No background - just pad to width with spaces
|
|
const visibleLen = visibleWidth(lineWithMargins);
|
|
const paddingNeeded = Math.max(0, width - visibleLen);
|
|
contentLines.push(lineWithMargins + " ".repeat(paddingNeeded));
|
|
}
|
|
}
|
|
|
|
// Add top/bottom padding (empty lines)
|
|
const emptyLine = " ".repeat(width);
|
|
const emptyLines: string[] = [];
|
|
for (let i = 0; i < this.paddingY; i++) {
|
|
const line = this.customBgFn ? applyBackgroundToLine(emptyLine, width, this.customBgFn) : emptyLine;
|
|
emptyLines.push(line);
|
|
}
|
|
|
|
const result = [...emptyLines, ...contentLines, ...emptyLines];
|
|
|
|
// Update cache
|
|
this.cachedText = this.text;
|
|
this.cachedWidth = width;
|
|
this.cachedLines = result;
|
|
|
|
return result.length > 0 ? result : [""];
|
|
}
|
|
}
|