@pi-unipi/footer 0.1.3 → 2.0.0
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 +73 -158
- package/package.json +1 -1
- package/src/commands.ts +38 -123
- package/src/config.ts +16 -0
- package/src/help.ts +160 -0
- package/src/index.ts +48 -15
- package/src/presets.ts +41 -32
- package/src/registry/index.ts +1 -1
- package/src/rendering/icons.ts +77 -59
- package/src/rendering/renderer.ts +246 -80
- package/src/rendering/theme.ts +56 -29
- package/src/segments/compactor.ts +76 -30
- package/src/segments/core.ts +124 -15
- package/src/segments/kanboard.ts +25 -9
- package/src/segments/mcp.ts +25 -16
- package/src/segments/memory.ts +9 -6
- package/src/segments/notify.ts +16 -5
- package/src/segments/ralph.ts +23 -8
- package/src/segments/status-ext.ts +1 -1
- package/src/segments/workflow.ts +41 -18
- package/src/tps-tracker.ts +204 -0
- package/src/tui/settings-tui.ts +253 -63
- package/src/types.ts +51 -12
package/src/tui/settings-tui.ts
CHANGED
|
@@ -1,20 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @pi-unipi/footer — Settings TUI
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Unified settings overlay with 3 categories:
|
|
5
|
+
* - Appearance: preset, separator, icon style, full labels
|
|
6
|
+
* - Segments: group → segment drill-down
|
|
7
|
+
* - Labels & Help: label mode, zone headers
|
|
8
|
+
*
|
|
9
|
+
* Uses pi-tui SettingsList for vim/arrow keybinding support.
|
|
7
10
|
*/
|
|
8
11
|
|
|
9
|
-
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
12
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
10
13
|
import { SettingsList, type SettingItem, type SettingsListTheme } from "@mariozechner/pi-tui";
|
|
11
14
|
import { loadFooterSettings, saveFooterSettings } from "../config.js";
|
|
12
|
-
import
|
|
15
|
+
import { PRESET_NAMES } from "../presets.js";
|
|
16
|
+
import { setIconStyle } from "../rendering/icons.js";
|
|
17
|
+
import type { FooterGroup, FooterSettings, SeparatorStyle, IconStyle } from "../types.js";
|
|
13
18
|
|
|
14
19
|
// ─── Section types ─────────────────────────────────────────────────────
|
|
15
20
|
|
|
16
|
-
type Section = "
|
|
17
|
-
const SECTIONS: Section[] = ["
|
|
21
|
+
type Section = "appearance" | "segments" | "labels";
|
|
22
|
+
const SECTIONS: Section[] = ["appearance", "segments", "labels"];
|
|
23
|
+
const SECTION_LABELS: Record<Section, string> = {
|
|
24
|
+
appearance: "Appearance",
|
|
25
|
+
segments: "Segments",
|
|
26
|
+
labels: "Labels & Help",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// ─── Valid option values ───────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
const SEPARATOR_STYLES: SeparatorStyle[] = ["powerline", "powerline-thin", "slash", "pipe", "dot", "ascii"];
|
|
32
|
+
const ICON_STYLES: IconStyle[] = ["nerd", "emoji", "text"];
|
|
33
|
+
const ZONE_SEPARATOR_OPTIONS = ["│", "╎", "·", "─", "none"];
|
|
18
34
|
|
|
19
35
|
// ─── Theme for SettingsList ────────────────────────────────────────────
|
|
20
36
|
|
|
@@ -44,17 +60,16 @@ function borderLine(innerWidth: number, edge: "top" | "bottom"): string {
|
|
|
44
60
|
return `\x1b[90m${left}${"─".repeat(innerWidth)}${right}\x1b[0m`;
|
|
45
61
|
}
|
|
46
62
|
|
|
47
|
-
// Visible width helper (strip ANSI)
|
|
48
63
|
function visibleWidth(text: string): number {
|
|
49
64
|
return text.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
50
65
|
}
|
|
51
66
|
|
|
52
67
|
// ─── Show the footer settings overlay ──────────────────────────────────
|
|
53
68
|
|
|
54
|
-
export function showFooterSettings(ctx:
|
|
55
|
-
ctx.ui.custom(
|
|
56
|
-
(tui
|
|
57
|
-
const overlay = new FooterSettingsOverlay(groups);
|
|
69
|
+
export function showFooterSettings(ctx: ExtensionCommandContext, groups: FooterGroup[], onSettingsChanged?: () => void): void {
|
|
70
|
+
ctx.ui.custom<void>(
|
|
71
|
+
(tui, _theme, _keybindings, done) => {
|
|
72
|
+
const overlay = new FooterSettingsOverlay(groups, onSettingsChanged);
|
|
58
73
|
overlay.onClose = () => done();
|
|
59
74
|
|
|
60
75
|
return {
|
|
@@ -71,8 +86,7 @@ export function showFooterSettings(ctx: any, groups: FooterGroup[]): void {
|
|
|
71
86
|
{
|
|
72
87
|
overlay: true,
|
|
73
88
|
overlayOptions: () => ({
|
|
74
|
-
|
|
75
|
-
horizontalAlign: "center",
|
|
89
|
+
anchor: "center" as const,
|
|
76
90
|
}),
|
|
77
91
|
},
|
|
78
92
|
).catch(() => {
|
|
@@ -82,53 +96,83 @@ export function showFooterSettings(ctx: any, groups: FooterGroup[]): void {
|
|
|
82
96
|
|
|
83
97
|
// ─── Footer settings overlay component ─────────────────────────────────
|
|
84
98
|
|
|
85
|
-
/**
|
|
86
|
-
* Footer settings overlay component.
|
|
87
|
-
* Uses SettingsList from pi-tui for proper vim/arrow keybinding support.
|
|
88
|
-
* Two sections: Groups (toggle groups) and Segments (toggle segments within a group).
|
|
89
|
-
*/
|
|
90
99
|
class FooterSettingsOverlay {
|
|
91
100
|
private settings: FooterSettings;
|
|
92
101
|
private groups: FooterGroup[];
|
|
93
|
-
private section: Section = "
|
|
102
|
+
private section: Section = "appearance";
|
|
94
103
|
private selectedGroupId: string | null = null;
|
|
95
104
|
onClose?: () => void;
|
|
105
|
+
private onSettingsChanged?: () => void;
|
|
96
106
|
|
|
97
107
|
// Per-section SettingsList instances
|
|
108
|
+
private appearanceList!: SettingsList;
|
|
98
109
|
private groupList!: SettingsList;
|
|
99
110
|
private segmentList: SettingsList | null = null;
|
|
111
|
+
private labelsList!: SettingsList;
|
|
100
112
|
|
|
101
|
-
constructor(groups: FooterGroup[]) {
|
|
113
|
+
constructor(groups: FooterGroup[], onSettingsChanged?: () => void) {
|
|
102
114
|
this.settings = loadFooterSettings();
|
|
103
115
|
this.groups = groups;
|
|
116
|
+
this.onSettingsChanged = onSettingsChanged;
|
|
117
|
+
this.buildAppearanceList();
|
|
104
118
|
this.buildGroupList();
|
|
119
|
+
this.buildLabelsList();
|
|
105
120
|
}
|
|
106
121
|
|
|
107
122
|
invalidate(): void {
|
|
123
|
+
this.appearanceList?.invalidate();
|
|
108
124
|
this.groupList?.invalidate();
|
|
109
125
|
this.segmentList?.invalidate();
|
|
126
|
+
this.labelsList?.invalidate();
|
|
110
127
|
}
|
|
111
128
|
|
|
112
129
|
handleInput(data: string): void {
|
|
113
|
-
// Tab
|
|
130
|
+
// Tab cycles sections
|
|
114
131
|
if (data === "\t" || data === "\x1b[Z") {
|
|
115
|
-
|
|
116
|
-
|
|
132
|
+
const idx = SECTIONS.indexOf(this.section);
|
|
133
|
+
if (data === "\t") {
|
|
134
|
+
this.section = SECTIONS[(idx + 1) % SECTIONS.length];
|
|
117
135
|
} else {
|
|
118
|
-
this.section =
|
|
136
|
+
this.section = SECTIONS[(idx - 1 + SECTIONS.length) % SECTIONS.length];
|
|
137
|
+
}
|
|
138
|
+
// If leaving segments drill-down, go back to groups
|
|
139
|
+
if (this.section !== "segments") {
|
|
119
140
|
this.selectedGroupId = null;
|
|
141
|
+
this.segmentList = null;
|
|
120
142
|
}
|
|
121
143
|
return;
|
|
122
144
|
}
|
|
123
145
|
|
|
124
|
-
//
|
|
125
|
-
if (data === "
|
|
146
|
+
// q — always close
|
|
147
|
+
if (data === "q") {
|
|
126
148
|
this.onClose?.();
|
|
127
149
|
return;
|
|
128
150
|
}
|
|
129
151
|
|
|
130
|
-
//
|
|
131
|
-
if (data === "\
|
|
152
|
+
// Escape — back from drill-down, or close at root
|
|
153
|
+
if (data === "\x1b") {
|
|
154
|
+
if (this.section === "segments" && this.selectedGroupId) {
|
|
155
|
+
this.backToGroups();
|
|
156
|
+
} else {
|
|
157
|
+
this.onClose?.();
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// j — navigate down (same as down arrow)
|
|
163
|
+
if (data === "j") {
|
|
164
|
+
this.currentList?.handleInput("\x1b[B");
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// k — navigate up (same as up arrow)
|
|
169
|
+
if (data === "k") {
|
|
170
|
+
this.currentList?.handleInput("\x1b[A");
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// l — drill into group from segments section (same as Enter)
|
|
175
|
+
if (data === "l" && this.section === "segments" && !this.selectedGroupId) {
|
|
132
176
|
const focusedId = this.getFocusedGroupId();
|
|
133
177
|
if (focusedId) {
|
|
134
178
|
this.enterSegmentsMode(focusedId);
|
|
@@ -136,8 +180,35 @@ class FooterSettingsOverlay {
|
|
|
136
180
|
return;
|
|
137
181
|
}
|
|
138
182
|
|
|
139
|
-
//
|
|
140
|
-
if (
|
|
183
|
+
// Space — toggle on/off
|
|
184
|
+
if (data === " ") {
|
|
185
|
+
this.currentList?.handleInput("\r");
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// h — back from segment drill-down
|
|
190
|
+
if (data === "h" && this.section === "segments" && this.selectedGroupId) {
|
|
191
|
+
this.backToGroups();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// / — focus search in segments section
|
|
196
|
+
if (data === "/") {
|
|
197
|
+
this.currentList?.handleInput("/");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Enter in segments/groups mode — enter segments for the focused group
|
|
202
|
+
if (data === "\r" && this.section === "segments" && !this.selectedGroupId) {
|
|
203
|
+
const focusedId = this.getFocusedGroupId();
|
|
204
|
+
if (focusedId) {
|
|
205
|
+
this.enterSegmentsMode(focusedId);
|
|
206
|
+
}
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Left arrow / backspace in segment drill-down — back to groups
|
|
211
|
+
if (this.section === "segments" && this.selectedGroupId && (data === "\x1b[D" || data === "\x7f")) {
|
|
141
212
|
this.backToGroups();
|
|
142
213
|
return;
|
|
143
214
|
}
|
|
@@ -147,12 +218,65 @@ class FooterSettingsOverlay {
|
|
|
147
218
|
}
|
|
148
219
|
|
|
149
220
|
private get currentList(): SettingsList | null {
|
|
150
|
-
|
|
151
|
-
|
|
221
|
+
switch (this.section) {
|
|
222
|
+
case "appearance": return this.appearanceList;
|
|
223
|
+
case "segments":
|
|
224
|
+
return this.segmentList ?? this.groupList;
|
|
225
|
+
case "labels": return this.labelsList;
|
|
226
|
+
}
|
|
152
227
|
}
|
|
153
228
|
|
|
154
229
|
// ─── Build SettingsList instances ──────────────────────────────────
|
|
155
230
|
|
|
231
|
+
private buildAppearanceList(): void {
|
|
232
|
+
const items: SettingItem[] = [
|
|
233
|
+
{
|
|
234
|
+
id: "preset",
|
|
235
|
+
label: "Preset",
|
|
236
|
+
description: "Footer layout preset",
|
|
237
|
+
currentValue: this.settings.preset,
|
|
238
|
+
values: PRESET_NAMES,
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: "separator",
|
|
242
|
+
label: "Separator",
|
|
243
|
+
description: "Segment divider style",
|
|
244
|
+
currentValue: this.settings.separator,
|
|
245
|
+
values: SEPARATOR_STYLES,
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
id: "iconStyle",
|
|
249
|
+
label: "Icon Style",
|
|
250
|
+
description: "Icon glyph set (nerd requires Nerd Font)",
|
|
251
|
+
currentValue: this.settings.iconStyle,
|
|
252
|
+
values: ICON_STYLES,
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
id: "zoneSeparator",
|
|
256
|
+
label: "Zone Separator",
|
|
257
|
+
description: "Divider between zones (left · center · right)",
|
|
258
|
+
currentValue: this.settings.zoneSeparator ?? "│",
|
|
259
|
+
values: ZONE_SEPARATOR_OPTIONS,
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
id: "showFullLabels",
|
|
263
|
+
label: "Full Labels",
|
|
264
|
+
description: "Show descriptive labels instead of abbreviations",
|
|
265
|
+
currentValue: this.settings.showFullLabels ? "on" : "off",
|
|
266
|
+
values: ["on", "off"],
|
|
267
|
+
},
|
|
268
|
+
];
|
|
269
|
+
|
|
270
|
+
this.appearanceList = new SettingsList(
|
|
271
|
+
items,
|
|
272
|
+
Math.min(items.length + 2, 12),
|
|
273
|
+
THEME,
|
|
274
|
+
(id, newValue) => this.onAppearanceChange(id, newValue),
|
|
275
|
+
() => this.onClose?.(),
|
|
276
|
+
{ enableSearch: false },
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
156
280
|
private buildGroupList(): void {
|
|
157
281
|
const items: SettingItem[] = this.groups.map((group) => {
|
|
158
282
|
const groupSettings = this.settings.groups[group.id] ?? { show: group.defaultShow, segments: {} };
|
|
@@ -166,7 +290,7 @@ class FooterSettingsOverlay {
|
|
|
166
290
|
return {
|
|
167
291
|
id: group.id,
|
|
168
292
|
label: group.name,
|
|
169
|
-
description: `${enabledCount}/${segCount} segments
|
|
293
|
+
description: `${enabledCount}/${segCount} segments · Enter to drill down`,
|
|
170
294
|
currentValue: isEnabled ? "on" : "off",
|
|
171
295
|
values: ["on", "off"],
|
|
172
296
|
};
|
|
@@ -195,8 +319,8 @@ class FooterSettingsOverlay {
|
|
|
195
319
|
const isEnabled = groupSettings.segments?.[seg.id] ?? seg.defaultShow;
|
|
196
320
|
return {
|
|
197
321
|
id: seg.id,
|
|
198
|
-
label: seg.label
|
|
199
|
-
description:
|
|
322
|
+
label: `${seg.shortLabel} ${seg.label}`,
|
|
323
|
+
description: seg.description,
|
|
200
324
|
currentValue: isEnabled ? "on" : "off",
|
|
201
325
|
values: ["on", "off"],
|
|
202
326
|
};
|
|
@@ -212,16 +336,69 @@ class FooterSettingsOverlay {
|
|
|
212
336
|
);
|
|
213
337
|
}
|
|
214
338
|
|
|
339
|
+
private buildLabelsList(): void {
|
|
340
|
+
const items: SettingItem[] = [
|
|
341
|
+
{
|
|
342
|
+
id: "showFullLabelsAlways",
|
|
343
|
+
label: "Full Labels",
|
|
344
|
+
description: "Always show descriptive labels instead of abbreviations",
|
|
345
|
+
currentValue: this.settings.showFullLabels ? "on" : "off",
|
|
346
|
+
values: ["on", "off"],
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
id: "showZoneHeaders",
|
|
350
|
+
label: "Zone Headers",
|
|
351
|
+
description: "Show zone labels (Identity / Metrics / Time)",
|
|
352
|
+
currentValue: "off", // TODO: implement zone headers
|
|
353
|
+
values: ["on", "off"],
|
|
354
|
+
},
|
|
355
|
+
];
|
|
356
|
+
|
|
357
|
+
this.labelsList = new SettingsList(
|
|
358
|
+
items,
|
|
359
|
+
Math.min(items.length + 2, 12),
|
|
360
|
+
THEME,
|
|
361
|
+
(id, newValue) => this.onLabelsChange(id, newValue),
|
|
362
|
+
() => this.onClose?.(),
|
|
363
|
+
{ enableSearch: false },
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
215
367
|
// ─── Change handlers ───────────────────────────────────────────────
|
|
216
368
|
|
|
369
|
+
private onAppearanceChange(id: string, newValue: string): void {
|
|
370
|
+
switch (id) {
|
|
371
|
+
case "preset":
|
|
372
|
+
this.settings.preset = newValue;
|
|
373
|
+
break;
|
|
374
|
+
case "separator":
|
|
375
|
+
this.settings.separator = newValue as SeparatorStyle;
|
|
376
|
+
break;
|
|
377
|
+
case "iconStyle":
|
|
378
|
+
this.settings.iconStyle = newValue as IconStyle;
|
|
379
|
+
setIconStyle(newValue as IconStyle);
|
|
380
|
+
break;
|
|
381
|
+
case "zoneSeparator":
|
|
382
|
+
this.settings.zoneSeparator = newValue;
|
|
383
|
+
break;
|
|
384
|
+
case "showFullLabels":
|
|
385
|
+
this.settings.showFullLabels = newValue === "on";
|
|
386
|
+
// Sync with labels section
|
|
387
|
+
this.labelsList.updateValue("showFullLabelsAlways", newValue);
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
saveFooterSettings(this.settings);
|
|
391
|
+
this.appearanceList.updateValue(id, newValue);
|
|
392
|
+
this.onSettingsChanged?.();
|
|
393
|
+
}
|
|
394
|
+
|
|
217
395
|
private onGroupChange(groupId: string, newValue: string): void {
|
|
218
396
|
const groupSettings = this.settings.groups[groupId] ?? { show: true, segments: {} };
|
|
219
397
|
groupSettings.show = newValue === "on";
|
|
220
398
|
this.settings.groups[groupId] = groupSettings;
|
|
221
399
|
saveFooterSettings(this.settings);
|
|
222
|
-
|
|
223
|
-
// Update display
|
|
224
400
|
this.groupList.updateValue(groupId, newValue);
|
|
401
|
+
this.onSettingsChanged?.();
|
|
225
402
|
}
|
|
226
403
|
|
|
227
404
|
private onSegmentChange(groupId: string, segmentId: string, newValue: string): void {
|
|
@@ -230,41 +407,51 @@ class FooterSettingsOverlay {
|
|
|
230
407
|
groupSettings.segments[segmentId] = newValue === "on";
|
|
231
408
|
this.settings.groups[groupId] = groupSettings;
|
|
232
409
|
saveFooterSettings(this.settings);
|
|
233
|
-
|
|
234
|
-
// Update the segment list display
|
|
235
410
|
this.segmentList?.updateValue(segmentId, newValue);
|
|
411
|
+
this.onSettingsChanged?.();
|
|
412
|
+
}
|
|
236
413
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
414
|
+
private onLabelsChange(id: string, newValue: string): void {
|
|
415
|
+
switch (id) {
|
|
416
|
+
case "showFullLabelsAlways":
|
|
417
|
+
this.settings.showFullLabels = newValue === "on";
|
|
418
|
+
// Sync with appearance section
|
|
419
|
+
this.appearanceList.updateValue("showFullLabels", newValue);
|
|
420
|
+
break;
|
|
421
|
+
case "showZoneHeaders":
|
|
422
|
+
// TODO: implement zone headers setting
|
|
423
|
+
break;
|
|
246
424
|
}
|
|
425
|
+
saveFooterSettings(this.settings);
|
|
426
|
+
this.labelsList.updateValue(id, newValue);
|
|
427
|
+
this.onSettingsChanged?.();
|
|
247
428
|
}
|
|
248
429
|
|
|
249
430
|
// ─── Section navigation ────────────────────────────────────────────
|
|
250
431
|
|
|
251
432
|
private getFocusedGroupId(): string | null {
|
|
252
|
-
|
|
253
|
-
//
|
|
254
|
-
|
|
255
|
-
|
|
433
|
+
if (this.selectedGroupId) return this.selectedGroupId;
|
|
434
|
+
// Access SettingsList internal state — it doesn't expose a getSelectedId() method
|
|
435
|
+
const list = this.groupList as unknown as {
|
|
436
|
+
selectedIndex: number;
|
|
437
|
+
items: SettingItem[];
|
|
438
|
+
filteredItems: SettingItem[];
|
|
439
|
+
searchEnabled: boolean;
|
|
440
|
+
};
|
|
441
|
+
const displayItems = list.searchEnabled ? list.filteredItems : list.items;
|
|
442
|
+
return displayItems[list.selectedIndex]?.id ?? null;
|
|
256
443
|
}
|
|
257
444
|
|
|
258
445
|
private enterSegmentsMode(groupId: string): void {
|
|
259
446
|
this.selectedGroupId = groupId;
|
|
260
|
-
this.section = "segments";
|
|
261
447
|
this.buildSegmentList(groupId);
|
|
262
448
|
}
|
|
263
449
|
|
|
264
450
|
private backToGroups(): void {
|
|
265
|
-
this.section = "groups";
|
|
266
451
|
this.selectedGroupId = null;
|
|
267
452
|
this.segmentList = null;
|
|
453
|
+
// Rebuild group list to reflect segment changes
|
|
454
|
+
this.buildGroupList();
|
|
268
455
|
}
|
|
269
456
|
|
|
270
457
|
// ─── Render ────────────────────────────────────────────────────────
|
|
@@ -279,19 +466,16 @@ class FooterSettingsOverlay {
|
|
|
279
466
|
|
|
280
467
|
// Section tabs
|
|
281
468
|
const tabParts = SECTIONS.map((s) => {
|
|
282
|
-
const label = s
|
|
469
|
+
const label = SECTION_LABELS[s];
|
|
283
470
|
if (s === this.section) {
|
|
284
471
|
return `\x1b[1m\x1b[36m[${label}]\x1b[0m`;
|
|
285
472
|
}
|
|
286
|
-
if (s === "segments" && !this.selectedGroupId) {
|
|
287
|
-
return `\x1b[2m${label}\x1b[0m`; // dimmed if no group selected
|
|
288
|
-
}
|
|
289
473
|
return `\x1b[2m${label}\x1b[0m`;
|
|
290
474
|
});
|
|
291
475
|
lines.push(frameLine(` ${tabParts.join(" ")}`, innerWidth));
|
|
292
476
|
lines.push(ruleLine(innerWidth));
|
|
293
477
|
|
|
294
|
-
// Section content
|
|
478
|
+
// Section content
|
|
295
479
|
const activeList = this.currentList;
|
|
296
480
|
if (activeList) {
|
|
297
481
|
const contentLines = activeList.render(innerWidth - 2);
|
|
@@ -302,9 +486,15 @@ class FooterSettingsOverlay {
|
|
|
302
486
|
|
|
303
487
|
// Footer hints
|
|
304
488
|
lines.push(ruleLine(innerWidth));
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
489
|
+
|
|
490
|
+
let hints: string;
|
|
491
|
+
if (this.section === "segments" && this.selectedGroupId) {
|
|
492
|
+
hints = "j/k navigate · Space toggle · h/Esc back · / search · q close";
|
|
493
|
+
} else if (this.section === "segments") {
|
|
494
|
+
hints = "j/k navigate · Space toggle · l/Enter segments · / search · q close";
|
|
495
|
+
} else {
|
|
496
|
+
hints = "j/k navigate · Space/Enter change · Tab section · q close";
|
|
497
|
+
}
|
|
308
498
|
lines.push(frameLine(`\x1b[2m${hints}\x1b[0m`, innerWidth));
|
|
309
499
|
lines.push(borderLine(innerWidth, "bottom"));
|
|
310
500
|
|
package/src/types.ts
CHANGED
|
@@ -9,39 +9,64 @@ import type { Theme, ThemeColor } from "@mariozechner/pi-coding-agent";
|
|
|
9
9
|
|
|
10
10
|
// ─── Semantic Colors ────────────────────────────────────────────────────────
|
|
11
11
|
|
|
12
|
+
/** Zone assignment for segment positioning */
|
|
13
|
+
export type SegmentZone = "left" | "center" | "right";
|
|
14
|
+
|
|
12
15
|
/** Semantic color names mapped to segment groups */
|
|
13
16
|
export type SemanticColor =
|
|
17
|
+
// ── Model & Identity (Left zone) ──
|
|
14
18
|
| "model"
|
|
15
19
|
| "path"
|
|
16
20
|
| "git"
|
|
17
|
-
| "
|
|
18
|
-
| "
|
|
19
|
-
| "
|
|
20
|
-
| "
|
|
21
|
-
|
|
22
|
-
| "ralphOff"
|
|
21
|
+
| "gitClean"
|
|
22
|
+
| "gitDirty"
|
|
23
|
+
| "session"
|
|
24
|
+
| "worktree"
|
|
25
|
+
// ── Workflow (Left zone) ──
|
|
23
26
|
| "workflow"
|
|
27
|
+
| "workflowNone"
|
|
24
28
|
| "workflowBrainstorm"
|
|
25
29
|
| "workflowPlan"
|
|
26
30
|
| "workflowWork"
|
|
27
31
|
| "workflowReview"
|
|
28
32
|
| "workflowAuto"
|
|
33
|
+
| "workflowDebug"
|
|
34
|
+
| "workflowChoreExec"
|
|
29
35
|
| "workflowOther"
|
|
36
|
+
// ── TPS tiers (Center zone) ──
|
|
37
|
+
| "tpsSlow"
|
|
38
|
+
| "tpsModerate"
|
|
39
|
+
| "tpsGood"
|
|
40
|
+
| "tpsFast"
|
|
41
|
+
| "tpsBlazing"
|
|
42
|
+
| "tpsIdle"
|
|
43
|
+
// ── Metrics (Center zone) ──
|
|
44
|
+
| "compactor"
|
|
45
|
+
| "memory"
|
|
46
|
+
| "mcp"
|
|
47
|
+
| "ralph"
|
|
48
|
+
| "ralphOn"
|
|
49
|
+
| "ralphOff"
|
|
30
50
|
| "kanboard"
|
|
31
51
|
| "notify"
|
|
32
|
-
| "separator"
|
|
33
|
-
| "border"
|
|
34
52
|
| "context"
|
|
35
53
|
| "contextWarn"
|
|
36
54
|
| "contextError"
|
|
37
55
|
| "cost"
|
|
38
56
|
| "tokens"
|
|
57
|
+
// ── Time (Right zone) ──
|
|
58
|
+
| "clock"
|
|
59
|
+
| "duration"
|
|
60
|
+
// ── Thinking levels ──
|
|
39
61
|
| "thinking"
|
|
40
62
|
| "thinkingMinimal"
|
|
41
63
|
| "thinkingLow"
|
|
42
64
|
| "thinkingMedium"
|
|
43
65
|
| "thinkingHigh"
|
|
44
|
-
| "thinkingXhigh"
|
|
66
|
+
| "thinkingXhigh"
|
|
67
|
+
// ── UI chrome ──
|
|
68
|
+
| "separator"
|
|
69
|
+
| "border";
|
|
45
70
|
|
|
46
71
|
/** A theme color name or custom hex color */
|
|
47
72
|
export type ColorValue = ThemeColor | `#${string}`;
|
|
@@ -98,6 +123,8 @@ export interface FooterSegmentContext {
|
|
|
98
123
|
piContext?: unknown;
|
|
99
124
|
/** Footer data provider (for core segments that need git, extension statuses) */
|
|
100
125
|
footerData?: unknown;
|
|
126
|
+
/** Label mode: compact (shortLabel) or labeled (full label) */
|
|
127
|
+
labelMode?: "compact" | "labeled";
|
|
101
128
|
}
|
|
102
129
|
|
|
103
130
|
/** Segment render function type */
|
|
@@ -107,8 +134,14 @@ export type SegmentRenderFn = (ctx: FooterSegmentContext) => RenderedSegment;
|
|
|
107
134
|
export interface FooterSegment {
|
|
108
135
|
/** Unique segment identifier (e.g., "model", "compactions") */
|
|
109
136
|
id: string;
|
|
110
|
-
/** Display label */
|
|
137
|
+
/** Display label (full name, used in labeled mode) */
|
|
111
138
|
label: string;
|
|
139
|
+
/** Compact display name (used in compact mode, e.g. "ses", "tps", "ctx") */
|
|
140
|
+
shortLabel: string;
|
|
141
|
+
/** Human-readable description (shown in footer-help overlay) */
|
|
142
|
+
description: string;
|
|
143
|
+
/** Layout zone assignment */
|
|
144
|
+
zone: SegmentZone;
|
|
112
145
|
/** Icon glyph (Nerd Font or ASCII) */
|
|
113
146
|
icon: string;
|
|
114
147
|
/** Render function */
|
|
@@ -153,6 +186,10 @@ export interface FooterSettings {
|
|
|
153
186
|
separator: SeparatorStyle;
|
|
154
187
|
/** Icon style: nerd (Nerd Font glyphs), emoji (Unicode emoji), text (plain labels) */
|
|
155
188
|
iconStyle: IconStyle;
|
|
189
|
+
/** Zone separator string (between zones, default: "│") */
|
|
190
|
+
zoneSeparator?: string;
|
|
191
|
+
/** Show full labels instead of compact short labels */
|
|
192
|
+
showFullLabels?: boolean;
|
|
156
193
|
/** Per-group settings */
|
|
157
194
|
groups: Record<string, FooterGroupSettings>;
|
|
158
195
|
}
|
|
@@ -174,10 +211,12 @@ export interface PresetDef {
|
|
|
174
211
|
rightSegments: string[];
|
|
175
212
|
/** Secondary row segments (shown when terminal is narrow) */
|
|
176
213
|
secondarySegments: string[];
|
|
177
|
-
/** Separator style for this preset */
|
|
178
|
-
separator: SeparatorStyle;
|
|
179
214
|
/** Color scheme for this preset */
|
|
180
215
|
colors?: ColorScheme;
|
|
181
216
|
/** Per-segment options */
|
|
182
217
|
segmentOptions?: Record<string, Record<string, unknown>>;
|
|
218
|
+
/** Zone order (default: left, center, right) */
|
|
219
|
+
zoneOrder?: ("left" | "center" | "right")[];
|
|
220
|
+
/** Zone separator string (between zones) */
|
|
221
|
+
zoneSeparator?: string;
|
|
183
222
|
}
|