Files
oh-my-pi/packages/coding-agent/src/tui/output-block.ts
T
can1357 fcbfab765b refactor: consolidated module import paths
- Removed custom TypeScript path aliases and the associated transformation script.
- Replaced aliased imports with direct `@oh-my-pi` package imports for improved clarity.
- Simplified internal module references by removing explicit `/index` suffixes.
2026-01-23 21:01:13 +01:00

74 lines
2.8 KiB
TypeScript

/**
* Bordered output container with optional header and sections.
*/
import type { Theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
import { visibleWidth } from "@oh-my-pi/pi-tui";
import type { State } from "./types";
import { getStateBgColor, padToWidth, truncateToWidth } from "./utils";
export interface OutputBlockOptions {
header?: string;
headerMeta?: string;
state?: State;
sections?: Array<{ label?: string; lines: string[] }>;
width: number;
}
export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): string[] {
const { header, headerMeta, state, sections = [], width } = options;
const h = theme.boxSharp.horizontal;
const v = theme.boxSharp.vertical;
const cap = h.repeat(3);
const lineWidth = Math.max(0, width);
// Border colors: running/pending use accent, success uses dim (gray), error/warning keep their colors
const borderColor: "error" | "warning" | "accent" | "dim" =
state === "error"
? "error"
: state === "warning"
? "warning"
: state === "running" || state === "pending"
? "accent"
: "dim";
const border = (text: string) => theme.fg(borderColor, text);
const bgFn = state ? (text: string) => theme.bg(getStateBgColor(state), text) : undefined;
const buildBarLine = (leftChar: string, label?: string, meta?: string): string => {
const left = border(`${leftChar}${cap}`);
if (lineWidth <= 0) return left;
const labelText = [label, meta].filter(Boolean).join(theme.sep.dot);
const rawLabel = labelText ? ` ${labelText} ` : " ";
const maxLabelWidth = Math.max(0, lineWidth - visibleWidth(left));
const trimmedLabel = truncateToWidth(rawLabel, maxLabelWidth, theme.format.ellipsis);
const fillCount = Math.max(0, lineWidth - visibleWidth(left + trimmedLabel));
return `${left}${trimmedLabel}${border(h.repeat(fillCount))}`;
};
const contentPrefix = border(`${v} `);
const contentWidth = Math.max(0, lineWidth - visibleWidth(contentPrefix));
const lines: string[] = [];
lines.push(padToWidth(buildBarLine(theme.boxSharp.topLeft, header, headerMeta), lineWidth, bgFn));
const hasSections = sections.length > 0;
const normalizedSections = hasSections ? sections : [{ lines: [] }];
for (let i = 0; i < normalizedSections.length; i++) {
const section = normalizedSections[i];
if (section.label) {
lines.push(padToWidth(buildBarLine(theme.boxSharp.teeRight, section.label), lineWidth, bgFn));
}
for (const line of section.lines) {
const text = truncateToWidth(line, contentWidth, theme.format.ellipsis);
lines.push(padToWidth(`${contentPrefix}${text}`, lineWidth, bgFn));
}
}
const bottomLeft = border(`${theme.boxSharp.bottomLeft}${cap}`);
const bottomFillCount = Math.max(0, lineWidth - visibleWidth(bottomLeft));
const bottomLine = `${bottomLeft}${border(h.repeat(bottomFillCount))}`;
lines.push(padToWidth(bottomLine, lineWidth, bgFn));
return lines;
}