94 lines
3.9 KiB
TypeScript
94 lines
3.9 KiB
TypeScript
import type { ModuleSpec, RenderedModule } from "./types";
|
|
|
|
// eslint-disable-next-line no-control-regex
|
|
const ANSI_RE = new RegExp("\\u001b\\[[0-9;]*m", "g");
|
|
|
|
export function visibleWidth(text: string): number {
|
|
return text.replace(ANSI_RE, "").length;
|
|
}
|
|
|
|
export function truncate(text: string, width: number): string {
|
|
if (width <= 0) return "";
|
|
if (visibleWidth(text) <= width) return text;
|
|
|
|
// ANSI-Aware Truncation
|
|
let output = "";
|
|
let count = 0;
|
|
for (let index = 0; index < text.length && count < width; index++) {
|
|
if (text[index] === "\x1b") {
|
|
// eslint-disable-next-line no-control-regex
|
|
const match = text.slice(index).match(new RegExp("^\\u001b\\[[0-9;]*m"));
|
|
if (match) {
|
|
output += match[0];
|
|
index += match[0].length - 1;
|
|
continue;
|
|
}
|
|
}
|
|
output += text[index];
|
|
count++;
|
|
}
|
|
return `${output}\x1b[0m`;
|
|
}
|
|
|
|
export function joinText(parts: string[], separator: string): string {
|
|
return parts.filter(Boolean).join(` ${separator} `);
|
|
}
|
|
|
|
export function overlay(base: string, text: string): string {
|
|
if (visibleWidth(text) >= visibleWidth(base)) return truncate(text, visibleWidth(base));
|
|
const start = Math.max(0, Math.floor((visibleWidth(base) - visibleWidth(text)) / 2));
|
|
return `${base.slice(0, start)}${text}${base.slice(start + visibleWidth(text))}`;
|
|
}
|
|
|
|
function dimText(text: string, theme?: any): string {
|
|
return text && theme?.fg ? theme.fg("dim", text) : text;
|
|
}
|
|
|
|
function renderGroup(modules: RenderedModule[], width: number, separator: string, theme?: any): string {
|
|
const fixed = modules.map(module => module.text ?? "");
|
|
const growModules = modules.filter(module => module.render);
|
|
const groupSeparator = modules.every(module => module.render) ? " " : ` ${separator} `;
|
|
const renderedSeparator = modules.every(module => module.render) ? " " : dimText(groupSeparator, theme);
|
|
const fixedText = fixed.filter(Boolean).map(text => dimText(text, theme)).join(dimText(` ${separator} `, theme));
|
|
if (growModules.length === 0) return truncate(fixedText, width);
|
|
|
|
const separatorWidth = Math.max(0, modules.length - 1) * groupSeparator.length;
|
|
const fixedWidth = fixed.reduce((total, text) => total + visibleWidth(text), 0);
|
|
const remaining = Math.max(0, width - fixedWidth - separatorWidth);
|
|
const totalGrow = growModules.reduce((total, module) => total + (module.grow ?? 1), 0) || 1;
|
|
let used = 0;
|
|
|
|
const rendered = modules.map(module => {
|
|
if (!module.render) return dimText(module.text ?? "", theme);
|
|
const isLastGrow = growModules[growModules.length - 1] === module;
|
|
const allocated = isLastGrow ? remaining - used : Math.floor(remaining * ((module.grow ?? 1) / totalGrow));
|
|
used += allocated;
|
|
return module.render(Math.max(0, allocated), theme);
|
|
});
|
|
|
|
return truncate(rendered.filter(Boolean).join(renderedSeparator), width);
|
|
}
|
|
|
|
export function renderRow(rendered: Record<"left" | "center" | "right", RenderedModule[]>, width: number, separator: string, theme?: any): string {
|
|
const leftText = renderGroup(rendered.left, width, separator, theme);
|
|
const rightText = renderGroup(rendered.right, width, separator, theme);
|
|
const gap = leftText && rightText ? 1 : 0;
|
|
const centerWidth = Math.max(0, width - visibleWidth(leftText) - visibleWidth(rightText) - gap);
|
|
const centerText = renderGroup(rendered.center, centerWidth, separator, theme);
|
|
|
|
if (centerText) {
|
|
const leftPad = leftText ? `${leftText} ` : "";
|
|
const rightPad = rightText ? ` ${rightText}` : "";
|
|
return truncate(`${leftPad}${centerText}${rightPad}`, width);
|
|
}
|
|
|
|
if (!leftText) return `${" ".repeat(Math.max(0, width - visibleWidth(rightText)))}${truncate(rightText, width)}`;
|
|
if (!rightText) return truncate(leftText, width);
|
|
const padding = " ".repeat(Math.max(1, width - visibleWidth(leftText) - visibleWidth(rightText)));
|
|
return truncate(`${leftText}${padding}${rightText}`, width);
|
|
}
|
|
|
|
export function moduleType(spec: ModuleSpec): string {
|
|
return typeof spec === "string" ? spec : spec.type;
|
|
}
|