File size: 3,371 Bytes
bc575bc | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | import { type Component, Loader, type TUI, truncateToWidth } from "@earendil-works/pi-tui";
import type { WorkingIndicatorOptions } from "../../../core/extensions/index.ts";
import { theme } from "../theme/theme.ts";
import { CountdownTimer } from "./countdown-timer.ts";
import { keyText } from "./keybinding-hints.ts";
export type StatusIndicatorKind = "working" | "retry" | "compaction" | "branchSummary";
export class StatusIndicator extends Loader {
readonly kind: StatusIndicatorKind;
constructor(
kind: StatusIndicatorKind,
ui: TUI,
spinnerColorFn: (str: string) => string,
messageColorFn: (str: string) => string,
message: string,
indicator?: WorkingIndicatorOptions,
) {
super(ui, spinnerColorFn, messageColorFn, message, indicator);
this.kind = kind;
}
renderInBorder(width: number): string {
const line = super.render(width + 2)[1] ?? "";
return truncateToWidth(line.startsWith(" ") ? line.slice(1).trimEnd() : line.trimEnd(), width, "");
}
renderSpinnerInBorder(width: number): string {
return truncateToWidth(this.getRenderedIndicator(), width, "");
}
dispose(): void {
this.stop();
}
}
export class WorkingStatusIndicator extends StatusIndicator {
constructor(ui: TUI, message: string, indicator?: WorkingIndicatorOptions, colorFn?: (text: string) => string) {
super(
"working",
ui,
colorFn ?? ((text) => theme.fg("accent", text)),
colorFn ?? ((text) => theme.fg("muted", text)),
message,
indicator,
);
}
}
export class RetryStatusIndicator extends StatusIndicator {
private countdown: CountdownTimer | undefined;
constructor(ui: TUI, attempt: number, maxAttempts: number, delayMs: number) {
const retryMessage = (seconds: number) =>
`Retrying (${attempt}/${maxAttempts}) in ${seconds}s... (${keyText("app.interrupt")} to cancel)`;
super(
"retry",
ui,
(spinner) => theme.fg("warning", spinner),
(text) => theme.fg("muted", text),
retryMessage(Math.ceil(delayMs / 1000)),
);
this.countdown = new CountdownTimer(
delayMs,
ui,
(seconds) => {
this.setMessage(retryMessage(seconds));
},
() => {
this.countdown = undefined;
},
);
}
override dispose(): void {
this.countdown?.dispose();
this.countdown = undefined;
super.dispose();
}
}
export type CompactionStatusReason = "manual" | "threshold" | "overflow";
export class CompactionStatusIndicator extends StatusIndicator {
constructor(ui: TUI, reason: CompactionStatusReason) {
const cancelHint = `(${keyText("app.interrupt")} to cancel)`;
const label =
reason === "manual"
? `Compacting context... ${cancelHint}`
: `${reason === "overflow" ? "Context overflow detected, " : ""}Auto-compacting... ${cancelHint}`;
super(
"compaction",
ui,
(spinner) => theme.fg("accent", spinner),
(text) => theme.fg("muted", text),
label,
);
}
}
export class BranchSummaryStatusIndicator extends StatusIndicator {
constructor(ui: TUI) {
super(
"branchSummary",
ui,
(spinner) => theme.fg("accent", spinner),
(text) => theme.fg("muted", text),
`Summarizing branch... (${keyText("app.interrupt")} to cancel)`,
);
}
}
export class IdleStatus implements Component {
invalidate(): void {
// No cached state to invalidate.
}
render(width: number): string[] {
const emptyLine = " ".repeat(width);
return [emptyLine, emptyLine];
}
}
|