@herbertgao/pi-extensions 2026.8.3 → 2026.8.4
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 +41 -0
- package/examples/pi-footer.json +275 -0
- package/node_modules/pi-footer/CHANGELOG.md +145 -0
- package/node_modules/pi-footer/LICENSE +21 -0
- package/node_modules/pi-footer/README.md +354 -0
- package/node_modules/pi-footer/package.json +68 -0
- package/node_modules/pi-footer/src/cache.ts +106 -0
- package/node_modules/pi-footer/src/colors.ts +237 -0
- package/node_modules/pi-footer/src/config.ts +202 -0
- package/node_modules/pi-footer/src/event-widgets.ts +41 -0
- package/node_modules/pi-footer/src/extension-statuses.ts +102 -0
- package/node_modules/pi-footer/src/git.ts +135 -0
- package/node_modules/pi-footer/src/index.ts +268 -0
- package/node_modules/pi-footer/src/metrics.ts +141 -0
- package/node_modules/pi-footer/src/presets.ts +383 -0
- package/node_modules/pi-footer/src/render.ts +114 -0
- package/node_modules/pi-footer/src/separators.ts +52 -0
- package/node_modules/pi-footer/src/types.ts +122 -0
- package/node_modules/pi-footer/src/ui/color-level-confirm.ts +22 -0
- package/node_modules/pi-footer/src/ui/color-options.ts +195 -0
- package/node_modules/pi-footer/src/ui/config-lifecycle.ts +61 -0
- package/node_modules/pi-footer/src/ui/edit-colors.ts +13 -0
- package/node_modules/pi-footer/src/ui/events.ts +35 -0
- package/node_modules/pi-footer/src/ui/extension-status-picker.ts +60 -0
- package/node_modules/pi-footer/src/ui/extension-statuses.ts +73 -0
- package/node_modules/pi-footer/src/ui/fields.ts +252 -0
- package/node_modules/pi-footer/src/ui/global-menu.ts +126 -0
- package/node_modules/pi-footer/src/ui/helpers.ts +44 -0
- package/node_modules/pi-footer/src/ui/layout.ts +17 -0
- package/node_modules/pi-footer/src/ui/line-list.ts +19 -0
- package/node_modules/pi-footer/src/ui/model.ts +64 -0
- package/node_modules/pi-footer/src/ui/navigation.ts +29 -0
- package/node_modules/pi-footer/src/ui/option-edit.ts +93 -0
- package/node_modules/pi-footer/src/ui/overlay-render.ts +103 -0
- package/node_modules/pi-footer/src/ui/screen-context.ts +18 -0
- package/node_modules/pi-footer/src/ui/screen-controller.ts +27 -0
- package/node_modules/pi-footer/src/ui/screen-render.ts +32 -0
- package/node_modules/pi-footer/src/ui/screen-state.ts +20 -0
- package/node_modules/pi-footer/src/ui/screen.ts +243 -0
- package/node_modules/pi-footer/src/ui/screens/add-widget.ts +92 -0
- package/node_modules/pi-footer/src/ui/screens/confirm-exit.ts +52 -0
- package/node_modules/pi-footer/src/ui/screens/controller.ts +12 -0
- package/node_modules/pi-footer/src/ui/screens/edit-colors.ts +68 -0
- package/node_modules/pi-footer/src/ui/screens/edit-widget.ts +115 -0
- package/node_modules/pi-footer/src/ui/screens/extension-status-row.ts +61 -0
- package/node_modules/pi-footer/src/ui/screens/global.ts +75 -0
- package/node_modules/pi-footer/src/ui/screens/line-list.ts +108 -0
- package/node_modules/pi-footer/src/ui/screens/main.ts +99 -0
- package/node_modules/pi-footer/src/ui/screens/terminal.ts +121 -0
- package/node_modules/pi-footer/src/ui/screens/widget-list.ts +154 -0
- package/node_modules/pi-footer/src/ui/terminal-menu.ts +32 -0
- package/node_modules/pi-footer/src/ui/theme.ts +48 -0
- package/node_modules/pi-footer/src/ui/title-bar.ts +100 -0
- package/node_modules/pi-footer/src/ui/widget-actions.ts +76 -0
- package/node_modules/pi-footer/src/ui.ts +66 -0
- package/node_modules/pi-footer/src/widgets/context.ts +28 -0
- package/node_modules/pi-footer/src/widgets/core/active-tools.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/context-window.ts +24 -0
- package/node_modules/pi-footer/src/widgets/core/cwd-basename.ts +19 -0
- package/node_modules/pi-footer/src/widgets/core/cwd.ts +108 -0
- package/node_modules/pi-footer/src/widgets/core/event.ts +34 -0
- package/node_modules/pi-footer/src/widgets/core/external-status.ts +102 -0
- package/node_modules/pi-footer/src/widgets/core/model-provider.ts +20 -0
- package/node_modules/pi-footer/src/widgets/core/model.ts +35 -0
- package/node_modules/pi-footer/src/widgets/core/provider.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/session-name.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/text-verbosity.ts +17 -0
- package/node_modules/pi-footer/src/widgets/core/thinking-level.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/ahead-behind.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/branch.ts +81 -0
- package/node_modules/pi-footer/src/widgets/git/clean.ts +19 -0
- package/node_modules/pi-footer/src/widgets/git/deletions.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/diff.ts +38 -0
- package/node_modules/pi-footer/src/widgets/git/insertions.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/remote.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/root.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/sha.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/staged.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/status.ts +19 -0
- package/node_modules/pi-footer/src/widgets/git/unstaged.ts +17 -0
- package/node_modules/pi-footer/src/widgets/git/untracked.ts +17 -0
- package/node_modules/pi-footer/src/widgets/instance.ts +118 -0
- package/node_modules/pi-footer/src/widgets/layout/custom-text.ts +18 -0
- package/node_modules/pi-footer/src/widgets/layout/flex-separator.ts +32 -0
- package/node_modules/pi-footer/src/widgets/layout/separator.ts +57 -0
- package/node_modules/pi-footer/src/widgets/layout/spacer.ts +31 -0
- package/node_modules/pi-footer/src/widgets/options.ts +122 -0
- package/node_modules/pi-footer/src/widgets/project/runtime.ts +361 -0
- package/node_modules/pi-footer/src/widgets/registry.ts +223 -0
- package/node_modules/pi-footer/src/widgets/session/assistant-messages.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/compactions.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/elapsed.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/last-activity.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/messages.ts +19 -0
- package/node_modules/pi-footer/src/widgets/session/session-id.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/session-start.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/tool-results.ts +17 -0
- package/node_modules/pi-footer/src/widgets/session/total-messages.ts +19 -0
- package/node_modules/pi-footer/src/widgets/session/total-time.ts +18 -0
- package/node_modules/pi-footer/src/widgets/session/user-messages.ts +17 -0
- package/node_modules/pi-footer/src/widgets/store.ts +26 -0
- package/node_modules/pi-footer/src/widgets/tokens/cache-hit-rate.ts +55 -0
- package/node_modules/pi-footer/src/widgets/tokens/cache-read.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/cache-write.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/context-bar.ts +108 -0
- package/node_modules/pi-footer/src/widgets/tokens/context-length.ts +26 -0
- package/node_modules/pi-footer/src/widgets/tokens/context-remaining.ts +29 -0
- package/node_modules/pi-footer/src/widgets/tokens/context.ts +29 -0
- package/node_modules/pi-footer/src/widgets/tokens/cost.ts +51 -0
- package/node_modules/pi-footer/src/widgets/tokens/input-speed.ts +25 -0
- package/node_modules/pi-footer/src/widgets/tokens/input-tokens.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/output-speed.ts +25 -0
- package/node_modules/pi-footer/src/widgets/tokens/output-tokens.ts +18 -0
- package/node_modules/pi-footer/src/widgets/tokens/tokens.ts +20 -0
- package/node_modules/pi-footer/src/widgets/tokens/total-speed.ts +25 -0
- package/node_modules/pi-footer/src/widgets/tokens/total-tokens.ts +18 -0
- package/node_modules/pi-footer/src/widgets/types.ts +181 -0
- package/node_modules/pi-footer/src/widgets/utils/colors.ts +11 -0
- package/node_modules/pi-footer/src/widgets/utils/context.ts +112 -0
- package/node_modules/pi-footer/src/widgets/utils/session.ts +19 -0
- package/node_modules/pi-footer/src/widgets/utils/token-format.ts +78 -0
- package/package.json +7 -2
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import {
|
|
2
|
+
appendAnsi256Digit,
|
|
3
|
+
deleteAnsi256Digit,
|
|
4
|
+
FOREGROUND_COLORS,
|
|
5
|
+
normalizeColor,
|
|
6
|
+
resetAnsi256Colors,
|
|
7
|
+
STANDARD_COLORS,
|
|
8
|
+
} from "../colors.js";
|
|
9
|
+
import type { WidgetOptions } from "../types.js";
|
|
10
|
+
import type { Widget } from "../widgets/types.js";
|
|
11
|
+
import { adjustAnsi, cycle } from "./helpers.js";
|
|
12
|
+
import type { ColorOptionField } from "./model.js";
|
|
13
|
+
|
|
14
|
+
export const EDIT_COLORS_HINT =
|
|
15
|
+
"↑/↓ field • ←/→ cycle/change • enter toggle • type digits for ANSI256 • backspace delete • esc back";
|
|
16
|
+
|
|
17
|
+
export function hasCustomAnsiColors(lines: readonly (readonly Widget[])[]): boolean {
|
|
18
|
+
return lines.some((line) =>
|
|
19
|
+
line.some(
|
|
20
|
+
(widget) =>
|
|
21
|
+
colorOption(widget, "fg")?.startsWith("ansi256:") ||
|
|
22
|
+
colorOption(widget, "bg")?.startsWith("ansi256:") ||
|
|
23
|
+
colorOption(widget, "warningFg")?.startsWith("ansi256:") ||
|
|
24
|
+
colorOption(widget, "warningBg")?.startsWith("ansi256:") ||
|
|
25
|
+
colorOption(widget, "dangerFg")?.startsWith("ansi256:") ||
|
|
26
|
+
colorOption(widget, "dangerBg")?.startsWith("ansi256:"),
|
|
27
|
+
),
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function resetCustomAnsiColors(lines: readonly (readonly Widget[])[]): void {
|
|
32
|
+
for (const line of lines) {
|
|
33
|
+
for (const widget of line) {
|
|
34
|
+
widget.update(resetAnsi256Colors(widget.options));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function applyColorDigit(widget: Widget, field: ColorOptionField, digit: string): boolean {
|
|
40
|
+
if (!/^\d$/.test(digit)) return false;
|
|
41
|
+
if (field.id === "fgAnsi")
|
|
42
|
+
return updateColorOption(widget, "fg", appendAnsi256Digit(colorOption(widget, "fg"), digit));
|
|
43
|
+
if (field.id === "bgAnsi")
|
|
44
|
+
return updateColorOption(widget, "bg", appendAnsi256Digit(colorOption(widget, "bg"), digit));
|
|
45
|
+
if (field.id === "warningFgAnsi")
|
|
46
|
+
return updateColorOption(
|
|
47
|
+
widget,
|
|
48
|
+
"warningFg",
|
|
49
|
+
appendAnsi256Digit(colorOption(widget, "warningFg"), digit),
|
|
50
|
+
);
|
|
51
|
+
if (field.id === "warningBgAnsi")
|
|
52
|
+
return updateColorOption(
|
|
53
|
+
widget,
|
|
54
|
+
"warningBg",
|
|
55
|
+
appendAnsi256Digit(colorOption(widget, "warningBg"), digit),
|
|
56
|
+
);
|
|
57
|
+
if (field.id === "dangerFgAnsi")
|
|
58
|
+
return updateColorOption(
|
|
59
|
+
widget,
|
|
60
|
+
"dangerFg",
|
|
61
|
+
appendAnsi256Digit(colorOption(widget, "dangerFg"), digit),
|
|
62
|
+
);
|
|
63
|
+
if (field.id === "dangerBgAnsi")
|
|
64
|
+
return updateColorOption(
|
|
65
|
+
widget,
|
|
66
|
+
"dangerBg",
|
|
67
|
+
appendAnsi256Digit(colorOption(widget, "dangerBg"), digit),
|
|
68
|
+
);
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function deleteColorDigit(widget: Widget, field: ColorOptionField): boolean {
|
|
73
|
+
if (field.id === "fgAnsi")
|
|
74
|
+
return updateColorOption(widget, "fg", deleteAnsi256Digit(colorOption(widget, "fg")));
|
|
75
|
+
if (field.id === "bgAnsi")
|
|
76
|
+
return updateColorOption(widget, "bg", deleteAnsi256Digit(colorOption(widget, "bg")));
|
|
77
|
+
if (field.id === "warningFgAnsi")
|
|
78
|
+
return updateColorOption(
|
|
79
|
+
widget,
|
|
80
|
+
"warningFg",
|
|
81
|
+
deleteAnsi256Digit(colorOption(widget, "warningFg")),
|
|
82
|
+
);
|
|
83
|
+
if (field.id === "warningBgAnsi")
|
|
84
|
+
return updateColorOption(
|
|
85
|
+
widget,
|
|
86
|
+
"warningBg",
|
|
87
|
+
deleteAnsi256Digit(colorOption(widget, "warningBg")),
|
|
88
|
+
);
|
|
89
|
+
if (field.id === "dangerFgAnsi")
|
|
90
|
+
return updateColorOption(
|
|
91
|
+
widget,
|
|
92
|
+
"dangerFg",
|
|
93
|
+
deleteAnsi256Digit(colorOption(widget, "dangerFg")),
|
|
94
|
+
);
|
|
95
|
+
if (field.id === "dangerBgAnsi")
|
|
96
|
+
return updateColorOption(
|
|
97
|
+
widget,
|
|
98
|
+
"dangerBg",
|
|
99
|
+
deleteAnsi256Digit(colorOption(widget, "dangerBg")),
|
|
100
|
+
);
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function applyColorOptionField(
|
|
105
|
+
widget: Widget,
|
|
106
|
+
field: ColorOptionField,
|
|
107
|
+
delta: number,
|
|
108
|
+
): void {
|
|
109
|
+
if (field.id === "bold") widget.update({ bold: !widget.options.bold });
|
|
110
|
+
else if (field.id === "fg")
|
|
111
|
+
updateColorOption(
|
|
112
|
+
widget,
|
|
113
|
+
"fg",
|
|
114
|
+
cycle(
|
|
115
|
+
FOREGROUND_COLORS.map((color) => color.value),
|
|
116
|
+
widget.options.fg ?? "default",
|
|
117
|
+
delta,
|
|
118
|
+
),
|
|
119
|
+
);
|
|
120
|
+
else if (field.id === "bg")
|
|
121
|
+
updateColorOption(
|
|
122
|
+
widget,
|
|
123
|
+
"bg",
|
|
124
|
+
cycle(
|
|
125
|
+
STANDARD_COLORS.map((color) => color.value),
|
|
126
|
+
widget.options.bg ?? "default",
|
|
127
|
+
delta,
|
|
128
|
+
),
|
|
129
|
+
);
|
|
130
|
+
else if (field.id === "warningFg")
|
|
131
|
+
updateColorOption(
|
|
132
|
+
widget,
|
|
133
|
+
"warningFg",
|
|
134
|
+
cycle(
|
|
135
|
+
FOREGROUND_COLORS.map((color) => color.value),
|
|
136
|
+
colorOption(widget, "warningFg") ?? "default",
|
|
137
|
+
delta,
|
|
138
|
+
),
|
|
139
|
+
);
|
|
140
|
+
else if (field.id === "warningBg")
|
|
141
|
+
updateColorOption(
|
|
142
|
+
widget,
|
|
143
|
+
"warningBg",
|
|
144
|
+
cycle(
|
|
145
|
+
STANDARD_COLORS.map((color) => color.value),
|
|
146
|
+
colorOption(widget, "warningBg") ?? "default",
|
|
147
|
+
delta,
|
|
148
|
+
),
|
|
149
|
+
);
|
|
150
|
+
else if (field.id === "dangerFg")
|
|
151
|
+
updateColorOption(
|
|
152
|
+
widget,
|
|
153
|
+
"dangerFg",
|
|
154
|
+
cycle(
|
|
155
|
+
FOREGROUND_COLORS.map((color) => color.value),
|
|
156
|
+
colorOption(widget, "dangerFg") ?? "default",
|
|
157
|
+
delta,
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
else if (field.id === "dangerBg")
|
|
161
|
+
updateColorOption(
|
|
162
|
+
widget,
|
|
163
|
+
"dangerBg",
|
|
164
|
+
cycle(
|
|
165
|
+
STANDARD_COLORS.map((color) => color.value),
|
|
166
|
+
colorOption(widget, "dangerBg") ?? "default",
|
|
167
|
+
delta,
|
|
168
|
+
),
|
|
169
|
+
);
|
|
170
|
+
else if (field.id === "fgAnsi")
|
|
171
|
+
updateColorOption(widget, "fg", adjustAnsi(colorOption(widget, "fg"), delta));
|
|
172
|
+
else if (field.id === "bgAnsi")
|
|
173
|
+
updateColorOption(widget, "bg", adjustAnsi(colorOption(widget, "bg"), delta));
|
|
174
|
+
else if (field.id === "warningFgAnsi")
|
|
175
|
+
updateColorOption(widget, "warningFg", adjustAnsi(colorOption(widget, "warningFg"), delta));
|
|
176
|
+
else if (field.id === "warningBgAnsi")
|
|
177
|
+
updateColorOption(widget, "warningBg", adjustAnsi(colorOption(widget, "warningBg"), delta));
|
|
178
|
+
else if (field.id === "dangerFgAnsi")
|
|
179
|
+
updateColorOption(widget, "dangerFg", adjustAnsi(colorOption(widget, "dangerFg"), delta));
|
|
180
|
+
else if (field.id === "dangerBgAnsi")
|
|
181
|
+
updateColorOption(widget, "dangerBg", adjustAnsi(colorOption(widget, "dangerBg"), delta));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function colorOption(widget: Widget, key: string) {
|
|
185
|
+
return normalizeColor(widget.options[key]);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function updateColorOption(
|
|
189
|
+
widget: Widget,
|
|
190
|
+
key: string,
|
|
191
|
+
value: WidgetOptions[keyof WidgetOptions],
|
|
192
|
+
): true {
|
|
193
|
+
widget.update({ [key]: value });
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { cloneConfig } from "../config.js";
|
|
2
|
+
import type { StatuslineConfig } from "../types.js";
|
|
3
|
+
|
|
4
|
+
export type ConfigChange = (config: StatuslineConfig) => void;
|
|
5
|
+
export type ConfigSave = (config: StatuslineConfig) => Promise<void>;
|
|
6
|
+
|
|
7
|
+
export interface ConfigUiResult {
|
|
8
|
+
config: StatuslineConfig;
|
|
9
|
+
saved: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type ConfigState = "clean" | "dirty" | "saving" | "saved" | "error";
|
|
13
|
+
|
|
14
|
+
export class ConfigLifecycle {
|
|
15
|
+
private savedConfig: StatuslineConfig;
|
|
16
|
+
private configState: ConfigState = "clean";
|
|
17
|
+
|
|
18
|
+
constructor(config: StatuslineConfig) {
|
|
19
|
+
this.savedConfig = cloneConfig(config);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get dirty(): boolean {
|
|
23
|
+
return this.configState === "dirty" || this.configState === "error";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get state(): ConfigState {
|
|
27
|
+
return this.configState;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get label(): string | undefined {
|
|
31
|
+
if (this.configState === "dirty") return "Unsaved";
|
|
32
|
+
if (this.configState === "saving") return "Saving…";
|
|
33
|
+
if (this.configState === "saved") return "Saved";
|
|
34
|
+
if (this.configState === "error") return "Save failed";
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
markChanged(): void {
|
|
39
|
+
this.configState = "dirty";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
beginSave(): void {
|
|
43
|
+
this.configState = "saving";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
markSaved(config: StatuslineConfig): void {
|
|
47
|
+
this.savedConfig = cloneConfig(config);
|
|
48
|
+
this.configState = "saved";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
markSaveFailed(): void {
|
|
52
|
+
this.configState = "error";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
closeResult(saved: boolean): ConfigUiResult {
|
|
56
|
+
return {
|
|
57
|
+
config: cloneConfig(this.savedConfig),
|
|
58
|
+
saved,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { registry } from "../widgets/registry.js";
|
|
2
|
+
import type { Widget } from "../widgets/types.js";
|
|
3
|
+
import { colorFields, colorFieldValue } from "./fields.js";
|
|
4
|
+
|
|
5
|
+
const EDIT_COLORS_TITLE_PREFIX = "Colors /";
|
|
6
|
+
|
|
7
|
+
export function editColorsTitle(widget: Widget): string {
|
|
8
|
+
return `${EDIT_COLORS_TITLE_PREFIX} ${registry.spec(widget.type).label}`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function editColorsFieldRows(widget: Widget): string[] {
|
|
12
|
+
return colorFields(widget).map((field) => `${field.label}: ${colorFieldValue(widget, field)}`);
|
|
13
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { highlightCode } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
import { UPDATE_EVENT_WIDGET_EVENT } from "../event-widgets.js";
|
|
4
|
+
import type { Widget } from "../widgets/types.js";
|
|
5
|
+
|
|
6
|
+
export function eventWidgetUsageLines(
|
|
7
|
+
widget: Widget,
|
|
8
|
+
width: number,
|
|
9
|
+
line: (content: string, width: number) => string,
|
|
10
|
+
dim: (text: string) => string,
|
|
11
|
+
): string[] {
|
|
12
|
+
return [
|
|
13
|
+
line("", width),
|
|
14
|
+
line(dim("Send events with a value:"), width),
|
|
15
|
+
line(eventWidgetUsageCode(widgetId(widget), "Value"), width),
|
|
16
|
+
line("", width),
|
|
17
|
+
line(dim("Send events to remove status:"), width),
|
|
18
|
+
line(eventWidgetUsageCode(widgetId(widget), "NULL"), width),
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function widgetId(widget: Widget) {
|
|
23
|
+
const value = widget.options.widgetId;
|
|
24
|
+
return typeof value === "string" ? value : "";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function eventWidgetUsageCode(widgetId: string, value: string): string {
|
|
28
|
+
const v = value === "NULL" ? "null" : `"${value}"`;
|
|
29
|
+
return (
|
|
30
|
+
highlightCode(
|
|
31
|
+
`pi.events.emit(${JSON.stringify(UPDATE_EVENT_WIDGET_EVENT)}, { "widgetId": ${JSON.stringify(widgetId)}, "value": ${v} });`,
|
|
32
|
+
"typescript",
|
|
33
|
+
)[0] ?? ""
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
2
|
+
|
|
3
|
+
import { STATUS_KEY } from "../config.js";
|
|
4
|
+
import {
|
|
5
|
+
allExtensionStatusEntries,
|
|
6
|
+
type ExtensionStatusRowConfig,
|
|
7
|
+
type GetExtensionStatuses,
|
|
8
|
+
} from "../extension-statuses.js";
|
|
9
|
+
import type { Widget } from "../widgets/types.js";
|
|
10
|
+
|
|
11
|
+
export function statusKeyPickerLines(
|
|
12
|
+
getExtensionStatuses: GetExtensionStatuses,
|
|
13
|
+
rowConfig: ExtensionStatusRowConfig,
|
|
14
|
+
width: number,
|
|
15
|
+
line: (content: string, width: number) => string,
|
|
16
|
+
dim: (text: string) => string,
|
|
17
|
+
): string[] {
|
|
18
|
+
const entries = allExtensionStatusEntries(getExtensionStatuses(), rowConfig, STATUS_KEY);
|
|
19
|
+
const lines = [line("", width), line(dim("Available extension statuses:"), width)];
|
|
20
|
+
|
|
21
|
+
if (entries.length === 0) {
|
|
22
|
+
lines.push(
|
|
23
|
+
line(dim("No extension statuses are currently available. Type a key manually."), width),
|
|
24
|
+
);
|
|
25
|
+
return lines;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
for (const entry of entries) {
|
|
29
|
+
lines.push(
|
|
30
|
+
line(
|
|
31
|
+
`${dim(entry.key)} ${truncateToWidth(entry.value, Math.max(1, width - entry.key.length - 4), "…")}`,
|
|
32
|
+
width,
|
|
33
|
+
),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return lines;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function cycleExternalStatusKey(
|
|
40
|
+
widget: Widget,
|
|
41
|
+
getExtensionStatuses: GetExtensionStatuses,
|
|
42
|
+
rowConfig: ExtensionStatusRowConfig,
|
|
43
|
+
delta: number,
|
|
44
|
+
): boolean {
|
|
45
|
+
const keys = allExtensionStatusEntries(getExtensionStatuses(), rowConfig, STATUS_KEY).map(
|
|
46
|
+
(entry) => entry.key,
|
|
47
|
+
);
|
|
48
|
+
if (keys.length === 0) return false;
|
|
49
|
+
const currentValue = widget.options.externalStatusKey;
|
|
50
|
+
const current = typeof currentValue === "string" ? currentValue : "";
|
|
51
|
+
const currentIndex = keys.indexOf(current);
|
|
52
|
+
const nextIndex =
|
|
53
|
+
currentIndex === -1
|
|
54
|
+
? delta > 0
|
|
55
|
+
? 0
|
|
56
|
+
: keys.length - 1
|
|
57
|
+
: (currentIndex + delta + keys.length) % keys.length;
|
|
58
|
+
widget.update({ externalStatusKey: keys[nextIndex] ?? current });
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
2
|
+
|
|
3
|
+
import { STATUS_KEY } from "../config.js";
|
|
4
|
+
import {
|
|
5
|
+
allExtensionStatusEntries,
|
|
6
|
+
toggleExtensionStatusRowKey,
|
|
7
|
+
type GetExtensionStatuses,
|
|
8
|
+
} from "../extension-statuses.js";
|
|
9
|
+
import type { StatuslineSettings } from "../types.js";
|
|
10
|
+
|
|
11
|
+
export function extensionStatusRowLines(
|
|
12
|
+
config: StatuslineSettings,
|
|
13
|
+
getExtensionStatuses: GetExtensionStatuses,
|
|
14
|
+
selected: number,
|
|
15
|
+
width: number,
|
|
16
|
+
menuTitle: (title: string, subtitle: string) => string,
|
|
17
|
+
line: (content: string, width: number) => string,
|
|
18
|
+
menuLine: (selected: boolean, content: string, width: number) => string,
|
|
19
|
+
dim: (text: string) => string,
|
|
20
|
+
success: (text: string) => string,
|
|
21
|
+
warning: (text: string) => string,
|
|
22
|
+
): string[] {
|
|
23
|
+
const entries = allExtensionStatusEntries(
|
|
24
|
+
getExtensionStatuses(),
|
|
25
|
+
config.extensionStatusRow,
|
|
26
|
+
STATUS_KEY,
|
|
27
|
+
);
|
|
28
|
+
const hidden = new Set(config.extensionStatusRow.hiddenKeys);
|
|
29
|
+
const lines = [
|
|
30
|
+
line(
|
|
31
|
+
menuTitle("Pi extensions", "Published statuses and extension status row visibility"),
|
|
32
|
+
width,
|
|
33
|
+
),
|
|
34
|
+
line(dim("↑/↓ select • pgup/pgdn jump • ←/→ or enter toggle • esc back"), width),
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
if (entries.length === 0) {
|
|
38
|
+
lines.push(line(warning("No extension statuses are currently available."), width));
|
|
39
|
+
return lines;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
entries.forEach((entry, index) => {
|
|
43
|
+
const state = hidden.has(entry.key) ? dim("off") : success("on ");
|
|
44
|
+
const key = dim(entry.key);
|
|
45
|
+
const maxValueWidth = Math.max(1, width - entry.key.length - 8);
|
|
46
|
+
const value = truncateToWidth(entry.value, maxValueWidth, "…");
|
|
47
|
+
lines.push(menuLine(index === selected, `${state} ${key} ${value}`, width));
|
|
48
|
+
});
|
|
49
|
+
return lines;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function toggleExtensionStatusRowSelection(
|
|
53
|
+
config: StatuslineSettings,
|
|
54
|
+
getExtensionStatuses: GetExtensionStatuses,
|
|
55
|
+
selected: number,
|
|
56
|
+
): boolean {
|
|
57
|
+
const entry = allExtensionStatusEntries(
|
|
58
|
+
getExtensionStatuses(),
|
|
59
|
+
config.extensionStatusRow,
|
|
60
|
+
STATUS_KEY,
|
|
61
|
+
)[selected];
|
|
62
|
+
if (!entry) return false;
|
|
63
|
+
config.extensionStatusRow = toggleExtensionStatusRowKey(config.extensionStatusRow, entry.key);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function extensionStatusRowCount(
|
|
68
|
+
config: StatuslineSettings,
|
|
69
|
+
getExtensionStatuses: GetExtensionStatuses,
|
|
70
|
+
): number {
|
|
71
|
+
return allExtensionStatusEntries(getExtensionStatuses(), config.extensionStatusRow, STATUS_KEY)
|
|
72
|
+
.length;
|
|
73
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { colorDisplayName, normalizeColor } from "../colors.js";
|
|
2
|
+
import type { WidgetOptions } from "../types.js";
|
|
3
|
+
import { registry } from "../widgets/registry.js";
|
|
4
|
+
import type { Widget, WidgetProperty } from "../widgets/types.js";
|
|
5
|
+
import type { ColorOptionField, OptionField } from "./model.js";
|
|
6
|
+
|
|
7
|
+
export function fieldsForWidget(widget: Widget): OptionField[] {
|
|
8
|
+
const spec = registry.spec(widget.type);
|
|
9
|
+
const properties: readonly WidgetProperty[] = spec.properties;
|
|
10
|
+
const fields: OptionField[] = [{ id: "enabled", label: "Enabled", kind: "boolean" }];
|
|
11
|
+
|
|
12
|
+
for (const option of spec.baseOptions) {
|
|
13
|
+
if (option === "raw") fields.push({ id: "raw", label: "Raw value only", kind: "boolean" });
|
|
14
|
+
if (option === "hideWhenEmpty")
|
|
15
|
+
fields.push({ id: "hideWhenEmpty", label: "Hide when empty", kind: "boolean" });
|
|
16
|
+
if (option === "hideWhenZero")
|
|
17
|
+
fields.push({ id: "hideWhenZero", label: "Hide when zero", kind: "boolean" });
|
|
18
|
+
if (option === "icon") fields.push({ id: "icon", label: "Custom icon", kind: "text" });
|
|
19
|
+
if (option === "text" && !widget.options.hideWhenEmpty)
|
|
20
|
+
fields.push({ id: "text", label: "Text when empty", kind: "text" });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
for (const property of properties) {
|
|
24
|
+
if (property.options?.showInFields === false) continue;
|
|
25
|
+
if (!isMetadataPropertyVisible(property, widget.options)) continue;
|
|
26
|
+
fields.push(metadataField(property));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return fields;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function colorFields(widget?: Widget): ColorOptionField[] {
|
|
33
|
+
const fields: ColorOptionField[] = [
|
|
34
|
+
{ id: "fg", label: "Foreground", kind: "color" },
|
|
35
|
+
{ id: "bg", label: "Background", kind: "color" },
|
|
36
|
+
{ id: "bold", label: "Bold", kind: "boolean" },
|
|
37
|
+
{ id: "fgAnsi", label: "Custom ANSI256 foreground", kind: "ansi" },
|
|
38
|
+
{ id: "bgAnsi", label: "Custom ANSI256 background", kind: "ansi" },
|
|
39
|
+
];
|
|
40
|
+
if (
|
|
41
|
+
widget &&
|
|
42
|
+
registry
|
|
43
|
+
.spec(widget.type)
|
|
44
|
+
.properties.find((property) => property.id === "contextConditionalColors") &&
|
|
45
|
+
widget.options.contextConditionalColors
|
|
46
|
+
) {
|
|
47
|
+
fields.push(
|
|
48
|
+
{ id: "warningFg", label: "Warning foreground", kind: "color" },
|
|
49
|
+
{ id: "warningBg", label: "Warning background", kind: "color" },
|
|
50
|
+
{ id: "warningFgAnsi", label: "Custom ANSI256 warning foreground", kind: "ansi" },
|
|
51
|
+
{ id: "warningBgAnsi", label: "Custom ANSI256 warning background", kind: "ansi" },
|
|
52
|
+
{ id: "dangerFg", label: "Danger foreground", kind: "color" },
|
|
53
|
+
{ id: "dangerBg", label: "Danger background", kind: "color" },
|
|
54
|
+
{ id: "dangerFgAnsi", label: "Custom ANSI256 danger foreground", kind: "ansi" },
|
|
55
|
+
{ id: "dangerBgAnsi", label: "Custom ANSI256 danger background", kind: "ansi" },
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return fields;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function fieldValue(widget: Widget, field: OptionField): string {
|
|
62
|
+
if (field.id === "enabled") return widget.enabled ? "on" : "off";
|
|
63
|
+
if (field.kind === "boolean") return getBooleanField(widget, field.id) ? "on" : "off";
|
|
64
|
+
if (field.kind === "number") return String(getNumberField(widget, field.id) ?? "");
|
|
65
|
+
if (field.kind === "choice") {
|
|
66
|
+
const value = getTextField(widget, field.id);
|
|
67
|
+
return field.choices?.find((choice) => choice.id === value)?.label ?? value;
|
|
68
|
+
}
|
|
69
|
+
return getTextField(widget, field.id) || "(empty)";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function colorFieldValue(widget: Widget, field: ColorOptionField): string {
|
|
73
|
+
if (field.id === "bold") return widget.options.bold ? "on" : "off";
|
|
74
|
+
if (field.id === "fg") return colorDisplayName(widget.options.fg);
|
|
75
|
+
if (field.id === "bg") return colorDisplayName(widget.options.bg);
|
|
76
|
+
if (field.id === "warningFg") return colorDisplayName(colorOption(widget, "warningFg"));
|
|
77
|
+
if (field.id === "warningBg") return colorDisplayName(colorOption(widget, "warningBg"));
|
|
78
|
+
if (field.id === "dangerFg") return colorDisplayName(colorOption(widget, "dangerFg"));
|
|
79
|
+
if (field.id === "dangerBg") return colorDisplayName(colorOption(widget, "dangerBg"));
|
|
80
|
+
if (field.id === "fgAnsi")
|
|
81
|
+
return widget.options.fg?.startsWith("ansi256:") ? widget.options.fg.slice(8) : "0";
|
|
82
|
+
if (field.id === "bgAnsi")
|
|
83
|
+
return widget.options.bg?.startsWith("ansi256:") ? widget.options.bg.slice(8) : "0";
|
|
84
|
+
if (field.id === "warningFgAnsi") {
|
|
85
|
+
const color = colorOption(widget, "warningFg");
|
|
86
|
+
return color?.startsWith("ansi256:") ? color.slice(8) : "0";
|
|
87
|
+
}
|
|
88
|
+
if (field.id === "warningBgAnsi") {
|
|
89
|
+
const color = colorOption(widget, "warningBg");
|
|
90
|
+
return color?.startsWith("ansi256:") ? color.slice(8) : "0";
|
|
91
|
+
}
|
|
92
|
+
if (field.id === "dangerFgAnsi") {
|
|
93
|
+
const color = colorOption(widget, "dangerFg");
|
|
94
|
+
return color?.startsWith("ansi256:") ? color.slice(8) : "0";
|
|
95
|
+
}
|
|
96
|
+
if (field.id === "dangerBgAnsi") {
|
|
97
|
+
const color = colorOption(widget, "dangerBg");
|
|
98
|
+
return color?.startsWith("ansi256:") ? color.slice(8) : "0";
|
|
99
|
+
}
|
|
100
|
+
return "";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function getBooleanField(widget: Widget, id: string): boolean {
|
|
104
|
+
const spec = registry.spec(widget.type);
|
|
105
|
+
const property = spec.properties.find((item) => item.id === id);
|
|
106
|
+
if (property?.kind === "boolean") return Boolean(widget.options[id] ?? property.default);
|
|
107
|
+
if (spec.baseOptions.some((option) => option === id)) {
|
|
108
|
+
if (id === "raw") return widget.options.raw ?? false;
|
|
109
|
+
if (id === "hideWhenEmpty") return widget.options.hideWhenEmpty ?? false;
|
|
110
|
+
if (id === "hideWhenZero") return widget.options.hideWhenZero ?? false;
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function getNumberField(widget: Widget, id: string): number | undefined {
|
|
116
|
+
const property = registry.spec(widget.type).properties.find((item) => item.id === id);
|
|
117
|
+
if (property?.kind === "number") {
|
|
118
|
+
const value = widget.options[id];
|
|
119
|
+
return typeof value === "number" ? value : Number(property.default);
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function getTextField(widget: Widget, id: string): string {
|
|
125
|
+
const spec = registry.spec(widget.type);
|
|
126
|
+
const property = spec.properties.find((item) => item.id === id);
|
|
127
|
+
if (property && (property.kind === "text" || property.kind === "choice")) {
|
|
128
|
+
const value = widget.options[id];
|
|
129
|
+
return typeof value === "string" ? value : String(property.default);
|
|
130
|
+
}
|
|
131
|
+
if (spec.baseOptions.some((option) => option === id)) {
|
|
132
|
+
if (id === "icon") return widget.options.icon ?? "";
|
|
133
|
+
if (id === "text") return widget.options.text ?? "";
|
|
134
|
+
}
|
|
135
|
+
return "";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function formatWidgetOptions(widget: Widget): string {
|
|
139
|
+
const spec = registry.spec(widget.type);
|
|
140
|
+
const properties: readonly WidgetProperty[] = spec.properties;
|
|
141
|
+
const parts = baseSummaryParts(widget);
|
|
142
|
+
if (spec.baseOptions.some((option) => option === "hideWhenZero") && widget.options.hideWhenZero)
|
|
143
|
+
parts.push("hide-zero");
|
|
144
|
+
if (
|
|
145
|
+
spec.baseOptions.some((option) => option === "text") &&
|
|
146
|
+
!widget.options.hideWhenEmpty &&
|
|
147
|
+
widget.options.text
|
|
148
|
+
)
|
|
149
|
+
parts.push(`text='${widget.options.text}'`);
|
|
150
|
+
for (const property of properties) {
|
|
151
|
+
if (!isMetadataPropertyVisible(property, widget.options)) continue;
|
|
152
|
+
if (property.options?.showInWidgets === false) continue;
|
|
153
|
+
addMetadataSummaryPart(parts, widget, property);
|
|
154
|
+
}
|
|
155
|
+
return parts.join(" • ");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function formatWidgetColorOptions(widget: Widget): string {
|
|
159
|
+
const summary = formatMetadataWidgetColorOptions(widget);
|
|
160
|
+
const colorSummary = formatColorStyleSummary(widget);
|
|
161
|
+
if (!summary) return colorSummary;
|
|
162
|
+
if (!colorSummary) return summary;
|
|
163
|
+
return `${summary} • ${colorSummary}`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function isMetadataPropertyVisible(
|
|
167
|
+
property: WidgetProperty,
|
|
168
|
+
options: WidgetOptions,
|
|
169
|
+
): boolean {
|
|
170
|
+
const condition = property.showWhen;
|
|
171
|
+
if (!condition) return true;
|
|
172
|
+
return options[condition.property] === condition.equals;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function colorOption(widget: Widget, key: string) {
|
|
176
|
+
return normalizeColor(widget.options[key]);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function formatMetadataWidgetColorOptions(widget: Widget): string {
|
|
180
|
+
const spec = registry.spec(widget.type);
|
|
181
|
+
const properties: readonly WidgetProperty[] = spec.properties;
|
|
182
|
+
const parts = baseSummaryParts(widget);
|
|
183
|
+
if (
|
|
184
|
+
spec.baseOptions.some((option) => option === "text") &&
|
|
185
|
+
!widget.options.hideWhenEmpty &&
|
|
186
|
+
widget.options.text
|
|
187
|
+
)
|
|
188
|
+
parts.push(`text='${widget.options.text}'`);
|
|
189
|
+
for (const property of properties) {
|
|
190
|
+
if (!isMetadataPropertyVisible(property, widget.options)) continue;
|
|
191
|
+
if (!property.options?.showInColors) continue;
|
|
192
|
+
addMetadataSummaryPart(parts, widget, property);
|
|
193
|
+
}
|
|
194
|
+
return parts.join(" • ");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function metadataField(property: WidgetProperty): OptionField {
|
|
198
|
+
const field: OptionField = {
|
|
199
|
+
id: property.id,
|
|
200
|
+
label: property.label,
|
|
201
|
+
kind: property.kind,
|
|
202
|
+
};
|
|
203
|
+
if (property.options?.min !== undefined) field.min = property.options.min;
|
|
204
|
+
if (property.options?.max !== undefined) field.max = property.options.max;
|
|
205
|
+
if (property.options?.choices !== undefined) field.choices = property.options.choices;
|
|
206
|
+
if (property.options?.editAction !== undefined) field.editAction = property.options.editAction;
|
|
207
|
+
return field;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function addMetadataSummaryPart(parts: string[], widget: Widget, property: WidgetProperty): void {
|
|
211
|
+
const value = widget.options[property.id] ?? property.default;
|
|
212
|
+
if (property.kind === "boolean") {
|
|
213
|
+
if (value === true) parts.push(property.options?.label ?? property.id);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (property.kind === "choice") {
|
|
217
|
+
if (value === property.default) return;
|
|
218
|
+
const choice = property.options?.choices?.find((item) => item.id === value);
|
|
219
|
+
const label = choice?.list ?? String(value);
|
|
220
|
+
const prefix = property.options?.listProperty || property.id;
|
|
221
|
+
if (prefix) parts.push(`${prefix}=${label}`);
|
|
222
|
+
else parts.push(label);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (property.kind === "number" || property.kind === "text") {
|
|
226
|
+
const prefix = property.options?.listProperty || property.id;
|
|
227
|
+
const renderedValue = property.options?.quoteValue ? `'${String(value)}'` : String(value);
|
|
228
|
+
if (value !== undefined && value !== "") parts.push(`${prefix}=${renderedValue}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function baseSummaryParts(widget: Widget): string[] {
|
|
233
|
+
const { options } = widget;
|
|
234
|
+
const spec = registry.spec(widget.type);
|
|
235
|
+
const parts: string[] = [];
|
|
236
|
+
if (spec.baseOptions.some((option) => option === "raw") && options.raw) parts.push("raw");
|
|
237
|
+
if (spec.baseOptions.some((option) => option === "hideWhenEmpty") && options.hideWhenEmpty)
|
|
238
|
+
parts.push("hide-empty");
|
|
239
|
+
if (spec.baseOptions.some((option) => option === "icon") && options.icon)
|
|
240
|
+
parts.push(`icon='${options.icon}'`);
|
|
241
|
+
return parts;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function formatColorStyleSummary(widget: Widget): string {
|
|
245
|
+
const { options } = widget;
|
|
246
|
+
const defaultFg = registry.spec(widget.type).defaultStyle.fg ?? "default";
|
|
247
|
+
const parts: string[] = [];
|
|
248
|
+
if (options.fg && options.fg !== defaultFg) parts.push(`fg=${colorDisplayName(options.fg)}`);
|
|
249
|
+
if (options.bg && options.bg !== "default") parts.push(`bg=${colorDisplayName(options.bg)}`);
|
|
250
|
+
if (options.bold) parts.push("bold");
|
|
251
|
+
return parts.join(" • ");
|
|
252
|
+
}
|