@gajae-code/coding-agent 0.7.7 → 0.7.8
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 +20 -1
- package/README.md +8 -0
- package/dist/types/cli/local-provider-smoke.d.ts +59 -0
- package/dist/types/cli/notify-cli.d.ts +1 -1
- package/dist/types/cli/update-cli.d.ts +24 -1
- package/dist/types/commands/local-provider.d.ts +34 -0
- package/dist/types/config/model-registry.d.ts +5 -0
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +45 -0
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/coordinator-mcp/server.d.ts +8 -0
- package/dist/types/gjc-runtime/launch-tmux.d.ts +2 -2
- package/dist/types/gjc-runtime/team-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/tmux-common.d.ts +12 -0
- package/dist/types/modes/components/custom-model-preset-wizard.d.ts +1 -1
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/components/settings-selector.d.ts +4 -1
- package/dist/types/modes/components/status-line/types.d.ts +5 -7
- package/dist/types/modes/components/status-line.d.ts +4 -3
- package/dist/types/modes/controllers/input-controller.d.ts +1 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -0
- package/dist/types/modes/tmux-scroll.d.ts +18 -0
- package/dist/types/modes/utils/terminal-bell.d.ts +3 -0
- package/dist/types/notifications/telegram-daemon-cli.d.ts +13 -2
- package/package.json +7 -7
- package/src/cli/local-provider-smoke.ts +632 -0
- package/src/cli/notify-cli.ts +4 -2
- package/src/cli/update-cli.ts +155 -13
- package/src/cli.ts +18 -0
- package/src/commands/launch.ts +46 -1
- package/src/commands/local-provider.ts +62 -0
- package/src/commands/notify.ts +1 -1
- package/src/config/model-registry.ts +55 -1
- package/src/config/models-config-schema.ts +9 -0
- package/src/config/settings-schema.ts +47 -0
- package/src/config/settings.ts +11 -0
- package/src/coordinator-mcp/server.ts +276 -34
- package/src/gjc-runtime/launch-tmux.ts +34 -8
- package/src/gjc-runtime/launch-worktree.ts +16 -2
- package/src/gjc-runtime/team-runtime.ts +21 -6
- package/src/gjc-runtime/tmux-common.ts +16 -0
- package/src/gjc-runtime/tmux-sessions.ts +13 -9
- package/src/internal-urls/docs-index.generated.ts +5 -4
- package/src/modes/components/agent-dashboard.ts +2 -1
- package/src/modes/components/custom-model-preset-wizard.ts +30 -189
- package/src/modes/components/model-selector.ts +182 -5
- package/src/modes/components/session-observer-overlay.ts +3 -3
- package/src/modes/components/settings-selector.ts +491 -5
- package/src/modes/components/status-line/segments.ts +7 -18
- package/src/modes/components/status-line/types.ts +6 -2
- package/src/modes/components/status-line.ts +69 -35
- package/src/modes/controllers/event-controller.ts +98 -4
- package/src/modes/controllers/extension-ui-controller.ts +3 -0
- package/src/modes/controllers/input-controller.ts +15 -0
- package/src/modes/controllers/selector-controller.ts +12 -4
- package/src/modes/prompt-action-autocomplete.ts +8 -0
- package/src/modes/tmux-scroll.ts +92 -0
- package/src/modes/utils/terminal-bell.ts +48 -0
- package/src/notifications/telegram-daemon-cli.ts +112 -9
- package/src/notifications/telegram-daemon.ts +4 -0
- package/src/session/agent-session.ts +74 -5
- package/src/slash-commands/builtin-registry.ts +106 -13
|
@@ -27,7 +27,9 @@ import { getTabBarTheme } from "../shared";
|
|
|
27
27
|
import { DynamicBorder } from "./dynamic-border";
|
|
28
28
|
import { handleInputOrEscape, PluginSettingsComponent } from "./plugin-settings";
|
|
29
29
|
import { getSettingsForTab, type SettingDef } from "./settings-defs";
|
|
30
|
+
import type { StatusLineSegmentOptions } from "./status-line";
|
|
30
31
|
import { getPreset } from "./status-line/presets";
|
|
32
|
+
import { ALL_SEGMENT_IDS } from "./status-line/segments";
|
|
31
33
|
|
|
32
34
|
/**
|
|
33
35
|
* A submenu component for selecting from a list of options.
|
|
@@ -161,6 +163,449 @@ class SelectSubmenu extends Container {
|
|
|
161
163
|
this.#selectList.handleInput(data);
|
|
162
164
|
}
|
|
163
165
|
}
|
|
166
|
+
const STATUS_LINE_CUSTOM_EDITOR_ID = "statusLine.customEditor";
|
|
167
|
+
const PUBLIC_STATUS_SEGMENTS = ALL_SEGMENT_IDS.filter(id => id !== "pi");
|
|
168
|
+
|
|
169
|
+
type StatusLineDraft = Required<
|
|
170
|
+
Pick<StatusLinePreviewSettings, "preset" | "leftSegments" | "rightSegments" | "separator" | "segmentOptions">
|
|
171
|
+
>;
|
|
172
|
+
|
|
173
|
+
const BOOL_VALUES = ["true", "false"];
|
|
174
|
+
const PATH_LENGTH_OPTIONS: SelectItem[] = [16, 24, 32, 40, 50, 60, 80].map(value => ({
|
|
175
|
+
value: String(value),
|
|
176
|
+
label: String(value),
|
|
177
|
+
}));
|
|
178
|
+
const TIME_FORMAT_OPTIONS: SelectItem[] = [
|
|
179
|
+
{ value: "24h", label: "24h" },
|
|
180
|
+
{ value: "12h", label: "12h" },
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
function cloneSegmentOptions(options: StatusLineSegmentOptions | undefined): StatusLineSegmentOptions {
|
|
184
|
+
return mergeSegmentOptions(undefined, options);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function mergeSegmentOptions(
|
|
188
|
+
base: StatusLineSegmentOptions | undefined,
|
|
189
|
+
overrides: StatusLineSegmentOptions | undefined,
|
|
190
|
+
): StatusLineSegmentOptions {
|
|
191
|
+
return {
|
|
192
|
+
model: base?.model || overrides?.model ? { ...(base?.model ?? {}), ...(overrides?.model ?? {}) } : undefined,
|
|
193
|
+
path: base?.path || overrides?.path ? { ...(base?.path ?? {}), ...(overrides?.path ?? {}) } : undefined,
|
|
194
|
+
git: base?.git || overrides?.git ? { ...(base?.git ?? {}), ...(overrides?.git ?? {}) } : undefined,
|
|
195
|
+
time: base?.time || overrides?.time ? { ...(base?.time ?? {}), ...(overrides?.time ?? {}) } : undefined,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function effectiveSegmentOptions(
|
|
200
|
+
preset: StatusLinePreset,
|
|
201
|
+
options: StatusLineSegmentOptions | undefined,
|
|
202
|
+
): StatusLineSegmentOptions {
|
|
203
|
+
return mergeSegmentOptions(getPreset(preset).segmentOptions, options);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function effectiveCustomSegments(
|
|
207
|
+
preset: StatusLinePreset,
|
|
208
|
+
leftSegments: StatusLineSegmentId[],
|
|
209
|
+
rightSegments: StatusLineSegmentId[],
|
|
210
|
+
): { leftSegments: StatusLineSegmentId[]; rightSegments: StatusLineSegmentId[] } {
|
|
211
|
+
if (preset === "custom") {
|
|
212
|
+
return { leftSegments: [...leftSegments], rightSegments: [...rightSegments] };
|
|
213
|
+
}
|
|
214
|
+
const presetDef = getPreset(preset);
|
|
215
|
+
return {
|
|
216
|
+
leftSegments: [...presetDef.leftSegments],
|
|
217
|
+
rightSegments: [...presetDef.rightSegments],
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function statusSegmentLabel(id: StatusLineSegmentId): string {
|
|
222
|
+
return id.replace(/_/g, " ");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function segmentPlacement(
|
|
226
|
+
id: StatusLineSegmentId,
|
|
227
|
+
leftSegments: StatusLineSegmentId[],
|
|
228
|
+
rightSegments: StatusLineSegmentId[],
|
|
229
|
+
): "hidden" | "left" | "right" {
|
|
230
|
+
if (leftSegments.includes(id)) return "left";
|
|
231
|
+
if (rightSegments.includes(id)) return "right";
|
|
232
|
+
return "hidden";
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
class StatusLineCustomEditor extends Container {
|
|
236
|
+
#list!: SettingsList;
|
|
237
|
+
#draft: StatusLineDraft;
|
|
238
|
+
#currentWidthPreviewText!: Text;
|
|
239
|
+
#narrowWidthPreviewText!: Text;
|
|
240
|
+
#previewHighlightSegment: StatusLineSegmentId | undefined;
|
|
241
|
+
|
|
242
|
+
constructor(
|
|
243
|
+
private readonly callbacks: SettingsCallbacks,
|
|
244
|
+
private readonly done: (value?: string) => void,
|
|
245
|
+
) {
|
|
246
|
+
super();
|
|
247
|
+
const preset = settings.get("statusLine.preset");
|
|
248
|
+
const seeded = effectiveCustomSegments(
|
|
249
|
+
preset,
|
|
250
|
+
settings.get("statusLine.leftSegments"),
|
|
251
|
+
settings.get("statusLine.rightSegments"),
|
|
252
|
+
);
|
|
253
|
+
this.#draft = {
|
|
254
|
+
preset: "custom",
|
|
255
|
+
leftSegments: seeded.leftSegments,
|
|
256
|
+
rightSegments: seeded.rightSegments,
|
|
257
|
+
separator: settings.get("statusLine.separator"),
|
|
258
|
+
segmentOptions: effectiveSegmentOptions(
|
|
259
|
+
preset,
|
|
260
|
+
settings.get("statusLine.segmentOptions") as StatusLineSegmentOptions,
|
|
261
|
+
),
|
|
262
|
+
};
|
|
263
|
+
this.#preview();
|
|
264
|
+
this.#build();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
#build(): void {
|
|
268
|
+
this.clear();
|
|
269
|
+
this.addChild(new Text(theme.bold(theme.fg("accent", "Status Line Custom Editor")), 0, 0));
|
|
270
|
+
this.addChild(new Spacer(1));
|
|
271
|
+
this.addChild(new Text(theme.fg("muted", "Current width preview:"), 0, 0));
|
|
272
|
+
this.#currentWidthPreviewText = new Text(this.#statusLinePreview(), 0, 0);
|
|
273
|
+
this.addChild(this.#currentWidthPreviewText);
|
|
274
|
+
this.addChild(new Text(theme.fg("muted", "Narrow width preview:"), 0, 0));
|
|
275
|
+
this.#narrowWidthPreviewText = new Text(this.#statusLinePreview(40), 0, 0);
|
|
276
|
+
this.addChild(this.#narrowWidthPreviewText);
|
|
277
|
+
this.addChild(new Spacer(1));
|
|
278
|
+
this.#list = new SettingsList(
|
|
279
|
+
this.#items(),
|
|
280
|
+
14,
|
|
281
|
+
getSettingsListTheme(),
|
|
282
|
+
(id, value) => this.#handleChange(id, value),
|
|
283
|
+
() => this.#cancel(),
|
|
284
|
+
item => this.#setSelectedItem(item),
|
|
285
|
+
);
|
|
286
|
+
this.addChild(this.#list);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
#refresh(): void {
|
|
290
|
+
this.#refreshPreview();
|
|
291
|
+
this.#list.setItems(this.#items());
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
#refreshPreview(): void {
|
|
295
|
+
this.#currentWidthPreviewText.setText(this.#statusLinePreview());
|
|
296
|
+
this.#narrowWidthPreviewText.setText(this.#statusLinePreview(40));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
#statusLinePreview(width?: number): string {
|
|
300
|
+
return this.callbacks.getStatusLinePreview?.(width) ?? theme.fg("dim", "(preview not available)");
|
|
301
|
+
}
|
|
302
|
+
#setSelectedItem(item: SettingItem | undefined): void {
|
|
303
|
+
this.#previewHighlightSegment = this.#highlightSegmentForItem(item);
|
|
304
|
+
this.#preview();
|
|
305
|
+
this.#refreshPreview();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
#highlightSegmentForItem(item: SettingItem | undefined): StatusLineSegmentId | undefined {
|
|
309
|
+
if (!item) return undefined;
|
|
310
|
+
if (item.id.startsWith("segment.")) {
|
|
311
|
+
return item.id.slice("segment.".length) as StatusLineSegmentId;
|
|
312
|
+
}
|
|
313
|
+
if (item.id.startsWith("moveup.")) {
|
|
314
|
+
return item.id.slice("moveup.".length) as StatusLineSegmentId;
|
|
315
|
+
}
|
|
316
|
+
if (item.id.startsWith("movedown.")) {
|
|
317
|
+
return item.id.slice("movedown.".length) as StatusLineSegmentId;
|
|
318
|
+
}
|
|
319
|
+
if (item.id.startsWith("option.")) {
|
|
320
|
+
const [segment] = item.id.slice("option.".length).split(".");
|
|
321
|
+
return segment as StatusLineSegmentId;
|
|
322
|
+
}
|
|
323
|
+
return undefined;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
#items(): SettingItem[] {
|
|
327
|
+
const items: SettingItem[] = [
|
|
328
|
+
{
|
|
329
|
+
id: "action.save",
|
|
330
|
+
label: "Save custom status line",
|
|
331
|
+
description: "Persist the previewed custom status line to user config.",
|
|
332
|
+
currentValue: "approve",
|
|
333
|
+
values: ["approve"],
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
id: "action.cancel",
|
|
337
|
+
label: "Cancel and restore",
|
|
338
|
+
description: "Close without persisting draft settings and restore the previous preview.",
|
|
339
|
+
currentValue: "restore",
|
|
340
|
+
values: ["restore"],
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
id: "separator",
|
|
344
|
+
label: "Separator",
|
|
345
|
+
description: "Separator style between status line segments.",
|
|
346
|
+
currentValue: this.#draft.separator,
|
|
347
|
+
submenu: (currentValue, done) =>
|
|
348
|
+
new SelectSubmenu(
|
|
349
|
+
"Status Line Separator",
|
|
350
|
+
"Style of separators between segments.",
|
|
351
|
+
[
|
|
352
|
+
{ value: "powerline", label: "Powerline" },
|
|
353
|
+
{ value: "powerline-thin", label: "Thin chevron" },
|
|
354
|
+
{ value: "slash", label: "Slash" },
|
|
355
|
+
{ value: "pipe", label: "Pipe" },
|
|
356
|
+
{ value: "block", label: "Block" },
|
|
357
|
+
{ value: "none", label: "None" },
|
|
358
|
+
{ value: "ascii", label: "ASCII" },
|
|
359
|
+
],
|
|
360
|
+
currentValue,
|
|
361
|
+
done,
|
|
362
|
+
() => done(),
|
|
363
|
+
),
|
|
364
|
+
},
|
|
365
|
+
];
|
|
366
|
+
|
|
367
|
+
for (const id of PUBLIC_STATUS_SEGMENTS) {
|
|
368
|
+
items.push({
|
|
369
|
+
id: `segment.${id}`,
|
|
370
|
+
label: `Segment: ${statusSegmentLabel(id)}`,
|
|
371
|
+
description: "Cycle placement: hidden → left → right. Use move actions below to reorder visible segments.",
|
|
372
|
+
currentValue: segmentPlacement(id, this.#draft.leftSegments, this.#draft.rightSegments),
|
|
373
|
+
values: ["hidden", "left", "right"],
|
|
374
|
+
});
|
|
375
|
+
if (segmentPlacement(id, this.#draft.leftSegments, this.#draft.rightSegments) !== "hidden") {
|
|
376
|
+
items.push(
|
|
377
|
+
{
|
|
378
|
+
id: `moveup.${id}`,
|
|
379
|
+
label: `Move left: ${statusSegmentLabel(id)}`,
|
|
380
|
+
currentValue: "←",
|
|
381
|
+
values: ["←"],
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
id: `movedown.${id}`,
|
|
385
|
+
label: `Move right: ${statusSegmentLabel(id)}`,
|
|
386
|
+
currentValue: "→",
|
|
387
|
+
values: ["→"],
|
|
388
|
+
},
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
items.push(
|
|
394
|
+
{
|
|
395
|
+
id: "option.model.showThinkingLevel",
|
|
396
|
+
label: "Model: show thinking level",
|
|
397
|
+
currentValue: String(this.#draft.segmentOptions.model?.showThinkingLevel !== false),
|
|
398
|
+
values: BOOL_VALUES,
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
id: "option.path.abbreviate",
|
|
402
|
+
label: "Path: abbreviate",
|
|
403
|
+
currentValue: String(this.#draft.segmentOptions.path?.abbreviate !== false),
|
|
404
|
+
values: BOOL_VALUES,
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
id: "option.path.maxLength",
|
|
408
|
+
label: "Path: max length",
|
|
409
|
+
currentValue: String(this.#draft.segmentOptions.path?.maxLength ?? 32),
|
|
410
|
+
submenu: (currentValue, done) =>
|
|
411
|
+
new SelectSubmenu(
|
|
412
|
+
"Path max length",
|
|
413
|
+
"Maximum displayed path length.",
|
|
414
|
+
PATH_LENGTH_OPTIONS,
|
|
415
|
+
currentValue,
|
|
416
|
+
done,
|
|
417
|
+
() => done(),
|
|
418
|
+
),
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
id: "option.path.stripWorkPrefix",
|
|
422
|
+
label: "Path: strip work prefix",
|
|
423
|
+
currentValue: String(this.#draft.segmentOptions.path?.stripWorkPrefix === true),
|
|
424
|
+
values: BOOL_VALUES,
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
id: "option.git.showBranch",
|
|
428
|
+
label: "Git: show branch",
|
|
429
|
+
currentValue: String(this.#draft.segmentOptions.git?.showBranch !== false),
|
|
430
|
+
values: BOOL_VALUES,
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
id: "option.git.showStaged",
|
|
434
|
+
label: "Git: show staged",
|
|
435
|
+
currentValue: String(this.#draft.segmentOptions.git?.showStaged !== false),
|
|
436
|
+
values: BOOL_VALUES,
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
id: "option.git.showUnstaged",
|
|
440
|
+
label: "Git: show unstaged",
|
|
441
|
+
currentValue: String(this.#draft.segmentOptions.git?.showUnstaged !== false),
|
|
442
|
+
values: BOOL_VALUES,
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
id: "option.git.showUntracked",
|
|
446
|
+
label: "Git: show untracked",
|
|
447
|
+
currentValue: String(this.#draft.segmentOptions.git?.showUntracked !== false),
|
|
448
|
+
values: BOOL_VALUES,
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
id: "option.time.format",
|
|
452
|
+
label: "Time: format",
|
|
453
|
+
currentValue: this.#draft.segmentOptions.time?.format ?? "24h",
|
|
454
|
+
submenu: (currentValue, done) =>
|
|
455
|
+
new SelectSubmenu(
|
|
456
|
+
"Time format",
|
|
457
|
+
"Clock format for the time segment.",
|
|
458
|
+
TIME_FORMAT_OPTIONS,
|
|
459
|
+
currentValue,
|
|
460
|
+
done,
|
|
461
|
+
() => done(),
|
|
462
|
+
),
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
id: "option.time.showSeconds",
|
|
466
|
+
label: "Time: show seconds",
|
|
467
|
+
currentValue: String(this.#draft.segmentOptions.time?.showSeconds === true),
|
|
468
|
+
values: BOOL_VALUES,
|
|
469
|
+
},
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
return items;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
#handleChange(id: string, value: string): void {
|
|
476
|
+
if (id === "action.save") {
|
|
477
|
+
this.#save();
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
if (id === "action.cancel") {
|
|
481
|
+
this.#cancel();
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (id === "separator") {
|
|
485
|
+
this.#draft.separator = value as StatusLineSeparatorStyle;
|
|
486
|
+
} else if (id.startsWith("segment.")) {
|
|
487
|
+
this.#setSegmentPlacement(
|
|
488
|
+
id.slice("segment.".length) as StatusLineSegmentId,
|
|
489
|
+
value as "hidden" | "left" | "right",
|
|
490
|
+
);
|
|
491
|
+
} else if (id.startsWith("moveup.")) {
|
|
492
|
+
this.#moveSegment(id.slice("moveup.".length) as StatusLineSegmentId, -1);
|
|
493
|
+
} else if (id.startsWith("movedown.")) {
|
|
494
|
+
this.#moveSegment(id.slice("movedown.".length) as StatusLineSegmentId, 1);
|
|
495
|
+
} else if (id.startsWith("option.")) {
|
|
496
|
+
this.#setOption(id.slice("option.".length), value);
|
|
497
|
+
}
|
|
498
|
+
this.#preview();
|
|
499
|
+
this.#refresh();
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
#setSegmentPlacement(id: StatusLineSegmentId, placement: "hidden" | "left" | "right"): void {
|
|
503
|
+
this.#draft.leftSegments = this.#draft.leftSegments.filter(segment => segment !== id);
|
|
504
|
+
this.#draft.rightSegments = this.#draft.rightSegments.filter(segment => segment !== id);
|
|
505
|
+
if (placement === "left") this.#draft.leftSegments.push(id);
|
|
506
|
+
if (placement === "right") this.#draft.rightSegments.push(id);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
#moveSegment(id: StatusLineSegmentId, delta: -1 | 1): void {
|
|
510
|
+
const group = this.#draft.leftSegments.includes(id) ? this.#draft.leftSegments : this.#draft.rightSegments;
|
|
511
|
+
const index = group.indexOf(id);
|
|
512
|
+
if (index < 0) return;
|
|
513
|
+
const nextIndex = Math.max(0, Math.min(group.length - 1, index + delta));
|
|
514
|
+
if (nextIndex === index) return;
|
|
515
|
+
const [segment] = group.splice(index, 1);
|
|
516
|
+
if (segment) group.splice(nextIndex, 0, segment);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
#setOption(path: string, value: string): void {
|
|
520
|
+
const bool = value === "true";
|
|
521
|
+
switch (path) {
|
|
522
|
+
case "model.showThinkingLevel":
|
|
523
|
+
this.#draft.segmentOptions.model = { ...(this.#draft.segmentOptions.model ?? {}), showThinkingLevel: bool };
|
|
524
|
+
break;
|
|
525
|
+
case "path.abbreviate":
|
|
526
|
+
this.#draft.segmentOptions.path = { ...(this.#draft.segmentOptions.path ?? {}), abbreviate: bool };
|
|
527
|
+
break;
|
|
528
|
+
case "path.maxLength":
|
|
529
|
+
this.#draft.segmentOptions.path = { ...(this.#draft.segmentOptions.path ?? {}), maxLength: Number(value) };
|
|
530
|
+
break;
|
|
531
|
+
case "path.stripWorkPrefix":
|
|
532
|
+
this.#draft.segmentOptions.path = { ...(this.#draft.segmentOptions.path ?? {}), stripWorkPrefix: bool };
|
|
533
|
+
break;
|
|
534
|
+
case "git.showBranch":
|
|
535
|
+
this.#draft.segmentOptions.git = { ...(this.#draft.segmentOptions.git ?? {}), showBranch: bool };
|
|
536
|
+
break;
|
|
537
|
+
case "git.showStaged":
|
|
538
|
+
this.#draft.segmentOptions.git = { ...(this.#draft.segmentOptions.git ?? {}), showStaged: bool };
|
|
539
|
+
break;
|
|
540
|
+
case "git.showUnstaged":
|
|
541
|
+
this.#draft.segmentOptions.git = { ...(this.#draft.segmentOptions.git ?? {}), showUnstaged: bool };
|
|
542
|
+
break;
|
|
543
|
+
case "git.showUntracked":
|
|
544
|
+
this.#draft.segmentOptions.git = { ...(this.#draft.segmentOptions.git ?? {}), showUntracked: bool };
|
|
545
|
+
break;
|
|
546
|
+
case "time.format":
|
|
547
|
+
this.#draft.segmentOptions.time = {
|
|
548
|
+
...(this.#draft.segmentOptions.time ?? {}),
|
|
549
|
+
format: value as "12h" | "24h",
|
|
550
|
+
};
|
|
551
|
+
break;
|
|
552
|
+
case "time.showSeconds":
|
|
553
|
+
this.#draft.segmentOptions.time = { ...(this.#draft.segmentOptions.time ?? {}), showSeconds: bool };
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
#preview(): void {
|
|
559
|
+
this.callbacks.onStatusLinePreview?.({
|
|
560
|
+
preset: "custom",
|
|
561
|
+
leftSegments: [...this.#draft.leftSegments],
|
|
562
|
+
rightSegments: [...this.#draft.rightSegments],
|
|
563
|
+
separator: this.#draft.separator,
|
|
564
|
+
segmentOptions: cloneSegmentOptions(this.#draft.segmentOptions),
|
|
565
|
+
previewHighlightSegment: this.#previewHighlightSegment,
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
#restorePreview(): void {
|
|
570
|
+
this.callbacks.onStatusLinePreview?.({
|
|
571
|
+
preset: settings.get("statusLine.preset"),
|
|
572
|
+
leftSegments: settings.get("statusLine.leftSegments"),
|
|
573
|
+
rightSegments: settings.get("statusLine.rightSegments"),
|
|
574
|
+
separator: settings.get("statusLine.separator"),
|
|
575
|
+
segmentOptions: cloneSegmentOptions(settings.get("statusLine.segmentOptions") as StatusLineSegmentOptions),
|
|
576
|
+
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
577
|
+
previewHighlightSegment: undefined,
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
#save(): void {
|
|
582
|
+
settings.set("statusLine.preset", "custom");
|
|
583
|
+
settings.set("statusLine.leftSegments", [...this.#draft.leftSegments]);
|
|
584
|
+
settings.set("statusLine.rightSegments", [...this.#draft.rightSegments]);
|
|
585
|
+
settings.set("statusLine.separator", this.#draft.separator);
|
|
586
|
+
settings.set(
|
|
587
|
+
"statusLine.segmentOptions",
|
|
588
|
+
cloneSegmentOptions(this.#draft.segmentOptions) as Record<string, unknown>,
|
|
589
|
+
);
|
|
590
|
+
this.callbacks.onChange("statusLine.preset", "custom");
|
|
591
|
+
this.callbacks.onChange("statusLine.leftSegments", [...this.#draft.leftSegments]);
|
|
592
|
+
this.callbacks.onChange("statusLine.rightSegments", [...this.#draft.rightSegments]);
|
|
593
|
+
this.callbacks.onChange("statusLine.separator", this.#draft.separator);
|
|
594
|
+
this.callbacks.onChange("statusLine.segmentOptions", cloneSegmentOptions(this.#draft.segmentOptions));
|
|
595
|
+
this.#previewHighlightSegment = undefined;
|
|
596
|
+
this.#preview();
|
|
597
|
+
this.done("saved");
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
#cancel(): void {
|
|
601
|
+
this.#restorePreview();
|
|
602
|
+
this.done();
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
handleInput(data: string): void {
|
|
606
|
+
this.#list.handleInput(data);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
164
609
|
|
|
165
610
|
function getSettingsTabs(): Tab[] {
|
|
166
611
|
return [
|
|
@@ -194,6 +639,8 @@ export interface StatusLinePreviewSettings {
|
|
|
194
639
|
leftSegments?: StatusLineSegmentId[];
|
|
195
640
|
rightSegments?: StatusLineSegmentId[];
|
|
196
641
|
separator?: StatusLineSeparatorStyle;
|
|
642
|
+
segmentOptions?: StatusLineSegmentOptions;
|
|
643
|
+
previewHighlightSegment?: StatusLineSegmentId;
|
|
197
644
|
sessionAccent?: boolean;
|
|
198
645
|
}
|
|
199
646
|
|
|
@@ -207,7 +654,7 @@ export interface SettingsCallbacks {
|
|
|
207
654
|
/** Called for status line preview while configuring */
|
|
208
655
|
onStatusLinePreview?: (settings: StatusLinePreviewSettings) => void;
|
|
209
656
|
/** Get current rendered status line for inline preview */
|
|
210
|
-
getStatusLinePreview?: () => string;
|
|
657
|
+
getStatusLinePreview?: (width?: number) => string;
|
|
211
658
|
/** Called when plugins change */
|
|
212
659
|
onPluginsChanged?: () => void;
|
|
213
660
|
/** Called when settings panel is closed */
|
|
@@ -372,7 +819,9 @@ export class SettingsSelectorComponent extends Container {
|
|
|
372
819
|
} else if (def.path === "theme.dark" || def.path === "theme.light") {
|
|
373
820
|
options = this.context.availableThemes.map(t => ({ value: t, label: t }));
|
|
374
821
|
}
|
|
375
|
-
|
|
822
|
+
if (def.path === "statusLine.preset") {
|
|
823
|
+
options = options.filter(option => option.value !== "custom");
|
|
824
|
+
}
|
|
376
825
|
// Preview handlers
|
|
377
826
|
let onPreview: ((value: string) => void | Promise<void>) | undefined;
|
|
378
827
|
let onPreviewCancel: (() => void) | undefined;
|
|
@@ -395,17 +844,31 @@ export class SettingsSelectorComponent extends Container {
|
|
|
395
844
|
leftSegments: presetDef.leftSegments,
|
|
396
845
|
rightSegments: presetDef.rightSegments,
|
|
397
846
|
separator: presetDef.separator,
|
|
847
|
+
previewHighlightSegment: undefined,
|
|
398
848
|
});
|
|
399
849
|
this.#updateStatusPreview();
|
|
400
850
|
};
|
|
401
851
|
onPreviewCancel = () => {
|
|
402
852
|
const currentPreset = settings.get("statusLine.preset");
|
|
403
853
|
const presetDef = getPreset(currentPreset);
|
|
854
|
+
const savedCustomSettings =
|
|
855
|
+
currentPreset === "custom"
|
|
856
|
+
? {
|
|
857
|
+
leftSegments: settings.get("statusLine.leftSegments"),
|
|
858
|
+
rightSegments: settings.get("statusLine.rightSegments"),
|
|
859
|
+
separator: settings.get("statusLine.separator"),
|
|
860
|
+
segmentOptions: cloneSegmentOptions(
|
|
861
|
+
settings.get("statusLine.segmentOptions") as StatusLineSegmentOptions,
|
|
862
|
+
),
|
|
863
|
+
}
|
|
864
|
+
: {};
|
|
404
865
|
this.callbacks.onStatusLinePreview?.({
|
|
405
866
|
preset: currentPreset,
|
|
406
867
|
leftSegments: presetDef.leftSegments,
|
|
407
868
|
rightSegments: presetDef.rightSegments,
|
|
408
869
|
separator: presetDef.separator,
|
|
870
|
+
...savedCustomSettings,
|
|
871
|
+
previewHighlightSegment: undefined,
|
|
409
872
|
});
|
|
410
873
|
this.#updateStatusPreview();
|
|
411
874
|
};
|
|
@@ -416,7 +879,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
416
879
|
};
|
|
417
880
|
onPreviewCancel = () => {
|
|
418
881
|
const separator = settings.get("statusLine.separator");
|
|
419
|
-
this.callbacks.onStatusLinePreview?.({ separator });
|
|
882
|
+
this.callbacks.onStatusLinePreview?.({ separator, previewHighlightSegment: undefined });
|
|
420
883
|
this.#updateStatusPreview();
|
|
421
884
|
};
|
|
422
885
|
}
|
|
@@ -509,7 +972,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
509
972
|
}
|
|
510
973
|
|
|
511
974
|
this.#currentList = new SettingsList(
|
|
512
|
-
this.#
|
|
975
|
+
this.#buildItemsForTab(defs, tabId),
|
|
513
976
|
10,
|
|
514
977
|
getSettingsListTheme(),
|
|
515
978
|
(id, newValue) => {
|
|
@@ -553,10 +1016,31 @@ export class SettingsSelectorComponent extends Container {
|
|
|
553
1016
|
return items;
|
|
554
1017
|
}
|
|
555
1018
|
|
|
1019
|
+
#buildItemsForTab(defs: SettingDef[], tabId: SettingTab): SettingItem[] {
|
|
1020
|
+
const items = this.#buildItemsForDefs(defs);
|
|
1021
|
+
if (tabId === "appearance") {
|
|
1022
|
+
const customEditorItem: SettingItem = {
|
|
1023
|
+
id: STATUS_LINE_CUSTOM_EDITOR_ID,
|
|
1024
|
+
label: "Status Line Custom Editor",
|
|
1025
|
+
description:
|
|
1026
|
+
"Edit custom status line segments, placement, separator, and typed segment options with live previews.",
|
|
1027
|
+
currentValue: "open",
|
|
1028
|
+
submenu: (_currentValue, done) => new StatusLineCustomEditor(this.callbacks, done),
|
|
1029
|
+
};
|
|
1030
|
+
const presetIndex = items.findIndex(item => item.id === "statusLine.preset");
|
|
1031
|
+
if (presetIndex >= 0) {
|
|
1032
|
+
items.splice(presetIndex + 1, 0, customEditorItem);
|
|
1033
|
+
} else {
|
|
1034
|
+
items.push(customEditorItem);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
return items;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
556
1040
|
/** Re-evaluate condition gates against the current settings and refresh the active list. */
|
|
557
1041
|
#refreshCurrentTabItems(defs: SettingDef[]): void {
|
|
558
1042
|
if (this.#currentTabId === "plugins" || !this.#currentList) return;
|
|
559
|
-
this.#currentList.setItems(this.#
|
|
1043
|
+
this.#currentList.setItems(this.#buildItemsForTab(defs, this.#currentTabId));
|
|
560
1044
|
}
|
|
561
1045
|
|
|
562
1046
|
/**
|
|
@@ -578,7 +1062,9 @@ export class SettingsSelectorComponent extends Container {
|
|
|
578
1062
|
leftSegments: settings.get("statusLine.leftSegments"),
|
|
579
1063
|
rightSegments: settings.get("statusLine.rightSegments"),
|
|
580
1064
|
separator: settings.get("statusLine.separator"),
|
|
1065
|
+
segmentOptions: cloneSegmentOptions(settings.get("statusLine.segmentOptions") as StatusLineSegmentOptions),
|
|
581
1066
|
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
1067
|
+
previewHighlightSegment: undefined,
|
|
582
1068
|
};
|
|
583
1069
|
this.callbacks.onStatusLinePreview?.(statusLineSettings);
|
|
584
1070
|
this.#updateStatusPreview();
|
|
@@ -505,28 +505,17 @@ const usageSegment: StatusLineSegment = {
|
|
|
505
505
|
id: "usage",
|
|
506
506
|
render(ctx) {
|
|
507
507
|
const u = ctx.usage;
|
|
508
|
-
if (!u ||
|
|
508
|
+
if (!u || u.windows.length === 0) {
|
|
509
509
|
return { content: "", visible: false };
|
|
510
510
|
}
|
|
511
|
-
const parts
|
|
512
|
-
|
|
513
|
-
const pct = u.fiveHour.percent;
|
|
514
|
-
const pctText = theme.fg(pickUsageColor(pct), `${Math.round(pct)}%`);
|
|
511
|
+
const parts = u.windows.map(window => {
|
|
512
|
+
const pctText = theme.fg(pickUsageColor(window.percent), `${Math.round(window.percent)}%`);
|
|
515
513
|
const reset =
|
|
516
|
-
|
|
517
|
-
? theme.fg("muted", ` (${formatUsageReset(
|
|
514
|
+
window.resetValue !== undefined && window.resetUnit !== undefined
|
|
515
|
+
? theme.fg("muted", ` (${formatUsageReset(window.resetValue, window.resetUnit)})`)
|
|
518
516
|
: "";
|
|
519
|
-
|
|
520
|
-
}
|
|
521
|
-
if (u.sevenDay) {
|
|
522
|
-
const pct = u.sevenDay.percent;
|
|
523
|
-
const pctText = theme.fg(pickUsageColor(pct), `${Math.round(pct)}%`);
|
|
524
|
-
const reset =
|
|
525
|
-
u.sevenDay.resetHours !== undefined
|
|
526
|
-
? theme.fg("muted", ` (${formatUsageReset(u.sevenDay.resetHours, "h")})`)
|
|
527
|
-
: "";
|
|
528
|
-
parts.push(`7d ${pctText}${reset}`);
|
|
529
|
-
}
|
|
517
|
+
return `${window.label} ${pctText}${reset}`;
|
|
518
|
+
});
|
|
530
519
|
const content = withIcon(theme.icon.time, parts.join(theme.sep.dot));
|
|
531
520
|
return { content, visible: true };
|
|
532
521
|
},
|
|
@@ -51,8 +51,12 @@ export interface SegmentContext {
|
|
|
51
51
|
pr: { number: number; url: string } | null;
|
|
52
52
|
};
|
|
53
53
|
usage: {
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
windows: Array<{
|
|
55
|
+
label: string;
|
|
56
|
+
percent: number;
|
|
57
|
+
resetValue?: number;
|
|
58
|
+
resetUnit?: "m" | "h";
|
|
59
|
+
}>;
|
|
56
60
|
} | null;
|
|
57
61
|
}
|
|
58
62
|
|