@hank-warren/pi-statusline 0.1.1 → 0.1.2
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/README.md +3 -1
- package/index.ts +13 -11
- package/package.json +1 -1
- package/redraw.ts +58 -17
package/README.md
CHANGED
|
@@ -30,7 +30,9 @@ PR lookups use the `gh` CLI when available and degrade gracefully without it.
|
|
|
30
30
|
|
|
31
31
|
Pi's fullscreen renderer only re-emits terminal rows whose rendered content changed. The worktree and session-ID lines are static for the life of a session, so if their cells ever desync from Pi's row cache — stale transcript text, a process sharing the tty, a stray escape sequence — nothing repaints them and the artifact persists.
|
|
32
32
|
|
|
33
|
-
To repair that
|
|
33
|
+
To repair that promptly without repeatedly clearing the screen, a one-second sweep changes an invisible marker on the fullscreen footer and requests a targeted differential render. Only the statusline rows compare as changed, so they repaint at most once per second even while the rest of Pi is actively rendering. A 30-second forced full redraw remains as a fallback for corruption outside the footer, throttled to at most one every five seconds and also requested at turn boundaries.
|
|
34
|
+
|
|
35
|
+
Both repair layers are skipped entirely in regular TUI mode, which reprints its whole block each frame and therefore self-heals. Only the slower forced fallback can drop a scrollback text selection highlight for a single frame.
|
|
34
36
|
|
|
35
37
|
## Install
|
|
36
38
|
|
package/index.ts
CHANGED
|
@@ -178,17 +178,19 @@ export default function statuslineExtension(pi: ExtensionAPI): void {
|
|
|
178
178
|
const cwd = basename(ctx.cwd) || ctx.cwd;
|
|
179
179
|
const model = ctx.model?.id.split("/").pop() || "no-model";
|
|
180
180
|
|
|
181
|
-
return
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
181
|
+
return fullRedraw.decorate(
|
|
182
|
+
renderStatusline(
|
|
183
|
+
{
|
|
184
|
+
model,
|
|
185
|
+
cwd,
|
|
186
|
+
cwdGit,
|
|
187
|
+
contextTokens: usage?.tokens ?? null,
|
|
188
|
+
contextWindow: usage?.contextWindow ?? ctx.model?.contextWindow ?? 0,
|
|
189
|
+
worktrees: tracker?.getWorktrees() ?? [],
|
|
190
|
+
sessionId: ctx.sessionManager.getSessionId(),
|
|
191
|
+
},
|
|
192
|
+
width,
|
|
193
|
+
),
|
|
192
194
|
);
|
|
193
195
|
},
|
|
194
196
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hank-warren/pi-statusline",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Compact Pi footer statusline: model ID, git branch/dirty/behind state, linked worktrees with PR numbers, context usage, and session ID.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
package/redraw.ts
CHANGED
|
@@ -3,24 +3,30 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Pi's fullscreen (alt-screen) renderer writes rows differentially: a row whose
|
|
5
5
|
* rendered content is byte-identical to the previous frame is never re-emitted.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* session id never changes at all. Once those terminal cells desync from Pi's
|
|
9
|
-
* row cache (stale transcript text, a process sharing the tty, a stray escape
|
|
10
|
-
* sequence), nothing repaints them and the garbage persists for the whole
|
|
11
|
-
* session.
|
|
6
|
+
* Once terminal cells desync from Pi's row cache, static statusline rows can
|
|
7
|
+
* therefore retain stale text indefinitely.
|
|
12
8
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
9
|
+
* This scheduler repairs the footer in two layers:
|
|
10
|
+
*
|
|
11
|
+
* 1. A short timer alternates between two visually equivalent ANSI reset
|
|
12
|
+
* prefixes and requests a normal render. Only the statusline rows compare as
|
|
13
|
+
* changed, even when Pi is otherwise idle.
|
|
14
|
+
* 2. `request()` keeps the slower forced full redraw fallback for corruption
|
|
15
|
+
* outside the footer.
|
|
16
|
+
*
|
|
17
|
+
* Both layers are fullscreen-only. Regular mode already reprints its block and
|
|
18
|
+
* does not need a periodic render.
|
|
17
19
|
*/
|
|
18
20
|
|
|
19
21
|
/** Minimum spacing between forced full redraws. */
|
|
20
22
|
export const DEFAULT_MIN_GAP_MS = 5_000;
|
|
21
|
-
/** Idle cadence
|
|
23
|
+
/** Idle cadence for targeted statusline-row repainting. */
|
|
24
|
+
export const DEFAULT_ROW_REFRESH_INTERVAL_MS = 1_000;
|
|
25
|
+
/** Slow fallback cadence for corruption outside the footer. */
|
|
22
26
|
export const DEFAULT_IDLE_INTERVAL_MS = 30_000;
|
|
23
27
|
|
|
28
|
+
const ANSI_RESET = "\x1b[0m";
|
|
29
|
+
|
|
24
30
|
/** The subset of Pi's TUI surface this scheduler needs. */
|
|
25
31
|
export interface RedrawTarget {
|
|
26
32
|
readonly mode: string;
|
|
@@ -29,6 +35,7 @@ export interface RedrawTarget {
|
|
|
29
35
|
|
|
30
36
|
export interface FullRedrawSchedulerOptions {
|
|
31
37
|
minGapMs?: number;
|
|
38
|
+
rowRefreshIntervalMs?: number;
|
|
32
39
|
idleIntervalMs?: number;
|
|
33
40
|
now?: () => number;
|
|
34
41
|
schedule?: (callback: () => void, intervalMs: number) => unknown;
|
|
@@ -48,9 +55,12 @@ function defaultCancel(handle: unknown): void {
|
|
|
48
55
|
|
|
49
56
|
export class FullRedrawScheduler {
|
|
50
57
|
private target: RedrawTarget | undefined;
|
|
51
|
-
private
|
|
58
|
+
private rowRefreshHandle: unknown;
|
|
59
|
+
private fullRedrawHandle: unknown;
|
|
52
60
|
private lastRedrawAt = Number.NEGATIVE_INFINITY;
|
|
61
|
+
private decorationPhase = 0;
|
|
53
62
|
private readonly minGapMs: number;
|
|
63
|
+
private readonly rowRefreshIntervalMs: number;
|
|
54
64
|
private readonly idleIntervalMs: number;
|
|
55
65
|
private readonly now: () => number;
|
|
56
66
|
private readonly schedule: (callback: () => void, intervalMs: number) => unknown;
|
|
@@ -58,34 +68,65 @@ export class FullRedrawScheduler {
|
|
|
58
68
|
|
|
59
69
|
constructor(options: FullRedrawSchedulerOptions = {}) {
|
|
60
70
|
this.minGapMs = options.minGapMs ?? DEFAULT_MIN_GAP_MS;
|
|
71
|
+
this.rowRefreshIntervalMs = options.rowRefreshIntervalMs ?? DEFAULT_ROW_REFRESH_INTERVAL_MS;
|
|
61
72
|
this.idleIntervalMs = options.idleIntervalMs ?? DEFAULT_IDLE_INTERVAL_MS;
|
|
62
73
|
this.now = options.now ?? Date.now;
|
|
63
74
|
this.schedule = options.schedule ?? defaultSchedule;
|
|
64
75
|
this.cancel = options.cancel ?? defaultCancel;
|
|
65
76
|
}
|
|
66
77
|
|
|
67
|
-
/** Bind to the TUI that owns the footer and start
|
|
78
|
+
/** Bind to the TUI that owns the footer and start both repair sweeps. */
|
|
68
79
|
attach(target: RedrawTarget): void {
|
|
69
80
|
this.detach();
|
|
70
81
|
this.target = target;
|
|
71
|
-
this.
|
|
82
|
+
this.lastRedrawAt = Number.NEGATIVE_INFINITY;
|
|
83
|
+
this.decorationPhase = 0;
|
|
84
|
+
this.rowRefreshHandle = this.schedule(() => {
|
|
85
|
+
this.requestRowRefresh();
|
|
86
|
+
}, this.rowRefreshIntervalMs);
|
|
87
|
+
this.fullRedrawHandle = this.schedule(() => {
|
|
72
88
|
this.request();
|
|
73
89
|
}, this.idleIntervalMs);
|
|
74
90
|
}
|
|
75
91
|
|
|
76
92
|
detach(): void {
|
|
77
|
-
if (this.
|
|
78
|
-
this.
|
|
93
|
+
if (this.rowRefreshHandle !== undefined) this.cancel(this.rowRefreshHandle);
|
|
94
|
+
if (this.fullRedrawHandle !== undefined) this.cancel(this.fullRedrawHandle);
|
|
95
|
+
this.rowRefreshHandle = undefined;
|
|
96
|
+
this.fullRedrawHandle = undefined;
|
|
79
97
|
this.target = undefined;
|
|
80
98
|
}
|
|
81
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Make footer rows byte-different without changing their visible contents.
|
|
102
|
+
* Pi will then clear and repaint those rows during an ordinary differential
|
|
103
|
+
* render instead of requiring a full-screen clear.
|
|
104
|
+
*/
|
|
105
|
+
decorate(lines: string[]): string[] {
|
|
106
|
+
if (this.target?.mode !== "fullscreen") return lines;
|
|
107
|
+
const prefix = this.decorationPhase === 0 ? ANSI_RESET : `${ANSI_RESET}${ANSI_RESET}`;
|
|
108
|
+
return lines.map((line) => `${prefix}${line}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Advance the invisible footer marker and request an ordinary render. The
|
|
113
|
+
* timer calls this at most once per interval, so active Pi frames do not
|
|
114
|
+
* repeatedly repaint otherwise-static statusline rows.
|
|
115
|
+
*/
|
|
116
|
+
requestRowRefresh(): boolean {
|
|
117
|
+
const target = this.target;
|
|
118
|
+
if (!target || target.mode !== "fullscreen") return false;
|
|
119
|
+
this.decorationPhase = (this.decorationPhase + 1) % 2;
|
|
120
|
+
target.requestRender();
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
82
124
|
/**
|
|
83
125
|
* Ask for a forced full redraw. No-ops outside fullscreen mode and while
|
|
84
126
|
* throttled. Returns whether a redraw was actually requested.
|
|
85
127
|
*/
|
|
86
128
|
request(): boolean {
|
|
87
129
|
const target = this.target;
|
|
88
|
-
// Regular mode reprints its whole block every frame, so it self-heals.
|
|
89
130
|
if (!target || target.mode !== "fullscreen") return false;
|
|
90
131
|
const now = this.now();
|
|
91
132
|
if (now - this.lastRedrawAt < this.minGapMs) return false;
|