@heyhuynhgiabuu/pi-pretty 0.6.26 → 0.6.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +34 -17
- package/dist/config.d.ts +16 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +30 -0
- package/dist/config.js.map +1 -1
- package/dist/fff.d.ts +16 -1
- package/dist/fff.d.ts.map +1 -1
- package/dist/fff.js +139 -33
- package/dist/fff.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +70 -24
- package/dist/index.js.map +1 -1
- package/dist/prompt-editor.d.ts +11 -0
- package/dist/prompt-editor.d.ts.map +1 -0
- package/dist/prompt-editor.js +78 -0
- package/dist/prompt-editor.js.map +1 -0
- package/dist/render.d.ts +9 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +25 -9
- package/dist/render.js.map +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +79 -32
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/find.d.ts.map +1 -1
- package/dist/tools/find.js +29 -10
- package/dist/tools/find.js.map +1 -1
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +20 -10
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/ls.d.ts.map +1 -1
- package/dist/tools/ls.js +11 -5
- package/dist/tools/ls.js.map +1 -1
- package/dist/tools/metrics.d.ts +6 -1
- package/dist/tools/metrics.d.ts.map +1 -1
- package/dist/tools/metrics.js +21 -8
- package/dist/tools/metrics.js.map +1 -1
- package/dist/tools/read.d.ts +2 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +97 -31
- package/dist/tools/read.js.map +1 -1
- package/dist/types.d.ts +13 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/working-indicator.d.ts +39 -31
- package/dist/working-indicator.d.ts.map +1 -1
- package/dist/working-indicator.js +114 -87
- package/dist/working-indicator.js.map +1 -1
- package/package.json +1 -1
- package/src/config.ts +51 -0
- package/src/fff.ts +158 -29
- package/src/index.ts +77 -24
- package/src/prompt-editor.ts +102 -0
- package/src/render.ts +30 -11
- package/src/tools/bash.ts +100 -34
- package/src/tools/find.ts +39 -17
- package/src/tools/grep.ts +26 -15
- package/src/tools/ls.ts +20 -10
- package/src/tools/metrics.ts +28 -8
- package/src/tools/read.ts +126 -31
- package/src/types.ts +14 -4
- package/src/working-indicator.ts +123 -95
package/src/working-indicator.ts
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* omp-style shimmer working indicator
|
|
2
|
+
* omp-style shimmer working indicator.
|
|
3
3
|
*
|
|
4
4
|
* Ports the shimmer sweep from oh-my-pi's `modes/theme/shimmer.ts`: the sweep
|
|
5
5
|
* is discretized into one pre-rendered frame per band position, cycled at
|
|
6
6
|
* 1000/30 ms so the band travels 30 cells/second — omp's exact speed.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* (`setWorkingVisible(false)`) and install our own zero-padding component via
|
|
12
|
-
* `setWidget(..., "aboveEditor")`, animating it with a 33ms interval.
|
|
8
|
+
* On hosts that expose Pi's embedded working-status API, frames are delegated
|
|
9
|
+
* to the host so the row is rendered in the editor's top border. Older hosts
|
|
10
|
+
* without that API use the zero-padding widget fallback above the editor.
|
|
13
11
|
*
|
|
14
|
-
* Frame layout: `<dim spinner> <shimmer text
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* Frame layout: `<dim spinner> <shimmer text>`, one full sweep per phrase —
|
|
13
|
+
* `texts` rotates through phrases chapter by chapter with a continuous spinner
|
|
14
|
+
* phase. The extension adds no interrupt hint or token-count suffix.
|
|
17
15
|
*/
|
|
18
16
|
|
|
19
17
|
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
@@ -61,6 +59,7 @@ export interface WorkingIndicatorSettings {
|
|
|
61
59
|
mode: WorkingIndicatorMode;
|
|
62
60
|
palette: WorkingIndicatorPalette;
|
|
63
61
|
bold: boolean;
|
|
62
|
+
/** @deprecated Retained for config compatibility; no interrupt hint is rendered. */
|
|
64
63
|
hint: boolean;
|
|
65
64
|
/** Tint mid/high tiers (and dim the spinner) with a per-session accent color. */
|
|
66
65
|
sessionAccent: boolean;
|
|
@@ -261,7 +260,7 @@ export interface WorkingFrameOptions {
|
|
|
261
260
|
spinnerColor?: string;
|
|
262
261
|
/** Wrap every frame in italic (pi's hidden-thinking label is italic). */
|
|
263
262
|
italic?: boolean;
|
|
264
|
-
/**
|
|
263
|
+
/** @deprecated Retained for call-site compatibility; suffixes are not rendered. */
|
|
265
264
|
hint?: string;
|
|
266
265
|
intervalMs?: number;
|
|
267
266
|
}
|
|
@@ -277,7 +276,6 @@ export function buildWorkingFrames(
|
|
|
277
276
|
): { frames: string[]; intervalMs: number } {
|
|
278
277
|
const intervalMs = options.intervalMs ?? WORKING_INTERVAL_MS;
|
|
279
278
|
const bold = options.bold ?? true;
|
|
280
|
-
const suffix = options.hint ?? "";
|
|
281
279
|
const withSpinner = options.spinner ?? true;
|
|
282
280
|
const spinnerColor = options.spinnerColor ?? options.ansi.low;
|
|
283
281
|
const highOpen = bold ? `${BOLD_OPEN}${options.ansi.high}` : options.ansi.high;
|
|
@@ -331,7 +329,7 @@ export function buildWorkingFrames(
|
|
|
331
329
|
text,
|
|
332
330
|
ranges,
|
|
333
331
|
Array.from({ length: total }, () => "mid" as Tier),
|
|
334
|
-
)}
|
|
332
|
+
)}`,
|
|
335
333
|
);
|
|
336
334
|
return { frames, intervalMs };
|
|
337
335
|
}
|
|
@@ -344,7 +342,7 @@ export function buildWorkingFrames(
|
|
|
344
342
|
text,
|
|
345
343
|
ranges,
|
|
346
344
|
Array.from({ length: total }, () => "high" as Tier),
|
|
347
|
-
)}
|
|
345
|
+
)}`,
|
|
348
346
|
);
|
|
349
347
|
continue;
|
|
350
348
|
}
|
|
@@ -353,20 +351,20 @@ export function buildWorkingFrames(
|
|
|
353
351
|
const goingRight = sweep < range;
|
|
354
352
|
const head = goingRight ? sweep : cycleCells - sweep;
|
|
355
353
|
const tiers = Array.from({ length: total }, (_, i) => tierFor(kittIntensity(i, head, goingRight)));
|
|
356
|
-
frames.push(`${spinnerPrefix(spinnerIndex++)}${paintFrame(text, ranges, tiers)}
|
|
354
|
+
frames.push(`${spinnerPrefix(spinnerIndex++)}${paintFrame(text, ranges, tiers)}`);
|
|
357
355
|
}
|
|
358
356
|
} else {
|
|
359
357
|
const period = total + CLASSIC_PADDING * 2;
|
|
360
358
|
for (let pos = 0; pos < period; pos++) {
|
|
361
359
|
const tiers = Array.from({ length: total }, (_, i) => tierFor(classicIntensity(i, pos)));
|
|
362
|
-
frames.push(`${spinnerPrefix(spinnerIndex++)}${paintFrame(text, ranges, tiers)}
|
|
360
|
+
frames.push(`${spinnerPrefix(spinnerIndex++)}${paintFrame(text, ranges, tiers)}`);
|
|
363
361
|
}
|
|
364
362
|
}
|
|
365
363
|
}
|
|
366
364
|
return { frames, intervalMs };
|
|
367
365
|
}
|
|
368
366
|
|
|
369
|
-
/**
|
|
367
|
+
/** @deprecated The working row no longer displays token stats. */
|
|
370
368
|
export function workingTokens(message: unknown): number {
|
|
371
369
|
const usage = (message as { usage?: { output?: unknown } } | undefined)?.usage;
|
|
372
370
|
const output = Number(usage?.output);
|
|
@@ -391,8 +389,8 @@ export interface WidgetTuiLike {
|
|
|
391
389
|
|
|
392
390
|
/**
|
|
393
391
|
* Zero-padding working-row component. The host renders whatever `render()`
|
|
394
|
-
* returns with no margins, so the row sits flush-left
|
|
395
|
-
*
|
|
392
|
+
* returns with no margins, so the legacy widget row sits flush-left without
|
|
393
|
+
* relying on the host loader's padding.
|
|
396
394
|
*/
|
|
397
395
|
export class WorkingWidget {
|
|
398
396
|
#frames: string[] = [];
|
|
@@ -402,7 +400,6 @@ export class WorkingWidget {
|
|
|
402
400
|
#tui: WidgetTuiLike | undefined;
|
|
403
401
|
#started = false;
|
|
404
402
|
#disposed = false;
|
|
405
|
-
#stats: string | undefined;
|
|
406
403
|
|
|
407
404
|
setFrames(frames: string[], intervalMs: number): void {
|
|
408
405
|
this.#frames = frames;
|
|
@@ -421,11 +418,8 @@ export class WorkingWidget {
|
|
|
421
418
|
this.#tui?.requestRender();
|
|
422
419
|
}
|
|
423
420
|
|
|
424
|
-
/**
|
|
425
|
-
|
|
426
|
-
setStats(text: string | undefined): void {
|
|
427
|
-
this.#stats = text || undefined;
|
|
428
|
-
}
|
|
421
|
+
/** @deprecated Token stats are owned by Pi and are no longer rendered here. */
|
|
422
|
+
setStats(_text: string | undefined): void {}
|
|
429
423
|
|
|
430
424
|
start(): void {
|
|
431
425
|
if (this.#disposed || this.#started) return;
|
|
@@ -451,8 +445,7 @@ export class WorkingWidget {
|
|
|
451
445
|
render(width: number): string[] {
|
|
452
446
|
if (!this.#started || this.#frames.length === 0) return [];
|
|
453
447
|
const frame = this.#frames[this.#index % this.#frames.length] ?? "";
|
|
454
|
-
|
|
455
|
-
return [truncateToWidth(frame + suffix, Math.max(1, width))];
|
|
448
|
+
return [truncateToWidth(frame, Math.max(1, width))];
|
|
456
449
|
}
|
|
457
450
|
|
|
458
451
|
invalidate(): void {
|
|
@@ -500,7 +493,7 @@ export interface PerRowThinkingLabels {
|
|
|
500
493
|
/** Mark the message currently streaming a thinking block (its row animates). */
|
|
501
494
|
setActive(timestamp: number): void;
|
|
502
495
|
/** Freeze a message's completed thinking duration in ms (its row shows
|
|
503
|
-
* `Thought for
|
|
496
|
+
* `Thought for X` derived from this). */
|
|
504
497
|
complete(timestamp: number, durationMs: number): void;
|
|
505
498
|
/** Drop the active mark (run ended); completed rows keep their durations. */
|
|
506
499
|
clearActive(): void;
|
|
@@ -518,7 +511,7 @@ interface RowInternals {
|
|
|
518
511
|
* Intercept the host's per-row label fan-out method
|
|
519
512
|
* (`AssistantMessageComponent.prototype.setHiddenThinkingLabel`): each row
|
|
520
513
|
* substitutes the incoming global label with its own — the streaming row
|
|
521
|
-
* animates, completed rows stay frozen at their own `Thought for
|
|
514
|
+
* animates, completed rows stay frozen at their own `Thought for X`, unknown
|
|
522
515
|
* rows fall back to pi's default. Any per-call failure passes the incoming
|
|
523
516
|
* label through unchanged, so a host internals change degrades to pi-pretty's
|
|
524
517
|
* global-label behavior instead of breaking. Durations live per session.
|
|
@@ -546,7 +539,7 @@ export function installPerRowThinkingLabels(componentClass: unknown): PerRowThin
|
|
|
546
539
|
if (typeof ts !== "number") return incoming;
|
|
547
540
|
if (ts === state.activeTs) return incoming;
|
|
548
541
|
const ms = state.completed.get(ts);
|
|
549
|
-
return ms === undefined ? THINKING_LABEL :
|
|
542
|
+
return ms === undefined ? THINKING_LABEL : `${THOUGHT_LABEL} ${formatThinkingDuration(ms)}`;
|
|
550
543
|
} catch {
|
|
551
544
|
return incoming;
|
|
552
545
|
}
|
|
@@ -595,11 +588,15 @@ export interface ThinkingTimer {
|
|
|
595
588
|
restore(): void;
|
|
596
589
|
}
|
|
597
590
|
|
|
598
|
-
const THINKING_LABEL = "Thinking...";
|
|
591
|
+
const THINKING_LABEL = " Thinking...";
|
|
592
|
+
const THOUGHT_LABEL = " Thought for";
|
|
593
|
+
const padThinkingLabel = (label: string): string => (label.startsWith(" ") ? label : ` ${label}`);
|
|
599
594
|
|
|
600
|
-
/** Format elapsed
|
|
595
|
+
/** Format sub-second elapsed time as milliseconds, then use compact whole seconds. */
|
|
601
596
|
export function formatThinkingDuration(elapsedMs: number): string {
|
|
602
|
-
const
|
|
597
|
+
const wholeMs = Math.max(0, Math.floor(elapsedMs));
|
|
598
|
+
if (wholeMs < 1000) return `${wholeMs}ms`;
|
|
599
|
+
const totalSeconds = Math.floor(wholeMs / 1000);
|
|
603
600
|
const seconds = totalSeconds % 60;
|
|
604
601
|
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
605
602
|
const minutes = totalMinutes % 60;
|
|
@@ -635,7 +632,7 @@ export function createThinkingTimer(
|
|
|
635
632
|
if (terminal) return undefined;
|
|
636
633
|
terminal = true;
|
|
637
634
|
const ms = elapsed();
|
|
638
|
-
animator.show(
|
|
635
|
+
animator.show(`${THOUGHT_LABEL} ${formatThinkingDuration(ms)}`);
|
|
639
636
|
return ms;
|
|
640
637
|
},
|
|
641
638
|
restore(): void {
|
|
@@ -662,8 +659,8 @@ export function thinkingBlockActive(message: unknown): boolean {
|
|
|
662
659
|
* the working row. Pi renders the label as static italic `thinkingText` text;
|
|
663
660
|
* the only extension lever is `setHiddenThinkingLabel(label)`, which rebuilds
|
|
664
661
|
* chat children, so index.ts drives it at 30fps only while the streaming
|
|
665
|
-
* message's last block is thinking. The elapsed label is rebuilt
|
|
666
|
-
*
|
|
662
|
+
* message's last block is thinking. The elapsed label is rebuilt when its
|
|
663
|
+
* displayed duration changes; the shimmer frame still advances every tick.
|
|
667
664
|
*
|
|
668
665
|
* Tiers: low = theme `thinkingText` (pi's own label look), mid/high = session
|
|
669
666
|
* accent when enabled; every frame is italic like pi's label; no spinner.
|
|
@@ -692,14 +689,18 @@ export function createThinkingLabelAnimator(
|
|
|
692
689
|
const accent = hexToAnsiFg(sessionAccentHex(sessionName));
|
|
693
690
|
if (accent) ansi = { ...ansi, mid: accent, high: accent };
|
|
694
691
|
}
|
|
695
|
-
const buildLabelFrames = (label: string): string[] =>
|
|
696
|
-
|
|
692
|
+
const buildLabelFrames = (label: string): string[] => {
|
|
693
|
+
const padded = padThinkingLabel(label);
|
|
694
|
+
const text = padded.trimStart();
|
|
695
|
+
const leftPadding = padded.slice(0, padded.length - text.length);
|
|
696
|
+
return buildWorkingFrames([text], {
|
|
697
697
|
mode: workSettings.mode,
|
|
698
698
|
ansi,
|
|
699
699
|
bold: workSettings.bold,
|
|
700
700
|
spinner: false,
|
|
701
701
|
italic: true,
|
|
702
|
-
}).frames;
|
|
702
|
+
}).frames.map((frame) => `${leftPadding}${frame}`);
|
|
703
|
+
};
|
|
703
704
|
|
|
704
705
|
let currentLabel = THINKING_LABEL;
|
|
705
706
|
let frames = buildLabelFrames(currentLabel);
|
|
@@ -711,9 +712,10 @@ export function createThinkingLabelAnimator(
|
|
|
711
712
|
return frames;
|
|
712
713
|
},
|
|
713
714
|
tick(label = THINKING_LABEL): void {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
715
|
+
const paddedLabel = padThinkingLabel(label);
|
|
716
|
+
if (paddedLabel !== currentLabel) {
|
|
717
|
+
frames = buildLabelFrames(paddedLabel);
|
|
718
|
+
currentLabel = paddedLabel;
|
|
717
719
|
}
|
|
718
720
|
// A static frame changes only when its elapsed-time label changes.
|
|
719
721
|
if (appliedLabel === currentLabel && frames.length === 1) return;
|
|
@@ -722,7 +724,7 @@ export function createThinkingLabelAnimator(
|
|
|
722
724
|
appliedLabel = currentLabel;
|
|
723
725
|
},
|
|
724
726
|
show(label: string): void {
|
|
725
|
-
ui.setHiddenThinkingLabel(label);
|
|
727
|
+
ui.setHiddenThinkingLabel(padThinkingLabel(label));
|
|
726
728
|
},
|
|
727
729
|
restore(): void {
|
|
728
730
|
// undefined → pi falls back to its default static label.
|
|
@@ -733,15 +735,24 @@ export function createThinkingLabelAnimator(
|
|
|
733
735
|
|
|
734
736
|
// ─── Installation ────────────────────────────────────────────────────────────
|
|
735
737
|
|
|
736
|
-
/** Structural slice of pi's Theme needed for the
|
|
738
|
+
/** Structural slice of pi's Theme needed for the palette and thinking label. */
|
|
737
739
|
export interface WorkingThemeLike {
|
|
738
|
-
/**
|
|
740
|
+
/** Retained for compatibility with older theme adapters. */
|
|
739
741
|
fg?: (name: "dim", text: string) => string;
|
|
740
742
|
getFgAnsi?: (name: "dim" | "muted" | "accent" | "thinkingText") => string;
|
|
741
743
|
}
|
|
742
744
|
|
|
745
|
+
export interface WorkingStatusOptionsLike {
|
|
746
|
+
frames?: string[];
|
|
747
|
+
intervalMs?: number;
|
|
748
|
+
}
|
|
749
|
+
|
|
743
750
|
export interface WorkingUiLike {
|
|
744
751
|
theme?: WorkingThemeLike;
|
|
752
|
+
/** Hosts with Pi's embedded working-status API render this in the editor border. */
|
|
753
|
+
setWorkingIndicator?: (options?: WorkingStatusOptionsLike) => void;
|
|
754
|
+
/** Clear the host message appended after a custom native frame. */
|
|
755
|
+
setWorkingMessage?: (message?: string) => void;
|
|
745
756
|
setWorkingVisible(visible: boolean): void;
|
|
746
757
|
setWidget(
|
|
747
758
|
key: string,
|
|
@@ -750,6 +761,7 @@ export interface WorkingUiLike {
|
|
|
750
761
|
): void;
|
|
751
762
|
}
|
|
752
763
|
|
|
764
|
+
/** @deprecated Retained for compatibility with the former hint-resolution seam. */
|
|
753
765
|
export interface KeybindingsLike {
|
|
754
766
|
getKeys(name: string): string[];
|
|
755
767
|
}
|
|
@@ -759,11 +771,11 @@ export interface WorkingIndicatorController {
|
|
|
759
771
|
start(): void;
|
|
760
772
|
/** Streaming ended — hide the row and pause the animation. */
|
|
761
773
|
stop(): void;
|
|
762
|
-
/** Remove the
|
|
774
|
+
/** Remove the indicator and stop the animation. */
|
|
763
775
|
dispose(): void;
|
|
764
|
-
/**
|
|
776
|
+
/** @deprecated Token stats are owned by Pi and are no longer rendered here. */
|
|
765
777
|
setStats(text: string | undefined): void;
|
|
766
|
-
/** Request a host render
|
|
778
|
+
/** Request a host render for consumers that drive another animated label. */
|
|
767
779
|
requestRender(): void;
|
|
768
780
|
/** Pre-rendered frames (exposed for diagnostics). */
|
|
769
781
|
readonly frames: readonly string[];
|
|
@@ -771,52 +783,14 @@ export interface WorkingIndicatorController {
|
|
|
771
783
|
|
|
772
784
|
const WIDGET_KEY = "pi-pretty-working";
|
|
773
785
|
|
|
774
|
-
async function loadHostKeybindings(): Promise<KeybindingsLike | undefined> {
|
|
775
|
-
try {
|
|
776
|
-
const tui = (await import("@earendil-works/pi-tui")) as typeof import("@earendil-works/pi-tui");
|
|
777
|
-
return tui.getKeybindings?.();
|
|
778
|
-
} catch {
|
|
779
|
-
return undefined;
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
function formatKeyText(keys: string[]): string {
|
|
784
|
-
const darwin = process.platform === "darwin";
|
|
785
|
-
return keys
|
|
786
|
-
.join("/")
|
|
787
|
-
.split("/")
|
|
788
|
-
.map((key) =>
|
|
789
|
-
key
|
|
790
|
-
.split("+")
|
|
791
|
-
.map((part) => (darwin && part.toLowerCase() === "alt" ? "option" : part))
|
|
792
|
-
.join("+"),
|
|
793
|
-
)
|
|
794
|
-
.join("/");
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
async function resolveHint(
|
|
798
|
-
ui: WorkingUiLike,
|
|
799
|
-
deps?: { getKeybindings?: () => KeybindingsLike | undefined | Promise<KeybindingsLike | undefined> },
|
|
800
|
-
): Promise<string | undefined> {
|
|
801
|
-
try {
|
|
802
|
-
const loader = deps?.getKeybindings ?? loadHostKeybindings;
|
|
803
|
-
const keybindings = await loader();
|
|
804
|
-
const keys = keybindings?.getKeys?.("app.interrupt");
|
|
805
|
-
if (!keys?.length) return undefined;
|
|
806
|
-
const text = ` (${formatKeyText(keys)} to interrupt)`;
|
|
807
|
-
return ui.theme?.fg ? ui.theme.fg("dim", text) : text;
|
|
808
|
-
} catch {
|
|
809
|
-
return undefined;
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
|
|
813
786
|
/**
|
|
814
|
-
*
|
|
787
|
+
* Install the custom working shimmer and return its lifecycle controller.
|
|
815
788
|
*
|
|
816
|
-
*
|
|
817
|
-
*
|
|
818
|
-
*
|
|
819
|
-
*
|
|
789
|
+
* Current Pi hosts receive the pre-rendered frames through the embedded
|
|
790
|
+
* working-status API, which places them in the editor's top border. Older
|
|
791
|
+
* hosts use a zero-padding widget above the editor. The host lifecycle drives
|
|
792
|
+
* the controller (`start` on agent_start, `stop` on agent_end); disabled or
|
|
793
|
+
* empty phrase settings leave host defaults untouched.
|
|
820
794
|
*/
|
|
821
795
|
export async function installWorkingIndicator(
|
|
822
796
|
ui: WorkingUiLike,
|
|
@@ -835,6 +809,7 @@ export async function installWorkingIndicator(
|
|
|
835
809
|
if (!settings.enabled) return noopController;
|
|
836
810
|
const texts = settings.texts.map((t) => t.trim()).filter((t) => t.length > 0);
|
|
837
811
|
if (texts.length === 0) return noopController;
|
|
812
|
+
void deps; // Retained for compatibility; interrupt hints are no longer resolved here.
|
|
838
813
|
|
|
839
814
|
const ansiResolved = resolvePaletteAnsi(settings.palette, ui.theme);
|
|
840
815
|
let ansi = ansiResolved;
|
|
@@ -847,20 +822,73 @@ export async function installWorkingIndicator(
|
|
|
847
822
|
spinnerColor = hexToAnsiFg(dimAccentHex(accentHex));
|
|
848
823
|
}
|
|
849
824
|
}
|
|
850
|
-
const hint = settings.hint ? await resolveHint(ui, deps) : undefined;
|
|
851
825
|
const { frames, intervalMs } = buildWorkingFrames(texts, {
|
|
852
826
|
mode: settings.mode,
|
|
853
827
|
ansi,
|
|
854
828
|
bold: settings.bold,
|
|
855
829
|
spinnerColor,
|
|
856
|
-
hint,
|
|
857
830
|
});
|
|
858
831
|
if (frames.length === 0) return noopController;
|
|
859
832
|
|
|
833
|
+
const setWorkingIndicator = ui.setWorkingIndicator;
|
|
834
|
+
const setWorkingMessage = ui.setWorkingMessage;
|
|
835
|
+
if (typeof setWorkingIndicator === "function" && typeof setWorkingMessage === "function") {
|
|
836
|
+
let started = false;
|
|
837
|
+
let disposed = false;
|
|
838
|
+
const updateNativeMessage = (): void => {
|
|
839
|
+
// Keep Pi's built-in message hidden because the custom frame already
|
|
840
|
+
// supplies the working label; no hint or token suffix is added here.
|
|
841
|
+
setWorkingMessage.call(ui, "");
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
try {
|
|
845
|
+
setWorkingIndicator.call(ui, { frames, intervalMs });
|
|
846
|
+
// Keep the native status row hidden until the extension lifecycle starts it.
|
|
847
|
+
// An empty message is intentional: all visible content lives in our frames.
|
|
848
|
+
updateNativeMessage();
|
|
849
|
+
ui.setWorkingVisible(false);
|
|
850
|
+
} catch (error: unknown) {
|
|
851
|
+
try {
|
|
852
|
+
setWorkingIndicator.call(ui);
|
|
853
|
+
setWorkingMessage.call(ui);
|
|
854
|
+
} catch {
|
|
855
|
+
// Best-effort rollback; the caller still restores host visibility.
|
|
856
|
+
}
|
|
857
|
+
throw error;
|
|
858
|
+
}
|
|
859
|
+
return {
|
|
860
|
+
start: () => {
|
|
861
|
+
if (disposed || started) return;
|
|
862
|
+
started = true;
|
|
863
|
+
updateNativeMessage();
|
|
864
|
+
ui.setWorkingVisible(true);
|
|
865
|
+
},
|
|
866
|
+
stop: () => {
|
|
867
|
+
if (!started) return;
|
|
868
|
+
started = false;
|
|
869
|
+
ui.setWorkingVisible(false);
|
|
870
|
+
},
|
|
871
|
+
dispose: () => {
|
|
872
|
+
if (disposed) return;
|
|
873
|
+
if (started) {
|
|
874
|
+
started = false;
|
|
875
|
+
ui.setWorkingVisible(false);
|
|
876
|
+
}
|
|
877
|
+
disposed = true;
|
|
878
|
+
setWorkingIndicator.call(ui);
|
|
879
|
+
setWorkingMessage.call(ui);
|
|
880
|
+
},
|
|
881
|
+
setStats: (_text: string | undefined) => {},
|
|
882
|
+
requestRender: () => updateNativeMessage(),
|
|
883
|
+
frames,
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
|
|
860
887
|
const widget = new WorkingWidget();
|
|
861
888
|
widget.setFrames(frames, intervalMs);
|
|
862
889
|
// Widget first, visibility second: if setWidget throws (older host without
|
|
863
|
-
// the API) the host loader was never hidden and pi's
|
|
890
|
+
// the native working-status API) the host loader was never hidden and pi's
|
|
891
|
+
// default remains.
|
|
864
892
|
ui.setWidget(
|
|
865
893
|
WIDGET_KEY,
|
|
866
894
|
(tui) => {
|
|
@@ -877,7 +905,7 @@ export async function installWorkingIndicator(
|
|
|
877
905
|
widget.dispose();
|
|
878
906
|
ui.setWidget(WIDGET_KEY, undefined);
|
|
879
907
|
},
|
|
880
|
-
setStats: (
|
|
908
|
+
setStats: (_text: string | undefined) => {},
|
|
881
909
|
requestRender: () => widget.requestRender(),
|
|
882
910
|
frames,
|
|
883
911
|
};
|