Files
oh-my-pi/packages/coding-agent/src/tui/width-aware-text.ts
T
can1357 3588fd30f2 feat(coding-agent/tui): adjusted tool output rendering to use terminal content width
- Introduced a WidthAwareText component that formatted fallback content during render using the computed content width.
- Updated ToolExecutionComponent to use WidthAwareText and invalidate cached text instead of a fixed-width Text path.
- Reworked generic tool output formatting to derive inline and output truncation budgets from terminal width rather than hard-coded 70/80 columns.
2026-06-17 19:14:28 +02:00

59 lines
2.1 KiB
TypeScript

import { type Component, getPaddingX, Text } from "@oh-my-pi/pi-tui";
/**
* Text whose content is (re)formatted against the actual render width.
*
* A plain `Text` receives an already-formatted string and only wraps it at
* render time, so width-dependent layout (per-line truncation, inline previews)
* has to be decided before the width is known. Renderers used to cope by
* hard-capping output lines at a fixed column count (e.g. 80), which truncated
* to roughly a third of a wide terminal. This defers formatting to
* `render(width)`: it computes the same content width the inner `Text` uses
* (mirroring its tight-layout flag so the budget can't desync), hands that to
* the formatter, and delegates margins/background/vertical padding to the inner
* `Text`. Lines the formatter caps at `contentWidth` fit exactly and so never
* wrap.
*/
export class WidthAwareText implements Component {
#format: (contentWidth: number) => string;
readonly #paddingX: number;
#inner: Text;
#cachedContentWidth = -1;
#cachedText: string | undefined;
#ignoreTight = false;
constructor(format: (contentWidth: number) => string, paddingX = 1, paddingY = 1) {
this.#format = format;
this.#paddingX = paddingX;
this.#inner = new Text("", paddingX, paddingY);
}
setCustomBgFn(customBgFn?: (text: string) => string): void {
this.#inner.setCustomBgFn(customBgFn);
}
setIgnoreTight(ignore: boolean): this {
this.#ignoreTight = ignore;
this.#inner.setIgnoreTight(ignore);
this.invalidate();
return this;
}
invalidate(): void {
this.#cachedContentWidth = -1;
this.#cachedText = undefined;
this.#inner.invalidate();
}
render(width: number): readonly string[] {
const paddingX = this.#ignoreTight ? this.#paddingX : getPaddingX(this.#paddingX);
const contentWidth = Math.max(1, width - paddingX * 2);
if (this.#cachedText === undefined || contentWidth !== this.#cachedContentWidth) {
this.#cachedContentWidth = contentWidth;
this.#cachedText = this.#format(contentWidth);
this.#inner.setText(this.#cachedText);
}
return this.#inner.render(width);
}
}