pi-zentui 0.3.1 → 0.3.3
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/LICENSE +1 -1
- package/README.md +3 -2
- package/extensions/zentui/config.ts +21 -4
- package/extensions/zentui/footer.ts +41 -11
- package/extensions/zentui/format.ts +10 -0
- package/extensions/zentui/git.ts +7 -6
- package/extensions/zentui/index.ts +26 -0
- package/extensions/zentui/settings-command.ts +7 -0
- package/extensions/zentui/state.ts +2 -0
- package/extensions/zentui/ui.ts +1 -1
- package/extensions/zentui/user-message.ts +4 -2
- package/package.json +1 -1
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -163,7 +163,8 @@ Default config values — copy this and change any value you want:
|
|
|
163
163
|
"deleted": "✘",
|
|
164
164
|
"typechanged": "T",
|
|
165
165
|
"cacheHit": "",
|
|
166
|
-
"editorPrompt": ""
|
|
166
|
+
"editorPrompt": "",
|
|
167
|
+
"rail": "│"
|
|
167
168
|
},
|
|
168
169
|
"colors": {
|
|
169
170
|
"cwd": "bold cyan",
|
|
@@ -218,7 +219,7 @@ Default config values — copy this and change any value you want:
|
|
|
218
219
|
|
|
219
220
|
- Style values can be Starship/terminal strings (`bold purple`, `fg:202`, `#89b4fa`, `bg:blue fg:bright-green`) or Pi theme tokens (`accent`, `borderMuted`, `thinkingHigh`).
|
|
220
221
|
- `projectRefreshIntervalMs`: project status polling interval; `0` disables polling.
|
|
221
|
-
- `icons`: every shown icon key is configurable; omit any key to use the Zentui default. `editorPrompt` controls an optional copy-friendly editor prompt glyph; the default is `""` so copy-friendly mode stays rail-free.
|
|
222
|
+
- `icons`: every shown icon key is configurable; omit any key to use the Zentui default. `rail` sets the vertical glyph drawn as the left rail of the active editor frame and previous user messages when `copyFriendly` is disabled (default `│`; any single Unicode vertical or block glyph). `editorPrompt` controls an optional copy-friendly editor prompt glyph; the default is `""` so copy-friendly mode stays rail-free.
|
|
222
223
|
- `colorSources`: `theme` maps styles through Pi theme tokens; `terminal` emits terminal colors. `/zentui` switches these sources; manual JSON controls specific style values.
|
|
223
224
|
- `features`: `editor` enables Zentui's custom editor, selector borders, and previous-message chrome. `statusLine` enables Zentui's custom footer/status line. `copyFriendly` hides editor and previous-message rail glyphs so native terminal selection copies less chrome. All three can be changed from `/zentui` or direct slash-command arguments.
|
|
224
225
|
- `footerSegments`: show or hide individual built-in footer segments (`cwd`, `gitBranch`, `gitStatus`, `runtime`, `context`, `tokens`, `cost`). Toggle them from the `Built-in segments` tab in `/zentui`.
|
|
@@ -22,10 +22,12 @@ export type FooterSegmentsConfig = {
|
|
|
22
22
|
cwd: boolean;
|
|
23
23
|
gitBranch: boolean;
|
|
24
24
|
gitStatus: boolean;
|
|
25
|
+
gitCounts: boolean;
|
|
25
26
|
runtime: boolean;
|
|
26
27
|
context: boolean;
|
|
27
28
|
tokens: boolean;
|
|
28
29
|
cost: boolean;
|
|
30
|
+
sessionDuration: boolean;
|
|
29
31
|
};
|
|
30
32
|
|
|
31
33
|
export type ExtensionStatusPlacement = "off" | "left" | "middle" | "right";
|
|
@@ -60,6 +62,7 @@ export type PolishedTuiConfig = {
|
|
|
60
62
|
typechanged: string;
|
|
61
63
|
cacheHit: string;
|
|
62
64
|
editorPrompt: string;
|
|
65
|
+
rail: string;
|
|
63
66
|
};
|
|
64
67
|
colors: {
|
|
65
68
|
cwd: ColorSpec;
|
|
@@ -73,6 +76,7 @@ export type PolishedTuiConfig = {
|
|
|
73
76
|
separator: ColorSpec;
|
|
74
77
|
runtimePrefix: ColorSpec;
|
|
75
78
|
extensionStatus: ColorSpec;
|
|
79
|
+
sessionDuration: ColorSpec;
|
|
76
80
|
editorAccent?: ColorSpec;
|
|
77
81
|
editorPrompt?: ColorSpec;
|
|
78
82
|
editorBorder?: ColorSpec;
|
|
@@ -111,6 +115,7 @@ export const defaultConfig: PolishedTuiConfig = {
|
|
|
111
115
|
typechanged: "T",
|
|
112
116
|
cacheHit: "",
|
|
113
117
|
editorPrompt: "",
|
|
118
|
+
rail: "│",
|
|
114
119
|
},
|
|
115
120
|
colors: {
|
|
116
121
|
cwd: "bold cyan",
|
|
@@ -124,6 +129,7 @@ export const defaultConfig: PolishedTuiConfig = {
|
|
|
124
129
|
separator: "bright-black",
|
|
125
130
|
runtimePrefix: "",
|
|
126
131
|
extensionStatus: "bright-black",
|
|
132
|
+
sessionDuration: "yellow",
|
|
127
133
|
},
|
|
128
134
|
colorSources: {
|
|
129
135
|
starship: "theme",
|
|
@@ -139,10 +145,12 @@ export const defaultConfig: PolishedTuiConfig = {
|
|
|
139
145
|
cwd: true,
|
|
140
146
|
gitBranch: true,
|
|
141
147
|
gitStatus: true,
|
|
148
|
+
gitCounts: false,
|
|
142
149
|
runtime: true,
|
|
143
150
|
context: true,
|
|
144
151
|
tokens: true,
|
|
145
152
|
cost: true,
|
|
153
|
+
sessionDuration: false,
|
|
146
154
|
},
|
|
147
155
|
extensionStatuses: {
|
|
148
156
|
defaultPlacement: "right",
|
|
@@ -187,6 +195,10 @@ function parseProjectRefreshIntervalMs(value: unknown): number {
|
|
|
187
195
|
: defaultConfig.projectRefreshIntervalMs;
|
|
188
196
|
}
|
|
189
197
|
|
|
198
|
+
function railValue(value: unknown): string {
|
|
199
|
+
return typeof value === "string" && value.trim().length > 0 ? value : defaultConfig.icons.rail;
|
|
200
|
+
}
|
|
201
|
+
|
|
190
202
|
function stringValue(record: Record<string, unknown>, key: string): string | undefined {
|
|
191
203
|
const value = record[key];
|
|
192
204
|
return typeof value === "string" ? value : undefined;
|
|
@@ -250,6 +262,7 @@ function normalizeColors(record: Record<string, unknown>): Partial<PolishedTuiCo
|
|
|
250
262
|
separator: colorValue(record, "separator"),
|
|
251
263
|
runtimePrefix: colorValue(record, "runtimePrefix"),
|
|
252
264
|
extensionStatus: colorValue(record, "extensionStatus"),
|
|
265
|
+
sessionDuration: colorValue(record, "sessionDuration"),
|
|
253
266
|
editorAccent: colorValue(record, "editorAccent"),
|
|
254
267
|
editorPrompt: colorValue(record, "editorPrompt"),
|
|
255
268
|
editorBorder: colorValue(record, "editorBorder"),
|
|
@@ -285,10 +298,12 @@ function normalizeFooterSegments(record: Record<string, unknown>): FooterSegment
|
|
|
285
298
|
cwd: footerSegmentValue(record, "cwd"),
|
|
286
299
|
gitBranch: footerSegmentValue(record, "gitBranch"),
|
|
287
300
|
gitStatus: footerSegmentValue(record, "gitStatus"),
|
|
301
|
+
gitCounts: footerSegmentValue(record, "gitCounts"),
|
|
288
302
|
runtime: footerSegmentValue(record, "runtime"),
|
|
289
303
|
context: footerSegmentValue(record, "context"),
|
|
290
304
|
tokens: footerSegmentValue(record, "tokens"),
|
|
291
305
|
cost: footerSegmentValue(record, "cost"),
|
|
306
|
+
sessionDuration: footerSegmentValue(record, "sessionDuration"),
|
|
292
307
|
};
|
|
293
308
|
}
|
|
294
309
|
|
|
@@ -341,10 +356,12 @@ function isFooterSegmentKey(value: string): value is keyof FooterSegmentsConfig
|
|
|
341
356
|
value === "cwd" ||
|
|
342
357
|
value === "gitBranch" ||
|
|
343
358
|
value === "gitStatus" ||
|
|
359
|
+
value === "gitCounts" ||
|
|
344
360
|
value === "runtime" ||
|
|
345
361
|
value === "context" ||
|
|
346
362
|
value === "tokens" ||
|
|
347
|
-
value === "cost"
|
|
363
|
+
value === "cost" ||
|
|
364
|
+
value === "sessionDuration"
|
|
348
365
|
);
|
|
349
366
|
}
|
|
350
367
|
|
|
@@ -394,9 +411,8 @@ export function ensureConfigExists(): void {
|
|
|
394
411
|
|
|
395
412
|
export function mergeConfig(parsed: unknown): PolishedTuiConfig {
|
|
396
413
|
const config = isRecord(parsed) ? parsed : {};
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
: {};
|
|
414
|
+
const iconsRecord = isRecord(config.icons) ? (config.icons as Record<string, unknown>) : {};
|
|
415
|
+
const icons = normalizeIcons(iconsRecord);
|
|
400
416
|
const colors = isRecord(config.colors)
|
|
401
417
|
? normalizeColors(config.colors as Record<string, unknown>)
|
|
402
418
|
: {};
|
|
@@ -417,6 +433,7 @@ export function mergeConfig(parsed: unknown): PolishedTuiConfig {
|
|
|
417
433
|
icons: {
|
|
418
434
|
...defaultConfig.icons,
|
|
419
435
|
...icons,
|
|
436
|
+
rail: railValue(iconsRecord.rail),
|
|
420
437
|
},
|
|
421
438
|
colors: {
|
|
422
439
|
...defaultConfig.colors,
|
|
@@ -2,7 +2,7 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
3
|
import type { PolishedTuiConfig } from "./config";
|
|
4
4
|
import { collectExtensionStatusSegments, type ExtensionStatusSegment } from "./extension-status";
|
|
5
|
-
import { formatCwdLabel, formatRuntimeSegment } from "./format";
|
|
5
|
+
import { buildSessionDurationLabel, formatCwdLabel, formatRuntimeSegment } from "./format";
|
|
6
6
|
import type { FooterState } from "./state";
|
|
7
7
|
import { renderStyleForSource } from "./style";
|
|
8
8
|
|
|
@@ -176,9 +176,16 @@ export function installFooter(
|
|
|
176
176
|
const gitStatusColor = (text: string) =>
|
|
177
177
|
renderStyleForSource(theme, colorSource, config.colors.gitStatus, text);
|
|
178
178
|
const gitIcon = config.icons.git ? gitColor(config.icons.git) : "";
|
|
179
|
+
const gitCounts = config.footerSegments.gitCounts;
|
|
180
|
+
const stashLabel =
|
|
181
|
+
state.stashed > 0
|
|
182
|
+
? gitCounts
|
|
183
|
+
? `${config.icons.stashed}${state.stashed}`
|
|
184
|
+
: config.icons.stashed
|
|
185
|
+
: "";
|
|
179
186
|
const allStatus = [
|
|
180
187
|
state.conflicted > 0 ? config.icons.conflicted : "",
|
|
181
|
-
|
|
188
|
+
stashLabel,
|
|
182
189
|
state.deleted > 0 ? config.icons.deleted : "",
|
|
183
190
|
state.renamed > 0 ? config.icons.renamed : "",
|
|
184
191
|
state.modified > 0 ? config.icons.modified : "",
|
|
@@ -186,14 +193,18 @@ export function installFooter(
|
|
|
186
193
|
state.staged > 0 ? config.icons.staged : "",
|
|
187
194
|
state.untracked > 0 ? config.icons.untracked : "",
|
|
188
195
|
].join("");
|
|
189
|
-
const aheadBehind =
|
|
190
|
-
state.ahead > 0 && state.behind > 0
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
196
|
+
const aheadBehind = (() => {
|
|
197
|
+
if (state.ahead > 0 && state.behind > 0) {
|
|
198
|
+
return gitCounts
|
|
199
|
+
? `${config.icons.ahead}${state.ahead}${config.icons.behind}${state.behind}`
|
|
200
|
+
: config.icons.diverged;
|
|
201
|
+
}
|
|
202
|
+
if (state.ahead > 0)
|
|
203
|
+
return gitCounts ? `${config.icons.ahead}${state.ahead}` : config.icons.ahead;
|
|
204
|
+
if (state.behind > 0)
|
|
205
|
+
return gitCounts ? `${config.icons.behind}${state.behind}` : config.icons.behind;
|
|
206
|
+
return "";
|
|
207
|
+
})();
|
|
197
208
|
const statusBlock =
|
|
198
209
|
allStatus || aheadBehind ? gitStatusColor(`[${allStatus}${aheadBehind}]`) : "";
|
|
199
210
|
const branchParts =
|
|
@@ -206,9 +217,27 @@ export function installFooter(
|
|
|
206
217
|
? formatRuntimeSegment(theme, state.runtime, config.colors.runtimePrefix, colorSource)
|
|
207
218
|
: "";
|
|
208
219
|
|
|
209
|
-
const
|
|
220
|
+
const sessionDurationSegment = (() => {
|
|
221
|
+
if (!config.footerSegments.sessionDuration || !state.sessionStartEpoch) return "";
|
|
222
|
+
const timeLabel = buildSessionDurationLabel(state.sessionStartEpoch);
|
|
223
|
+
const prefix = renderStyleForSource(theme, colorSource, "", "up for");
|
|
224
|
+
const time = renderStyleForSource(
|
|
225
|
+
theme,
|
|
226
|
+
colorSource,
|
|
227
|
+
config.colors.sessionDuration,
|
|
228
|
+
timeLabel,
|
|
229
|
+
);
|
|
230
|
+
return `${prefix} ${time}`;
|
|
231
|
+
})();
|
|
232
|
+
const left = [
|
|
233
|
+
config.footerSegments.cwd ? cwdLabel : "",
|
|
234
|
+
branchLabel,
|
|
235
|
+
runtimeLabel,
|
|
236
|
+
sessionDurationSegment,
|
|
237
|
+
]
|
|
210
238
|
.filter(Boolean)
|
|
211
239
|
.join(" ");
|
|
240
|
+
|
|
212
241
|
const right = [
|
|
213
242
|
config.footerSegments.context
|
|
214
243
|
? renderStyleForSource(theme, colorSource, contextColor, state.contextLabel)
|
|
@@ -222,6 +251,7 @@ export function installFooter(
|
|
|
222
251
|
]
|
|
223
252
|
.filter(Boolean)
|
|
224
253
|
.join(separator);
|
|
254
|
+
|
|
225
255
|
const extensionStatuses = collectExtensionStatusSegments(
|
|
226
256
|
footerData.getExtensionStatuses(),
|
|
227
257
|
config,
|
|
@@ -91,6 +91,16 @@ export function buildCostLabel(totals: UsageTotals): string {
|
|
|
91
91
|
return `$${totals.cost.toFixed(3)}`;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
export function buildSessionDurationLabel(startEpoch: number): string {
|
|
95
|
+
const totalSeconds = Math.max(0, Math.floor((Date.now() - startEpoch) / 1000));
|
|
96
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
97
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
98
|
+
const seconds = totalSeconds % 60;
|
|
99
|
+
if (hours > 0) return `${hours}h ${minutes}m`;
|
|
100
|
+
if (minutes > 0) return `${minutes}m ${seconds}s`;
|
|
101
|
+
return `${seconds}s`;
|
|
102
|
+
}
|
|
103
|
+
|
|
94
104
|
export function buildContextLabel(ctx: ExtensionContext): string {
|
|
95
105
|
const usage = ctx.getContextUsage();
|
|
96
106
|
const contextWindow = ctx.model?.contextWindow ?? usage?.contextWindow;
|
package/extensions/zentui/git.ts
CHANGED
|
@@ -11,7 +11,7 @@ export type GitStatusSummary = {
|
|
|
11
11
|
behind: number;
|
|
12
12
|
conflicted: number;
|
|
13
13
|
untracked: number;
|
|
14
|
-
stashed:
|
|
14
|
+
stashed: number;
|
|
15
15
|
modified: number;
|
|
16
16
|
staged: number;
|
|
17
17
|
renamed: number;
|
|
@@ -27,7 +27,7 @@ export function emptyGitStatus(): GitStatusSummary {
|
|
|
27
27
|
behind: 0,
|
|
28
28
|
conflicted: 0,
|
|
29
29
|
untracked: 0,
|
|
30
|
-
stashed:
|
|
30
|
+
stashed: 0,
|
|
31
31
|
modified: 0,
|
|
32
32
|
staged: 0,
|
|
33
33
|
renamed: 0,
|
|
@@ -36,9 +36,9 @@ export function emptyGitStatus(): GitStatusSummary {
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
export function parseGitStatusPorcelain(stdoutText: string,
|
|
39
|
+
export function parseGitStatusPorcelain(stdoutText: string, stashCount: number): GitStatusSummary {
|
|
40
40
|
const status = emptyGitStatus();
|
|
41
|
-
status.stashed =
|
|
41
|
+
status.stashed = stashCount;
|
|
42
42
|
|
|
43
43
|
for (const line of stdoutText.split(/\r?\n/)) {
|
|
44
44
|
if (!line) continue;
|
|
@@ -93,7 +93,7 @@ export async function readGitStatus(cwd: string): Promise<GitStatusSummary> {
|
|
|
93
93
|
cwd,
|
|
94
94
|
timeout: GIT_COMMAND_TIMEOUT_MS,
|
|
95
95
|
}),
|
|
96
|
-
execFileAsync("git", ["
|
|
96
|
+
execFileAsync("git", ["stash", "list"], {
|
|
97
97
|
cwd,
|
|
98
98
|
timeout: GIT_COMMAND_TIMEOUT_MS,
|
|
99
99
|
}).catch(() => ({ stdout: "" })),
|
|
@@ -101,7 +101,8 @@ export async function readGitStatus(cwd: string): Promise<GitStatusSummary> {
|
|
|
101
101
|
const stdoutText = typeof statusStdout === "string" ? statusStdout : String(statusStdout);
|
|
102
102
|
const stashStdout =
|
|
103
103
|
typeof stashResult.stdout === "string" ? stashResult.stdout : String(stashResult.stdout);
|
|
104
|
-
|
|
104
|
+
const stashCount = stashStdout.split(/\r?\n/).filter((line) => line.trim().length > 0).length;
|
|
105
|
+
return parseGitStatusPorcelain(stdoutText, stashCount);
|
|
105
106
|
} catch {
|
|
106
107
|
return emptyGitStatus();
|
|
107
108
|
}
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
type UiFeaturesConfig,
|
|
22
22
|
} from "./config";
|
|
23
23
|
import { installFooter } from "./footer";
|
|
24
|
+
import { buildSessionDurationLabel } from "./format";
|
|
24
25
|
import { emptyGitStatus, readGitStatus } from "./git";
|
|
25
26
|
import {
|
|
26
27
|
createProjectRefreshScheduler,
|
|
@@ -83,6 +84,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
83
84
|
let installedEditorFactory: EditorFactory | undefined;
|
|
84
85
|
let wrappedEditorFactory: EditorFactory | undefined;
|
|
85
86
|
let prototypePatchesInstalled = false;
|
|
87
|
+
let stopSessionTimer: () => void = () => {};
|
|
88
|
+
let lastDurationLabel = "";
|
|
86
89
|
|
|
87
90
|
const refresh = () => requestFooterRender?.();
|
|
88
91
|
const getActiveTheme = () => activeTheme;
|
|
@@ -117,6 +120,25 @@ export default function (pi: ExtensionAPI) {
|
|
|
117
120
|
projectRefreshScheduler.stop();
|
|
118
121
|
};
|
|
119
122
|
|
|
123
|
+
const startSessionTimer = () => {
|
|
124
|
+
stopSessionTimer();
|
|
125
|
+
lastDurationLabel = "";
|
|
126
|
+
const timer = setInterval(() => {
|
|
127
|
+
if (!(currentConfig.features.statusLine && currentConfig.footerSegments.sessionDuration))
|
|
128
|
+
return;
|
|
129
|
+
const label = state.sessionStartEpoch
|
|
130
|
+
? buildSessionDurationLabel(state.sessionStartEpoch)
|
|
131
|
+
: "";
|
|
132
|
+
if (label === lastDurationLabel) return;
|
|
133
|
+
lastDurationLabel = label;
|
|
134
|
+
refresh();
|
|
135
|
+
}, 1000);
|
|
136
|
+
stopSessionTimer = () => {
|
|
137
|
+
clearInterval(timer);
|
|
138
|
+
stopSessionTimer = () => {};
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
|
|
120
142
|
const installPrototypePatches = () => {
|
|
121
143
|
if (prototypePatchesInstalled) return;
|
|
122
144
|
const cleanupSelectorBorderStyle = installSelectorBorderStyle(getActiveTheme, getCurrentConfig);
|
|
@@ -239,9 +261,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
239
261
|
);
|
|
240
262
|
scheduleProjectRefresh(ctx, { force: true });
|
|
241
263
|
refresh();
|
|
264
|
+
startSessionTimer();
|
|
242
265
|
};
|
|
243
266
|
|
|
244
267
|
const uninstallStatusLine = (ctx: ExtensionContext) => {
|
|
268
|
+
stopSessionTimer();
|
|
245
269
|
stopProjectRefresh();
|
|
246
270
|
ctx.ui.setFooter(undefined);
|
|
247
271
|
footerInstalled = false;
|
|
@@ -297,6 +321,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
297
321
|
|
|
298
322
|
const cleanupUi = (ctx?: ExtensionContext) => {
|
|
299
323
|
uninstallPrototypePatches();
|
|
324
|
+
stopSessionTimer();
|
|
300
325
|
stopProjectRefresh();
|
|
301
326
|
requestFooterRender = undefined;
|
|
302
327
|
getActiveExtensionStatuses = () => new Map();
|
|
@@ -328,6 +353,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
328
353
|
};
|
|
329
354
|
|
|
330
355
|
pi.on("session_start", async (_event, ctx) => {
|
|
356
|
+
state.sessionStartEpoch = Date.now();
|
|
331
357
|
installUi(ctx);
|
|
332
358
|
scheduleEditorReconciliation(ctx);
|
|
333
359
|
});
|
|
@@ -88,6 +88,8 @@ const footerSegmentSettingLabels: Record<FooterSegmentSettingId, string> = {
|
|
|
88
88
|
cwd: "Current directory",
|
|
89
89
|
gitBranch: "Git branch",
|
|
90
90
|
gitStatus: "Git status",
|
|
91
|
+
gitCounts: "Git counts",
|
|
92
|
+
sessionDuration: "Session duration",
|
|
91
93
|
runtime: "Runtime",
|
|
92
94
|
context: "Context usage",
|
|
93
95
|
tokens: "Token counts",
|
|
@@ -98,6 +100,9 @@ const footerSegmentSettingDescriptions: Record<FooterSegmentSettingId, string> =
|
|
|
98
100
|
cwd: "Show or hide the current working directory segment on the left.",
|
|
99
101
|
gitBranch: "Show or hide the git branch name on the left.",
|
|
100
102
|
gitStatus: "Show or hide git status icons and ahead/behind markers.",
|
|
103
|
+
gitCounts:
|
|
104
|
+
"Show numeric ahead/behind and stash counts (requires the Git status segment to be enabled).",
|
|
105
|
+
sessionDuration: "Show session running time on the left, after the runtime.",
|
|
101
106
|
runtime: "Show or hide the detected runtime/language segment on the left.",
|
|
102
107
|
context: "Show or hide context usage on the right.",
|
|
103
108
|
tokens: "Show or hide input/output token counts on the right.",
|
|
@@ -144,6 +149,8 @@ function isFooterSegmentSettingId(value: string): value is FooterSegmentSettingI
|
|
|
144
149
|
value === "cwd" ||
|
|
145
150
|
value === "gitBranch" ||
|
|
146
151
|
value === "gitStatus" ||
|
|
152
|
+
value === "gitCounts" ||
|
|
153
|
+
value === "sessionDuration" ||
|
|
147
154
|
value === "runtime" ||
|
|
148
155
|
value === "context" ||
|
|
149
156
|
value === "tokens" ||
|
|
@@ -16,6 +16,7 @@ export type FooterState = GitStatusSummary & {
|
|
|
16
16
|
tokenLabel: string;
|
|
17
17
|
costLabel: string;
|
|
18
18
|
runtime?: RuntimeInfo;
|
|
19
|
+
sessionStartEpoch?: number;
|
|
19
20
|
};
|
|
20
21
|
|
|
21
22
|
export function createInitialState(gitDefaults: GitStatusSummary): FooterState {
|
|
@@ -26,6 +27,7 @@ export function createInitialState(gitDefaults: GitStatusSummary): FooterState {
|
|
|
26
27
|
tokenLabel: "↑0 ↓0",
|
|
27
28
|
costLabel: "$0.000",
|
|
28
29
|
runtime: undefined,
|
|
30
|
+
sessionStartEpoch: Date.now(),
|
|
29
31
|
...gitDefaults,
|
|
30
32
|
};
|
|
31
33
|
}
|
package/extensions/zentui/ui.ts
CHANGED
|
@@ -87,6 +87,7 @@ function getUserMessageConfigKey(config: PolishedTuiConfig): string {
|
|
|
87
87
|
config.colorSources.userMessages,
|
|
88
88
|
config.colors.editorAccent ?? "",
|
|
89
89
|
config.colors.editorBorder ?? "",
|
|
90
|
+
config.icons.rail,
|
|
90
91
|
].join("\0");
|
|
91
92
|
}
|
|
92
93
|
|
|
@@ -126,6 +127,7 @@ function fillLine(content: string, width: number): string {
|
|
|
126
127
|
|
|
127
128
|
function renderPromptBoxRail(theme: Theme | undefined, config: PolishedTuiConfig): string {
|
|
128
129
|
if (config.features.copyFriendly) return "";
|
|
130
|
+
const railGlyph = config.icons.rail;
|
|
129
131
|
|
|
130
132
|
return `${
|
|
131
133
|
theme
|
|
@@ -134,9 +136,9 @@ function renderPromptBoxRail(theme: Theme | undefined, config: PolishedTuiConfig
|
|
|
134
136
|
config.colorSources.userMessages,
|
|
135
137
|
config.colors.editorAccent,
|
|
136
138
|
EDITOR_ACCENT_FALLBACK,
|
|
137
|
-
|
|
139
|
+
railGlyph,
|
|
138
140
|
)
|
|
139
|
-
:
|
|
141
|
+
: railGlyph
|
|
140
142
|
} `;
|
|
141
143
|
}
|
|
142
144
|
|