/** * UI formatting utilities for the sub-bar extension */ import type { Theme } from "@mariozechner/pi-coding-agent"; import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui"; import type { RateWindow, UsageSnapshot, ProviderStatus, ModelInfo } from "./types.js"; import type { BaseTextColor, BarStyle, BarType, BarCharacter, BarWidth, ColorScheme, DividerBlanks, ResetTimerContainment, Settings, } from "./settings-types.js"; import { isBackgroundColor, resolveBaseTextColor, resolveDividerColor } from "./settings-types.js"; import { formatErrorForDisplay, isExpectedMissingData } from "./errors.js"; import { getStatusIcon, getStatusLabel } from "./status.js"; import { shouldShowWindow } from "./providers/windows.js"; import { getUsageExtras } from "./providers/extras.js"; import { normalizeTokens } from "./utils.js"; export interface UsageWindowParts { label: string; bar: string; pct: string; reset: string; } /** * Context window usage info from the pi framework */ export interface ContextInfo { tokens: number; contextWindow: number; percent: number; } type ModelInput = ModelInfo | string | undefined; function resolveModelInfo(model?: ModelInput): ModelInfo | undefined { if (!model) return undefined; return typeof model === "string" ? { id: model } : model; } function isCodexSparkModel(model?: ModelInput): boolean { const tokens = normalizeTokens(typeof model === "string" ? model : model?.id ?? ""); return tokens.includes("codex") && tokens.includes("spark"); } function isCodexSparkWindow(window: RateWindow): boolean { const tokens = normalizeTokens(window.label ?? ""); return tokens.includes("codex") && tokens.includes("spark"); } function getDisplayWindowLabel(window: RateWindow, model?: ModelInput): string { if (!isCodexSparkWindow(window)) return window.label; if (!isCodexSparkModel(model)) return window.label; const parts = window.label.trim().split(/\s+/); const suffix = parts.at(-1) ?? ""; if (/^\d+h$/i.test(suffix) || /^day$/i.test(suffix) || /^week$/i.test(suffix)) { return suffix; } return window.label; } /** * Get the characters to use for progress bars */ function getBarCharacters(barCharacter: BarCharacter): { filled: string; empty: string } { let filled = "━"; let empty = "━"; switch (barCharacter) { case "light": filled = "─"; empty = "─"; break; case "heavy": filled = "━"; empty = "━"; break; case "double": filled = "═"; empty = "═"; break; case "block": filled = "█"; empty = "█"; break; default: { const raw = String(barCharacter); const trimmed = raw.trim(); if (!trimmed) return { filled, empty }; const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" }); const segments = Array.from(segmenter.segment(raw), (entry) => entry.segment); const first = segments[0] ?? trimmed[0] ?? "━"; const second = segments[1]; filled = first; empty = second ?? first; break; } } return { filled, empty }; } /** * Get color based on percentage and color scheme */ function getUsageColor( percent: number, isRemaining: boolean, colorScheme: ColorScheme, errorThreshold: number = 25, warningThreshold: number = 50, successThreshold: number = 75 ): "error" | "warning" | "base" | "success" { if (colorScheme === "monochrome") { return "base"; } // For remaining percentage (Codex style), invert the logic const effectivePercent = isRemaining ? percent : 100 - percent; if (colorScheme === "success-base-warning-error") { // >75%: success, >50%: base, >25%: warning, <=25%: error if (effectivePercent < errorThreshold) return "error"; if (effectivePercent < warningThreshold) return "warning"; if (effectivePercent < successThreshold) return "base"; return "success"; } // base-warning-error (default) // >50%: base, >25%: warning, <=25%: error if (effectivePercent < errorThreshold) return "error"; if (effectivePercent < warningThreshold) return "warning"; return "base"; } function clampPercent(value: number): number { return Math.max(0, Math.min(100, value)); } function getStatusColor( indicator: NonNullable["indicator"], colorScheme: ColorScheme ): "error" | "warning" | "success" | "base" { if (colorScheme === "monochrome") { return "base"; } if (indicator === "minor" || indicator === "maintenance") { return "warning"; } if (indicator === "major" || indicator === "critical") { return "error"; } if (indicator === "none") { return colorScheme === "success-base-warning-error" ? "success" : "base"; } return "base"; } function resolveStatusTintColor( color: "error" | "warning" | "success" | "base", baseTextColor: BaseTextColor ): BaseTextColor { return color === "base" ? baseTextColor : color; } function fgFromBgAnsi(ansi: string): string { return ansi.replace(/\x1b\[48;/g, "\x1b[38;").replace(/\x1b\[49m/g, "\x1b[39m"); } function applyBaseTextColor(theme: Theme, color: BaseTextColor, text: string): string { if (isBackgroundColor(color)) { const fgAnsi = fgFromBgAnsi(theme.getBgAnsi(color as Parameters[0])); return `${fgAnsi}${text}\x1b[39m`; } return theme.fg(resolveDividerColor(color), text); } function resolveUsageColorTargets(settings?: Settings): { title: boolean; timer: boolean; bar: boolean; usageLabel: boolean; status: boolean; } { const targets = settings?.display.usageColorTargets; return { title: targets?.title ?? true, timer: targets?.timer ?? true, bar: targets?.bar ?? true, usageLabel: targets?.usageLabel ?? true, status: targets?.status ?? true, }; } function formatElapsedSince(timestamp: number): string { const diffMs = Date.now() - timestamp; if (diffMs < 60000) { const seconds = Math.max(1, Math.floor(diffMs / 1000)); return `${seconds}s`; } const diffMins = Math.floor(diffMs / 60000); if (diffMins < 60) return `${diffMins}m`; const hours = Math.floor(diffMins / 60); const mins = diffMins % 60; if (hours < 24) return mins > 0 ? `${hours}h${mins}m` : `${hours}h`; const days = Math.floor(hours / 24); const remHours = hours % 24; return remHours > 0 ? `${days}d${remHours}h` : `${days}d`; } const RESET_CONTAINMENT_SEGMENTER = new Intl.Segmenter(undefined, { granularity: "grapheme" }); function wrapResetContainment(text: string, containment: ResetTimerContainment): { wrapped: string; attachWithSpace: boolean } { switch (containment) { case "none": return { wrapped: text, attachWithSpace: true }; case "blank": return { wrapped: text, attachWithSpace: true }; case "[]": return { wrapped: `[${text}]`, attachWithSpace: true }; case "<>": return { wrapped: `<${text}>`, attachWithSpace: true }; case "()": return { wrapped: `(${text})`, attachWithSpace: true }; default: { const trimmed = String(containment).trim(); if (!trimmed) return { wrapped: `(${text})`, attachWithSpace: true }; const segments = Array.from(RESET_CONTAINMENT_SEGMENTER.segment(trimmed), (entry) => entry.segment) .map((segment) => segment.trim()) .filter(Boolean); if (segments.length === 0) return { wrapped: `(${text})`, attachWithSpace: true }; const left = segments[0]; const right = segments[1] ?? left; return { wrapped: `${left}${text}${right}`, attachWithSpace: true }; } } } function formatResetDateTime(resetAt: string): string { const date = new Date(resetAt); if (Number.isNaN(date.getTime())) return resetAt; return new Intl.DateTimeFormat(undefined, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", }).format(date); } function getBarTypeLevels(barType: BarType): string[] | null { switch (barType) { case "horizontal-single": return ["▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"]; case "vertical": return ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"]; case "braille": return ["⡀", "⡄", "⣄", "⣆", "⣇", "⣧", "⣷", "⣿"]; case "shade": return ["░", "▒", "▓", "█"]; default: return null; } } function renderBarSegments( percent: number, width: number, levels: string[], options?: { allowMinimum?: boolean; emptyChar?: string } ): { segments: Array<{ char: string; filled: boolean }>; minimal: boolean } { const totalUnits = Math.max(1, width) * levels.length; let filledUnits = Math.round((percent / 100) * totalUnits); let minimal = false; if (options?.allowMinimum && percent > 0 && filledUnits === 0) { filledUnits = 1; minimal = true; } const emptyChar = options?.emptyChar ?? " "; const segments: Array<{ char: string; filled: boolean }> = []; for (let i = 0; i < Math.max(1, width); i++) { if (filledUnits >= levels.length) { segments.push({ char: levels[levels.length - 1], filled: true }); filledUnits -= levels.length; continue; } if (filledUnits > 0) { segments.push({ char: levels[Math.min(levels.length - 1, filledUnits - 1)], filled: true }); filledUnits = 0; continue; } segments.push({ char: emptyChar, filled: false }); } return { segments, minimal }; } function formatProviderLabel(theme: Theme, usage: UsageSnapshot, settings?: Settings, model?: ModelInput): string { const showProviderName = settings?.display.showProviderName ?? true; const showStatus = settings?.providers[usage.provider]?.showStatus ?? true; const error = usage.error; const fetchError = Boolean(error && !isExpectedMissingData(error)); const baseStatus = showStatus ? usage.status : undefined; const lastSuccessAt = usage.lastSuccessAt; const elapsed = lastSuccessAt ? formatElapsedSince(lastSuccessAt) : undefined; const fetchDescription = elapsed ? (elapsed === "just now" ? "Last upd.: just now" : `Last upd.: ${elapsed} ago`) : "Fetch failed"; const fetchStatus: ProviderStatus | undefined = fetchError ? { indicator: "minor", description: fetchDescription } : undefined; const status = showStatus ? (fetchStatus ?? baseStatus) : undefined; const statusDismissOk = settings?.display.statusDismissOk ?? true; const statusModeRaw = settings?.display.statusIndicatorMode ?? "icon"; const statusMode = statusModeRaw === "icon" || statusModeRaw === "text" || statusModeRaw === "icon+text" ? statusModeRaw : "icon"; const statusIconPack = settings?.display.statusIconPack ?? "emoji"; const statusIconCustom = settings?.display.statusIconCustom; const providerLabelSetting = settings?.display.providerLabel ?? "none"; const showColon = settings?.display.providerLabelColon ?? true; const boldProviderLabel = settings?.display.providerLabelBold ?? false; const baseTextColor = resolveBaseTextColor(settings?.display.baseTextColor); const usageTargets = resolveUsageColorTargets(settings); const statusActive = Boolean(status && (!statusDismissOk || status.indicator !== "none")); const showIcon = statusActive && (statusMode === "icon" || statusMode === "icon+text"); const showText = statusActive && (statusMode === "text" || statusMode === "icon+text"); const labelSuffix = providerLabelSetting === "plan" ? "Plan" : providerLabelSetting === "subscription" ? "Subscription" : providerLabelSetting === "sub" ? "Sub." : providerLabelSetting === "none" ? "" : String(providerLabelSetting); const rawName = usage.displayName?.trim() ?? ""; const baseName = rawName.replace(/\s+(plan|subscription|sub\.?)[\s]*$/i, "").trim(); const resolvedProviderName = baseName || rawName; const isSpark = usage.provider === "codex" && isCodexSparkModel(model); const providerName = isSpark ? `${resolvedProviderName} (Spark)` : resolvedProviderName; const providerLabel = showProviderName ? [providerName, labelSuffix].filter(Boolean).join(" ") : ""; const providerLabelWithColon = providerLabel && showColon ? `${providerLabel}:` : providerLabel; const icon = showIcon && status ? getStatusIcon(status, statusIconPack, statusIconCustom) : ""; const statusText = showText && status ? getStatusLabel(status) : ""; const rawStatusColor = status ? getStatusColor(status.indicator, settings?.display.colorScheme ?? "base-warning-error") : "base"; const statusTint = usageTargets.status ? resolveStatusTintColor(rawStatusColor, baseTextColor) : baseTextColor; const statusColor = statusTint; const dividerEnabled = settings?.display.statusProviderDivider ?? false; const dividerChar = settings?.display.dividerCharacter ?? "│"; const dividerColor = resolveDividerColor(settings?.display.dividerColor); const dividerGlyph = dividerChar === "none" ? "" : dividerChar === "blank" ? " " : dividerChar; const statusParts: string[] = []; if (icon) statusParts.push(applyBaseTextColor(theme, statusColor, icon)); if (statusText) statusParts.push(applyBaseTextColor(theme, statusColor, statusText)); const parts: string[] = []; if (statusParts.length > 0) { parts.push(statusParts.join(" ")); } if (providerLabelWithColon) { if (statusParts.length > 0 && dividerEnabled && dividerGlyph) { parts.push(theme.fg(dividerColor, dividerGlyph)); } const colored = applyBaseTextColor(theme, baseTextColor, providerLabelWithColon); parts.push(boldProviderLabel ? theme.bold(colored) : colored); } if (parts.length === 0) return ""; return parts.join(" "); } /** * Format a single usage window as a styled string */ export function formatUsageWindow( theme: Theme, window: RateWindow, isCodex: boolean, settings?: Settings, usage?: UsageSnapshot, options?: { useNormalColors?: boolean; barWidthOverride?: number }, model?: ModelInput ): string { const parts = formatUsageWindowParts(theme, window, isCodex, settings, usage, options, model); const baseTextColor = resolveBaseTextColor(settings?.display.baseTextColor); const usageTargets = resolveUsageColorTargets(settings); // Special handling for Extra usage label if (window.label.startsWith("Extra [")) { const match = window.label.match(/^(Extra \[)(on|active)(\] .*)$/); if (match) { const [, prefix, status, suffix] = match; const styledLabel = status === "active" ? applyBaseTextColor(theme, baseTextColor, prefix) + theme.fg("text", status) + applyBaseTextColor(theme, baseTextColor, suffix) : applyBaseTextColor(theme, baseTextColor, window.label); const extraParts = [styledLabel, parts.bar, parts.pct].filter(Boolean); return extraParts.join(" "); } if (!usageTargets.title) { const extraParts = [applyBaseTextColor(theme, baseTextColor, window.label), parts.bar, parts.pct].filter(Boolean); return extraParts.join(" "); } const extraColor = getUsageColor(window.usedPercent, false, settings?.display.colorScheme ?? "base-warning-error"); const extraTextColor = (options?.useNormalColors && extraColor === "base") ? "text" : extraColor === "base" ? baseTextColor : extraColor; const extraParts = [applyBaseTextColor(theme, extraTextColor, window.label), parts.bar, parts.pct].filter(Boolean); return extraParts.join(" "); } const joinedParts = [parts.label, parts.bar, parts.pct, parts.reset].filter(Boolean); return joinedParts.join(" "); } export function formatUsageWindowParts( theme: Theme, window: RateWindow, isCodex: boolean, settings?: Settings, usage?: UsageSnapshot, options?: { useNormalColors?: boolean; barWidthOverride?: number }, model?: ModelInput ): UsageWindowParts { const barStyle: BarStyle = settings?.display.barStyle ?? "both"; const barWidthSetting = settings?.display.barWidth; const containBar = settings?.display.containBar ?? false; const barWidth = options?.barWidthOverride ?? (typeof barWidthSetting === "number" ? barWidthSetting : 6); const barType: BarType = settings?.display.barType ?? "horizontal-bar"; const brailleFillEmpty = settings?.display.brailleFillEmpty ?? false; const brailleFullBlocks = settings?.display.brailleFullBlocks ?? false; const barCharacter: BarCharacter = settings?.display.barCharacter ?? "heavy"; const colorScheme: ColorScheme = settings?.display.colorScheme ?? "base-warning-error"; const resetTimePosition = settings?.display.resetTimePosition ?? "front"; const resetTimeFormat = settings?.display.resetTimeFormat ?? "relative"; const showUsageLabels = settings?.display.showUsageLabels ?? true; const showWindowTitle = settings?.display.showWindowTitle ?? true; const boldWindowTitle = settings?.display.boldWindowTitle ?? false; const baseTextColor = resolveBaseTextColor(settings?.display.baseTextColor); const errorThreshold = settings?.display.errorThreshold ?? 25; const warningThreshold = settings?.display.warningThreshold ?? 50; const successThreshold = settings?.display.successThreshold ?? 75; const rawUsedPct = Math.round(window.usedPercent); const usedPct = clampPercent(rawUsedPct); const displayPct = isCodex ? clampPercent(100 - usedPct) : usedPct; const isRemaining = isCodex; const barPercent = clampPercent(displayPct); const filled = Math.round((barPercent / 100) * barWidth); const empty = Math.max(0, barWidth - filled); const baseColor = getUsageColor(displayPct, isRemaining, colorScheme, errorThreshold, warningThreshold, successThreshold); const usageTargets = resolveUsageColorTargets(settings); const usageTextColor = (options?.useNormalColors && baseColor === "base") ? "text" : baseColor === "base" ? baseTextColor : baseColor; const neutralTextColor = options?.useNormalColors ? "text" : baseTextColor; const titleColor = usageTargets.title ? usageTextColor : neutralTextColor; const timerColor = usageTargets.timer ? usageTextColor : neutralTextColor; const usageLabelColor = usageTargets.usageLabel ? usageTextColor : neutralTextColor; const barUsageColor = (options?.useNormalColors && baseColor === "base") ? "text" : baseColor === "base" ? "muted" : baseColor; const neutralBarColor = baseTextColor === "dim" ? "dim" : "muted"; const barColor = usageTargets.bar ? barUsageColor : neutralBarColor; const { filled: filledChar, empty: emptyChar } = getBarCharacters(barCharacter); const emptyColor = "dim"; let barStr = ""; if ((barStyle === "bar" || barStyle === "both") && barWidth > 0) { let levels = getBarTypeLevels(barType); if (barType === "braille" && brailleFullBlocks) { levels = ["⣿"]; } if (!levels || barType === "horizontal-bar") { const filledCharWidth = Math.max(1, visibleWidth(filledChar)); const emptyCharWidth = Math.max(1, visibleWidth(emptyChar)); const segmentCount = barWidth > 0 ? Math.floor(barWidth / filledCharWidth) : 0; const filledSegments = segmentCount > 0 ? Math.round((barPercent / 100) * segmentCount) : 0; const filledStr = filledChar.repeat(filledSegments); const filledWidth = filledSegments * filledCharWidth; const remainingWidth = Math.max(0, barWidth - filledWidth); const emptySegments = emptyCharWidth > 0 ? Math.floor(remainingWidth / emptyCharWidth) : 0; const emptyStr = emptyChar.repeat(emptySegments); const emptyRendered = emptyChar === " " ? emptyStr : theme.fg(emptyColor, emptyStr); barStr = theme.fg(barColor as Parameters[0], filledStr) + emptyRendered; const barVisualWidth = visibleWidth(barStr); if (barVisualWidth < barWidth) { barStr += " ".repeat(barWidth - barVisualWidth); } } else { const emptyChar = barType === "braille" && brailleFillEmpty && barWidth > 1 ? "⣿" : " "; const { segments, minimal } = renderBarSegments(barPercent, barWidth, levels, { allowMinimum: true, emptyChar, }); const filledColor = minimal ? "dim" : barColor; barStr = segments .map((segment) => { if (segment.filled) { return theme.fg(filledColor as Parameters[0], segment.char); } if (segment.char === " ") { return segment.char; } return theme.fg("dim", segment.char); }) .join(""); } if (settings?.display.containBar && barStr) { const leftCap = theme.fg(barColor as Parameters[0], "▕"); const rightCap = theme.fg(barColor as Parameters[0], "▏"); barStr = leftCap + barStr + rightCap; } } let pctStr = ""; if (barStyle === "percentage" || barStyle === "both") { // Special handling for Copilot Month window - can show percentage or requests if (window.label === "Month" && usage?.provider === "copilot") { const quotaDisplay = settings?.providers.copilot.quotaDisplay ?? "percentage"; if (quotaDisplay === "requests" && usage.requestsRemaining !== undefined && usage.requestsEntitlement !== undefined) { const used = usage.requestsEntitlement - usage.requestsRemaining; const suffix = showUsageLabels ? " used" : ""; pctStr = applyBaseTextColor(theme, usageLabelColor, `${used}/${usage.requestsEntitlement}${suffix}`); } else { const suffix = showUsageLabels ? " used" : ""; pctStr = applyBaseTextColor(theme, usageLabelColor, `${usedPct}%${suffix}`); } } else if (isCodex) { const suffix = showUsageLabels ? " rem." : ""; pctStr = applyBaseTextColor(theme, usageLabelColor, `${displayPct}%${suffix}`); } else { const suffix = showUsageLabels ? " used" : ""; pctStr = applyBaseTextColor(theme, usageLabelColor, `${usedPct}%${suffix}`); } } const isActiveReset = window.resetDescription === "__ACTIVE__"; const resetText = isActiveReset ? undefined : resetTimeFormat === "datetime" ? (window.resetAt ? formatResetDateTime(window.resetAt) : window.resetDescription) : window.resetDescription; const resetContainment = settings?.display.resetTimeContainment ?? "()"; const leftSuffix = resetText && resetTimeFormat === "relative" && showUsageLabels ? " left" : ""; const displayLabel = getDisplayWindowLabel(window, model); const coloredTitle = applyBaseTextColor(theme, titleColor, displayLabel); const titlePart = showWindowTitle ? (boldWindowTitle ? theme.bold(coloredTitle) : coloredTitle) : ""; let labelPart = titlePart; if (resetText) { const resetBody = `${resetText}${leftSuffix}`; const { wrapped, attachWithSpace } = wrapResetContainment(resetBody, resetContainment); const coloredReset = applyBaseTextColor(theme, timerColor, wrapped); if (resetTimePosition === "front") { if (!titlePart) { labelPart = coloredReset; } else { labelPart = attachWithSpace ? `${titlePart} ${coloredReset}` : `${titlePart}${coloredReset}`; } } else if (resetTimePosition === "integrated") { labelPart = titlePart ? `${applyBaseTextColor(theme, timerColor, `${wrapped}/`)}${titlePart}` : coloredReset; } else if (resetTimePosition === "back") { labelPart = titlePart; } } else if (!titlePart) { labelPart = ""; } const resetPart = resetTimePosition === "back" && resetText ? applyBaseTextColor(theme, timerColor, wrapResetContainment(`${resetText}${leftSuffix}`, resetContainment).wrapped) : ""; return { label: labelPart, bar: barStr, pct: pctStr, reset: resetPart, }; } /** * Format context window usage as a progress bar */ export function formatContextBar( theme: Theme, context: ContextInfo, settings?: Settings, options?: { barWidthOverride?: number } ): string { // Create a pseudo-RateWindow for context display const contextWindow: RateWindow = { label: "Ctx", usedPercent: context.percent, // No reset description for context }; // Format using the same window formatting logic, but with "used" semantics (not inverted) return formatUsageWindow(theme, contextWindow, false, settings, undefined, options); } /** * Format a complete usage snapshot as a usage line */ export function formatUsageStatus( theme: Theme, usage: UsageSnapshot, model?: ModelInput, settings?: Settings, context?: ContextInfo ): string | undefined { const baseTextColor = resolveBaseTextColor(settings?.display.baseTextColor); const modelInfo = resolveModelInfo(model); const label = formatProviderLabel(theme, usage, settings, modelInfo); // If no windows, just show the provider name with error if (usage.windows.length === 0) { const errorMsg = usage.error ? applyBaseTextColor(theme, baseTextColor, `(${formatErrorForDisplay(usage.error)})`) : ""; if (!label) { return errorMsg; } return errorMsg ? `${label} ${errorMsg}` : label; } // Build usage bars const parts: string[] = []; const isCodex = usage.provider === "codex"; const invertUsage = isCodex && (settings?.providers.codex.invertUsage ?? false); const modelId = modelInfo?.id; // Add context bar as leftmost element if enabled const showContextBar = settings?.display.showContextBar ?? false; if (showContextBar && context && context.contextWindow > 0) { parts.push(formatContextBar(theme, context, settings)); } for (const w of usage.windows) { // Skip windows that are disabled in settings if (!shouldShowWindow(usage, w, settings, modelInfo)) { continue; } parts.push(formatUsageWindow(theme, w, invertUsage, settings, usage, undefined, modelInfo)); } // Add extra usage lines (extra usage off, copilot multiplier, etc.) const extras = getUsageExtras(usage, settings, modelId); for (const extra of extras) { parts.push(applyBaseTextColor(theme, baseTextColor, extra.label)); } // Build divider from settings const dividerChar = settings?.display.dividerCharacter ?? "•"; const dividerColor = resolveDividerColor(settings?.display.dividerColor); const blanksSetting = settings?.display.dividerBlanks ?? 1; const showProviderDivider = settings?.display.showProviderDivider ?? false; const blanksPerSide = typeof blanksSetting === "number" ? blanksSetting : 1; const spacing = " ".repeat(blanksPerSide); const charToDisplay = dividerChar === "blank" ? " " : dividerChar === "none" ? "" : dividerChar; const divider = charToDisplay ? spacing + theme.fg(dividerColor, charToDisplay) + spacing : spacing + spacing; const labelGap = label && parts.length > 0 ? showProviderDivider && charToDisplay !== "" ? divider : spacing : ""; return label + labelGap + parts.join(divider); } export function formatUsageStatusWithWidth( theme: Theme, usage: UsageSnapshot, width: number, model?: ModelInput, settings?: Settings, options?: { labelGapFill?: boolean }, context?: ContextInfo ): string | undefined { const labelGapFill = options?.labelGapFill ?? false; const baseTextColor = resolveBaseTextColor(settings?.display.baseTextColor); const modelInfo = resolveModelInfo(model); const label = formatProviderLabel(theme, usage, settings, modelInfo); const showContextBar = settings?.display.showContextBar ?? false; const hasContext = showContextBar && context && context.contextWindow > 0; // If no windows, just show the provider name with error if (usage.windows.length === 0) { const errorMsg = usage.error ? applyBaseTextColor(theme, baseTextColor, `(${formatErrorForDisplay(usage.error)})`) : ""; if (!label) { return errorMsg; } return errorMsg ? `${label} ${errorMsg}` : label; } const barStyle: BarStyle = settings?.display.barStyle ?? "both"; const hasBar = barStyle === "bar" || barStyle === "both"; const barWidthSetting = settings?.display.barWidth ?? 6; const dividerBlanksSetting = settings?.display.dividerBlanks ?? 1; const dividerColor = resolveDividerColor(settings?.display.dividerColor); const showProviderDivider = settings?.display.showProviderDivider ?? false; const containBar = settings?.display.containBar ?? false; const barFill = barWidthSetting === "fill"; const barBaseWidth = typeof barWidthSetting === "number" ? barWidthSetting : (hasBar ? 1 : 0); const barContainerExtra = containBar && hasBar ? 2 : 0; const barBaseContentWidth = barFill ? 0 : barBaseWidth; const barBaseWidthCalc = barFill ? 0 : barBaseContentWidth + barContainerExtra; const barTotalBaseWidth = barBaseWidthCalc; const baseDividerBlanks = typeof dividerBlanksSetting === "number" ? dividerBlanksSetting : 1; const dividerFill = dividerBlanksSetting === "fill"; // Build usage windows const windows: RateWindow[] = []; const isCodex = usage.provider === "codex"; const invertUsage = isCodex && (settings?.providers.codex.invertUsage ?? false); const modelId = modelInfo?.id; // Add context window as first entry if enabled let contextWindowIndex = -1; if (hasContext) { contextWindowIndex = windows.length; windows.push({ label: "Ctx", usedPercent: context!.percent, }); } for (const w of usage.windows) { if (!shouldShowWindow(usage, w, settings, modelInfo)) { continue; } windows.push(w); } const barEligibleCount = hasBar ? windows.length : 0; const extras = getUsageExtras(usage, settings, modelId); const extraParts = extras.map((extra) => applyBaseTextColor(theme, baseTextColor, extra.label)); const barSpacerWidth = hasBar ? 1 : 0; const baseWindowWidths = windows.map((w, i) => { // Context window uses false for invertUsage (always show used percentage) const isContext = i === contextWindowIndex; return ( visibleWidth( formatUsageWindow( theme, w, isContext ? false : invertUsage, settings, isContext ? undefined : usage, { barWidthOverride: 0 }, modelInfo ) ) + barSpacerWidth ); }); const extraWidths = extraParts.map((part) => visibleWidth(part)); const partCount = windows.length + extraParts.length; const dividerCount = Math.max(0, partCount - 1); const dividerChar = settings?.display.dividerCharacter ?? "•"; const charToDisplay = dividerChar === "blank" ? " " : dividerChar === "none" ? "" : dividerChar; const dividerBaseWidth = (charToDisplay ? 1 : 0) + baseDividerBlanks * 2; const labelGapEnabled = partCount > 0 && (label !== "" || labelGapFill); const providerDividerActive = showProviderDivider && charToDisplay !== "" && label !== ""; const labelGapBaseWidth = labelGapEnabled ? providerDividerActive ? dividerBaseWidth : baseDividerBlanks : 0; const labelWidth = visibleWidth(label); const baseTotalWidth = labelWidth + labelGapBaseWidth + baseWindowWidths.reduce((sum, w) => sum + w, 0) + extraWidths.reduce((sum, w) => sum + w, 0) + (barEligibleCount * barTotalBaseWidth) + (dividerCount * dividerBaseWidth); let remainingWidth = width - baseTotalWidth; if (remainingWidth < 0) { remainingWidth = 0; } const useBars = barFill && barEligibleCount > 0; const labelGapUnits = labelGapEnabled ? (providerDividerActive ? 2 : 1) : 0; const dividerSlots = dividerCount + (labelGapEnabled ? 1 : 0); const dividerUnits = dividerCount * 2 + labelGapUnits; const useDividers = dividerFill && dividerUnits > 0; let barExtraTotal = 0; let dividerExtraTotal = 0; if (remainingWidth > 0 && (useBars || useDividers)) { const barWeight = useBars ? barEligibleCount : 0; const dividerWeight = useDividers ? dividerUnits : 0; const totalWeight = barWeight + dividerWeight; if (totalWeight > 0) { barExtraTotal = Math.floor((remainingWidth * barWeight) / totalWeight); dividerExtraTotal = remainingWidth - barExtraTotal; } } const barWidths: number[] = windows.map(() => barBaseWidthCalc); if (useBars && barEligibleCount > 0) { const perBar = Math.floor(barExtraTotal / barEligibleCount); let remainder = barExtraTotal % barEligibleCount; for (let i = 0; i < barWidths.length; i++) { barWidths[i] = barBaseWidthCalc + perBar + (remainder > 0 ? 1 : 0); if (remainder > 0) remainder -= 1; } } let labelBlanks = labelGapEnabled ? baseDividerBlanks : 0; const dividerBlanks: number[] = []; if (dividerUnits > 0) { const baseUnit = useDividers ? Math.floor(dividerExtraTotal / dividerUnits) : 0; let remainderUnits = useDividers ? dividerExtraTotal % dividerUnits : 0; if (labelGapEnabled) { if (useDividers && providerDividerActive) { let extraUnits = baseUnit * 2; if (remainderUnits >= 2) { extraUnits += 2; remainderUnits -= 2; } labelBlanks = baseDividerBlanks + Math.floor(extraUnits / 2); } else if (useDividers) { labelBlanks = baseDividerBlanks + baseUnit + (remainderUnits > 0 ? 1 : 0); if (remainderUnits > 0) remainderUnits -= 1; } } for (let i = 0; i < dividerCount; i++) { let extraUnits = baseUnit * 2; if (remainderUnits >= 2) { extraUnits += 2; remainderUnits -= 2; } const blanks = baseDividerBlanks + Math.floor(extraUnits / 2); dividerBlanks.push(blanks); } } const parts: string[] = []; for (let i = 0; i < windows.length; i++) { const totalWidth = barWidths[i] ?? barBaseWidthCalc; const contentWidth = containBar ? Math.max(0, totalWidth - barContainerExtra) : totalWidth; const isContext = i === contextWindowIndex; parts.push( formatUsageWindow( theme, windows[i], isContext ? false : invertUsage, settings, isContext ? undefined : usage, { barWidthOverride: contentWidth }, modelInfo ) ); } for (const extra of extraParts) { parts.push(extra); } let rest = ""; for (let i = 0; i < parts.length; i++) { rest += parts[i]; if (i < dividerCount) { const blanks = dividerBlanks[i] ?? baseDividerBlanks; const spacing = " ".repeat(Math.max(0, blanks)); rest += charToDisplay ? spacing + theme.fg(dividerColor, charToDisplay) + spacing : spacing + spacing; } } let labelGapExtra = 0; if (labelGapFill && labelGapEnabled) { const restWidth = visibleWidth(rest); const labelGapWidth = providerDividerActive ? (Math.max(0, labelBlanks) * 2) + (charToDisplay ? 1 : 0) : Math.max(0, labelBlanks); const totalWidth = visibleWidth(label) + restWidth + labelGapWidth; labelGapExtra = Math.max(0, width - totalWidth); } let output = label; if (labelGapEnabled) { if (providerDividerActive) { const spacing = " ".repeat(Math.max(0, labelBlanks)); output += spacing + theme.fg(dividerColor, charToDisplay) + spacing + " ".repeat(labelGapExtra); } else { output += " ".repeat(Math.max(0, labelBlanks + labelGapExtra)); } } output += rest; if (width > 0 && visibleWidth(output) > width) { return truncateToWidth(output, width, ""); } return output; }