Add padding to Text component and fix padding calculation

- Add paddingX and paddingY support to Text component (default 1, 1)
- Fix padding calculation bug in both Text and Markdown
- Right padding now correctly fills to terminal width
- Remove unused Spacer import from chat-simple
This commit is contained in:
Mario Zechner 2025-11-11 00:22:31 +01:00
parent 5f19dd62c7
commit 9dab1192ad
3 changed files with 45 additions and 18 deletions

View file

@ -111,8 +111,8 @@ export class Markdown implements Component {
for (const line of wrappedLines) { for (const line of wrappedLines) {
// Calculate visible length (strip ANSI codes) // Calculate visible length (strip ANSI codes)
const visibleLength = stripVTControlCharacters(line).length; const visibleLength = stripVTControlCharacters(line).length;
// Right padding to fill to width (accounting for left padding) // Right padding to fill to width (accounting for left padding and content)
const rightPadLength = Math.max(0, width - visibleLength - this.paddingX * 2); const rightPadLength = Math.max(0, width - this.paddingX - visibleLength);
const rightPad = " ".repeat(rightPadLength); const rightPad = " ".repeat(rightPadLength);
// Add left padding, content, and right padding // Add left padding, content, and right padding

View file

@ -56,13 +56,26 @@ export class Container implements Component {
* Text component - displays multi-line text with word wrapping * Text component - displays multi-line text with word wrapping
*/ */
export class Text implements Component { export class Text implements Component {
constructor(private text: string = "") {} private paddingX: number; // Left/right padding
private paddingY: number; // Top/bottom padding
constructor(
private text: string = "",
paddingX: number = 1,
paddingY: number = 1,
) {
this.paddingX = paddingX;
this.paddingY = paddingY;
}
setText(text: string): void { setText(text: string): void {
this.text = text; this.text = text;
} }
render(width: number): string[] { render(width: number): string[] {
// Calculate available width for content (subtract horizontal padding)
const contentWidth = Math.max(1, width - this.paddingX * 2);
if (!this.text) { if (!this.text) {
return [""]; return [""];
} }
@ -71,7 +84,7 @@ export class Text implements Component {
const textLines = this.text.split("\n"); const textLines = this.text.split("\n");
for (const line of textLines) { for (const line of textLines) {
if (line.length <= width) { if (line.length <= contentWidth) {
lines.push(line); lines.push(line);
} else { } else {
// Word wrap // Word wrap
@ -81,7 +94,7 @@ export class Text implements Component {
for (const word of words) { for (const word of words) {
if (currentLine.length === 0) { if (currentLine.length === 0) {
currentLine = word; currentLine = word;
} else if (currentLine.length + 1 + word.length <= width) { } else if (currentLine.length + 1 + word.length <= contentWidth) {
currentLine += " " + word; currentLine += " " + word;
} else { } else {
lines.push(currentLine); lines.push(currentLine);
@ -95,7 +108,33 @@ export class Text implements Component {
} }
} }
return lines.length > 0 ? lines : [""]; // Add padding to each line
const leftPad = " ".repeat(this.paddingX);
const paddedLines: string[] = [];
for (const line of lines) {
const rightPadLength = Math.max(0, width - this.paddingX - line.length);
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];
return result.length > 0 ? result : [""];
} }
} }

View file

@ -6,7 +6,6 @@ import { CombinedAutocompleteProvider } from "../src/autocomplete.js";
import { Editor } from "../src/components-new/editor.js"; import { Editor } from "../src/components-new/editor.js";
import { Loader } from "../src/components-new/loader.js"; import { Loader } from "../src/components-new/loader.js";
import { Markdown } from "../src/components-new/markdown.js"; import { Markdown } from "../src/components-new/markdown.js";
import { Spacer } from "../src/components-new/spacer.js";
import { ProcessTerminal } from "../src/terminal.js"; import { ProcessTerminal } from "../src/terminal.js";
import { Text, TUI } from "../src/tui-new.js"; import { Text, TUI } from "../src/tui-new.js";
@ -74,31 +73,21 @@ editor.onSubmit = (value: string) => {
} }
if (trimmed) { if (trimmed) {
// Mark as responding and disable submit
isResponding = true; isResponding = true;
editor.disableSubmit = true; editor.disableSubmit = true;
// Add user message with custom gray background (similar to Claude.ai)
const userMessage = new Markdown(value, undefined, undefined, { r: 52, g: 53, b: 65 }); const userMessage = new Markdown(value, undefined, undefined, { r: 52, g: 53, b: 65 });
// Insert before the editor (which is last)
const children = tui.children; const children = tui.children;
children.splice(children.length - 1, 0, userMessage); children.splice(children.length - 1, 0, userMessage);
children.splice(children.length - 1, 0, new Spacer());
// Add loader
const loader = new Loader(tui, "Thinking..."); const loader = new Loader(tui, "Thinking...");
const loaderSpacer = new Spacer();
children.splice(children.length - 1, 0, loader); children.splice(children.length - 1, 0, loader);
children.splice(children.length - 1, 0, loaderSpacer);
tui.requestRender(); tui.requestRender();
// Simulate a 1 second delay
setTimeout(() => { setTimeout(() => {
// Remove loader and its spacer
tui.removeChild(loader); tui.removeChild(loader);
tui.removeChild(loaderSpacer);
// Simulate a response // Simulate a response
const responses = [ const responses = [
@ -116,7 +105,6 @@ editor.onSubmit = (value: string) => {
// Add assistant message with no background (transparent) // Add assistant message with no background (transparent)
const botMessage = new Markdown(randomResponse); const botMessage = new Markdown(randomResponse);
children.splice(children.length - 1, 0, botMessage); children.splice(children.length - 1, 0, botMessage);
children.splice(children.length - 1, 0, new Spacer());
// Re-enable submit // Re-enable submit
isResponding = false; isResponding = false;