// 状态栏渲染器 // 忠实移植 CCometixLine StatusLineGenerator:段渲染 + Powerline 箭头分隔符颜色过渡 import { truncateToWidth } from "@earendil-works/pi-tui"; import type { AnsiColor, Config, SegmentConfig, SegmentData } from "./types.ts"; import { bgSeq, colorText, fgSeq } from "./ansi.ts"; /** 取段图标(dynamic_icon 优先,否则按 style.mode 选择) */ function getIcon(config: Config, seg: SegmentConfig, data: SegmentData): string { if (data.metadata["dynamic_icon"]) return data.metadata["dynamic_icon"]; return config.style.mode === "plain" ? seg.icon.plain : seg.icon.nerdFont; } /** * 渲染单个段(忠实移植 CCometixLine render_segment) * - 有背景色:去中间 reset,整体包背景,末尾恢复默认背景 * - 无背景色:图标 + 文本各自包裹并 reset */ function renderSegment(config: Config, seg: SegmentConfig, data: SegmentData): string { const icon = getIcon(config, seg, data); const bold = seg.styles.textBold; if (seg.colors.background) { const bg = bgSeq(seg.colors.background); // 去除中间 reset,保留颜色序列,避免破坏背景连续性 const iconColored = colorText(icon, seg.colors.icon, false).replace(/\x1b\[0m/g, ""); const textStyled = colorText(data.primary, seg.colors.text, bold).replace(/\x1b\[0m/g, ""); let content = ` ${iconColored} ${textStyled} `; if (data.secondary) { const sec = colorText(data.secondary, seg.colors.text, bold).replace(/\x1b\[0m/g, ""); content += `${sec} `; } return `${bg}${content}\x1b[49m`; // \x1b[49m = 默认背景 } // 无背景色 const iconColored = colorText(icon, seg.colors.icon, false); const textStyled = colorText(data.primary, seg.colors.text, bold); let s = `${iconColored} ${textStyled}`; if (data.secondary) { s += ` ${colorText(data.secondary, seg.colors.text, bold)}`; } return s; } /** Powerline 箭头分隔符(前景=上一段背景,背景=下一段背景,实现颜色过渡) */ function powerlineArrow(prevBg?: AnsiColor, currBg?: AnsiColor): string { const arrow = "\ue0b0"; if (prevBg && currBg) return `${bgSeq(currBg)}${fgSeq(prevBg)}${arrow}\x1b[0m`; if (prevBg) return `${fgSeq(prevBg)}${arrow}\x1b[0m`; if (currBg) return `${bgSeq(currBg)}${arrow}\x1b[0m`; return arrow; } /** * 生成完整状态栏单行(移植 CCometixLine generate) * - separator 为 powerline 箭头 \ue0b0 时,使用颜色过渡箭头 * - 否则使用白色分隔符 */ export function generateStatusLine( config: Config, segments: { seg: SegmentConfig; data: SegmentData }[], ): string { const enabled = segments.filter((s) => s.seg.enabled); const rendered = enabled .map((s) => renderSegment(config, s.seg, s.data)) .filter((s) => s.length > 0); if (rendered.length === 0) return ""; let joined: string; if (config.style.separator === "\ue0b0") { // Powerline 箭头:按相邻段背景色过渡 let result = rendered[0]!; for (let i = 1; i < rendered.length; i++) { const prevBg = enabled[i - 1]?.seg.colors.background; const currBg = enabled[i]?.seg.colors.background; result += powerlineArrow(prevBg, currBg) + rendered[i]!; } result += "\x1b[0m"; joined = result; } else { const sep = `\x1b[37m${config.style.separator}\x1b[0m`; // 白色分隔符 joined = rendered.join(sep); } return joined; } /** 渲染 footer 单行:生成状态栏并按宽度截断 */ export function renderFooterLine( config: Config, segments: { seg: SegmentConfig; data: SegmentData }[], width: number, ): string[] { const line = generateStatusLine(config, segments); if (!line) return []; return [truncateToWidth(line, width)]; }