Add footer renderer for TUI status display.
Displays git branch status, dirty state, extension statuses, model info, cost, and context usage percentage in the footer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c18f67d278
commit
ec39512960
1 changed files with 78 additions and 0 deletions
78
src/resources/extensions/sf-tui/footer.ts
Normal file
78
src/resources/extensions/sf-tui/footer.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import type { ExtensionContext, Theme, ReadonlyFooterDataProvider } from "@sf-run/pi-coding-agent";
|
||||
import { truncateToWidth } from "@sf-run/pi-tui";
|
||||
import { refreshGitStatus } from "./git.js";
|
||||
import { rightAlign } from "./shared.js";
|
||||
|
||||
function getSessionStats(ctx: ExtensionContext) {
|
||||
let cost = 0;
|
||||
let cxPct = 0;
|
||||
let cxColor: "success" | "warning" | "error" | "dim" = "dim";
|
||||
|
||||
try {
|
||||
for (const entry of ctx.sessionManager.getEntries()) {
|
||||
if (entry.type === "message") {
|
||||
const msg = (entry as any).message;
|
||||
if (msg?.role === "assistant" && msg.usage) {
|
||||
cost += msg.usage.cost?.total || 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
const cx = ctx.getContextUsage?.();
|
||||
if (cx?.percent != null) {
|
||||
cxPct = cx.percent;
|
||||
cxColor = cxPct >= 85 ? "error" : cxPct >= 60 ? "warning" : "success";
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
|
||||
return { cost, cxPct, cxColor };
|
||||
}
|
||||
|
||||
export function renderFooter(
|
||||
theme: Theme,
|
||||
footerData: ReadonlyFooterDataProvider,
|
||||
ctx: ExtensionContext,
|
||||
width: number,
|
||||
): string[] {
|
||||
const git = refreshGitStatus(process.cwd());
|
||||
const parts: string[] = [];
|
||||
|
||||
if (git.branch) {
|
||||
const dirty = git.dirty
|
||||
? theme.fg("error", "✗")
|
||||
: git.untracked
|
||||
? theme.fg("warning", "?")
|
||||
: theme.fg("success", "✓");
|
||||
const aheadBehind =
|
||||
git.ahead || git.behind
|
||||
? theme.fg("dim", `${git.ahead ? `↑${git.ahead}` : ""}${git.behind ? `↓${git.behind}` : ""}`)
|
||||
: "";
|
||||
parts.push(`${theme.fg("dim", "⎇")} ${git.branch} ${dirty}${aheadBehind ? ` ${aheadBehind}` : ""}`);
|
||||
}
|
||||
|
||||
const statuses = Array.from(footerData.getExtensionStatuses().entries())
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
.map(([, text]) => text)
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
if (statuses) {
|
||||
parts.push(theme.fg("dim", statuses));
|
||||
}
|
||||
|
||||
const left = parts.join(theme.fg("dim", " │ "));
|
||||
|
||||
const rightParts: string[] = [];
|
||||
if (ctx.model) {
|
||||
rightParts.push(theme.fg("muted", `${ctx.model.provider}/${ctx.model.id}`));
|
||||
}
|
||||
|
||||
const { cost, cxPct, cxColor } = getSessionStats(ctx);
|
||||
if (cost > 0) rightParts.push(theme.fg("warning", `$${cost.toFixed(2)}`));
|
||||
rightParts.push(theme.fg(cxColor, `${cxPct.toFixed(0)}%ctx`));
|
||||
|
||||
const right = rightParts.join(theme.fg("dim", " "));
|
||||
|
||||
const line = rightAlign(left, right, width);
|
||||
return [truncateToWidth(line, width, theme.fg("dim", "…"))];
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue