pi-ui-extend 0.1.13 → 0.1.17
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 +1 -1
- package/dist/app/app.d.ts +7 -0
- package/dist/app/app.js +102 -17
- package/dist/app/commands/command-controller.js +2 -0
- package/dist/app/commands/command-host.d.ts +5 -0
- package/dist/app/commands/command-model-actions.d.ts +2 -0
- package/dist/app/commands/command-model-actions.js +40 -4
- package/dist/app/commands/command-navigation-actions.d.ts +9 -0
- package/dist/app/commands/command-navigation-actions.js +62 -0
- package/dist/app/commands/command-registry.d.ts +2 -0
- package/dist/app/commands/command-registry.js +16 -0
- package/dist/app/constants.d.ts +0 -1
- package/dist/app/constants.js +0 -1
- package/dist/app/extensions/extension-ui-controller.d.ts +16 -5
- package/dist/app/extensions/extension-ui-controller.js +99 -61
- package/dist/app/icons.d.ts +1 -0
- package/dist/app/icons.js +2 -0
- package/dist/app/input/input-action-controller.d.ts +2 -0
- package/dist/app/input/input-action-controller.js +8 -1
- package/dist/app/logger.d.ts +25 -0
- package/dist/app/logger.js +90 -0
- package/dist/app/model/model-usage-status.js +30 -15
- package/dist/app/popup/menu-items-controller.d.ts +4 -0
- package/dist/app/popup/menu-items-controller.js +68 -6
- package/dist/app/popup/popup-action-controller.d.ts +2 -1
- package/dist/app/popup/popup-action-controller.js +7 -4
- package/dist/app/popup/popup-menu-controller.d.ts +36 -23
- package/dist/app/popup/popup-menu-controller.js +97 -326
- package/dist/app/rendering/conversation-entry-renderer.js +3 -3
- package/dist/app/rendering/conversation-viewport.d.ts +10 -2
- package/dist/app/rendering/conversation-viewport.js +157 -16
- package/dist/app/rendering/editor-panels.js +22 -9
- package/dist/app/rendering/popup-menu-renderer.d.ts +62 -0
- package/dist/app/rendering/popup-menu-renderer.js +405 -0
- package/dist/app/rendering/render-controller.js +30 -28
- package/dist/app/rendering/render-text.js +5 -2
- package/dist/app/rendering/status-line-renderer.d.ts +8 -1
- package/dist/app/rendering/status-line-renderer.js +217 -117
- package/dist/app/rendering/toast-controller.d.ts +12 -3
- package/dist/app/rendering/toast-controller.js +70 -12
- package/dist/app/runtime.d.ts +2 -1
- package/dist/app/runtime.js +20 -10
- package/dist/app/screen/mouse-controller.d.ts +2 -2
- package/dist/app/screen/mouse-controller.js +27 -48
- package/dist/app/screen/screen-styler.d.ts +1 -1
- package/dist/app/screen/screen-styler.js +9 -7
- package/dist/app/screen/scroll-controller.d.ts +12 -9
- package/dist/app/screen/scroll-controller.js +56 -45
- package/dist/app/screen/status-controller.js +2 -1
- package/dist/app/session/lazy-session-manager.d.ts +11 -0
- package/dist/app/session/lazy-session-manager.js +539 -0
- package/dist/app/session/pix-system-message.d.ts +16 -0
- package/dist/app/session/pix-system-message.js +64 -0
- package/dist/app/session/request-history.d.ts +4 -0
- package/dist/app/session/request-history.js +11 -0
- package/dist/app/session/session-event-controller.d.ts +11 -0
- package/dist/app/session/session-event-controller.js +58 -2
- package/dist/app/session/session-history.d.ts +18 -0
- package/dist/app/session/session-history.js +72 -3
- package/dist/app/session/session-lifecycle-controller.d.ts +6 -2
- package/dist/app/session/session-lifecycle-controller.js +7 -2
- package/dist/app/session/session-search.js +10 -0
- package/dist/app/session/tabs-controller.d.ts +17 -5
- package/dist/app/session/tabs-controller.js +308 -29
- package/dist/app/todo/todo-model.d.ts +4 -2
- package/dist/app/todo/todo-model.js +23 -13
- package/dist/app/types.d.ts +17 -6
- package/dist/app/workspace/workspace-actions-controller.d.ts +2 -0
- package/dist/app/workspace/workspace-actions-controller.js +12 -0
- package/dist/config.d.ts +6 -1
- package/dist/config.js +82 -25
- package/dist/default-pix-config.js +4 -0
- package/dist/fuzzy.d.ts +2 -0
- package/dist/fuzzy.js +27 -7
- package/dist/input-editor.d.ts +9 -0
- package/dist/input-editor.js +52 -0
- package/dist/schemas/pi-tools-suite-schema.d.ts +1 -0
- package/dist/schemas/pi-tools-suite-schema.js +1 -0
- package/dist/schemas/pix-schema.d.ts +3 -1
- package/dist/schemas/pix-schema.js +6 -4
- package/dist/terminal-width.d.ts +2 -0
- package/dist/terminal-width.js +64 -3
- package/dist/theme.js +6 -6
- package/dist/ui.d.ts +8 -0
- package/external/pi-tools-suite/README.md +3 -2
- package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +52 -8
- package/external/pi-tools-suite/src/antigravity-auth/commands.ts +3 -41
- package/external/pi-tools-suite/src/antigravity-auth/constants.ts +0 -2
- package/external/pi-tools-suite/src/antigravity-auth/index.ts +11 -18
- package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +129 -61
- package/external/pi-tools-suite/src/antigravity-auth/status.ts +82 -3
- package/external/pi-tools-suite/src/antigravity-auth/stream.ts +20 -7
- package/external/pi-tools-suite/src/antigravity-auth/types.ts +21 -0
- package/external/pi-tools-suite/src/config.ts +8 -0
- package/external/pi-tools-suite/src/dcp/index.ts +16 -1
- package/external/pi-tools-suite/src/dcp/state.ts +35 -0
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +3 -0
- package/external/pi-tools-suite/src/todo/index.ts +123 -14
- package/external/pi-tools-suite/src/todo/state/persistence.ts +0 -1
- package/external/pi-tools-suite/src/todo/state/state-reducer.ts +26 -43
- package/external/pi-tools-suite/src/todo/todo.ts +12 -23
- package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +34 -16
- package/external/pi-tools-suite/src/todo/tool/types.ts +7 -28
- package/external/pi-tools-suite/src/todo/view/format.ts +2 -3
- package/external/pi-tools-suite/src/tool-descriptions.ts +6 -4
- package/external/pi-tools-suite/src/usage/index.ts +5 -2
- package/external/pi-tools-suite/src/usage/lib/google.ts +53 -40
- package/external/pi-tools-suite/src/usage/lib/types.ts +12 -2
- package/package.json +1 -1
- package/schemas/pi-tools-suite.json +4 -0
- package/schemas/pix.json +11 -2
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import { colorLine } from "../../theme.js";
|
|
2
|
+
import { stringDisplayWidth } from "../../terminal-width.js";
|
|
3
|
+
import { resolveColor, resolveModelColor } from "../../config.js";
|
|
4
|
+
import { SLASH_COMMAND_DESCRIPTION_COLUMN, } from "../constants.js";
|
|
5
|
+
import { APP_ICONS } from "../icons.js";
|
|
6
|
+
import { ellipsizeDisplay, padOrTrimPlain, sanitizeText } from "./render-text.js";
|
|
7
|
+
import { modelProviderThemeColor, thinkingLevelThemeColor } from "./status-line-renderer.js";
|
|
8
|
+
const POPUP_MENU_ESCAPE_BUTTON = "Esc";
|
|
9
|
+
const POPUP_MENU_DESCRIPTION_GAP = " ";
|
|
10
|
+
const POPUP_MENU_HEADER_SIDE_PADDING = 2;
|
|
11
|
+
export class PopupMenuRenderer {
|
|
12
|
+
host;
|
|
13
|
+
constructor(host) {
|
|
14
|
+
this.host = host;
|
|
15
|
+
}
|
|
16
|
+
popupMenuWidth(columns) {
|
|
17
|
+
return columns;
|
|
18
|
+
}
|
|
19
|
+
popupMenuMargin(columns) {
|
|
20
|
+
return columns > 44 ? 2 : 0;
|
|
21
|
+
}
|
|
22
|
+
effectivePopupMenuWidth(columns) {
|
|
23
|
+
const sideMargin = this.popupMenuMargin(columns);
|
|
24
|
+
return Math.min(this.popupMenuWidth(columns), Math.max(1, columns - sideMargin * 2));
|
|
25
|
+
}
|
|
26
|
+
styleOverlayLine(row, line, width, activeMenu) {
|
|
27
|
+
const colors = this.host.theme.colors;
|
|
28
|
+
const margin = this.popupMenuMargin(width);
|
|
29
|
+
const menuWidth = this.effectivePopupMenuWidth(width);
|
|
30
|
+
const rightMargin = Math.max(0, width - margin - menuWidth);
|
|
31
|
+
const selected = line.target?.kind === "popup-menu" && activeMenu.selectedIndex === line.target.index;
|
|
32
|
+
const foreground = this.popupLineForeground(line);
|
|
33
|
+
const background = this.popupLineBackground(line, selected);
|
|
34
|
+
const plain = `${" ".repeat(margin)}${padOrTrimPlain(line.text, menuWidth)}${" ".repeat(rightMargin)}`;
|
|
35
|
+
if (this.host.screenStyler.selectionRangeForRow(row, width)) {
|
|
36
|
+
return this.host.screenStyler.styleLine(row, plain, width, { foreground, background });
|
|
37
|
+
}
|
|
38
|
+
return [
|
|
39
|
+
colorLine("", margin, { background: colors.background }),
|
|
40
|
+
line.segments && line.segments.length > 0
|
|
41
|
+
? this.host.screenStyler.styleLineSegments(row, line.text, menuWidth, { foreground, background, bold: selected }, line.segments)
|
|
42
|
+
: colorLine(line.text, menuWidth, { foreground, background, bold: selected }),
|
|
43
|
+
colorLine("", rightMargin, { background: colors.background }),
|
|
44
|
+
].join("");
|
|
45
|
+
}
|
|
46
|
+
overlayPlainText(line, width) {
|
|
47
|
+
const margin = this.popupMenuMargin(width);
|
|
48
|
+
const menuWidth = this.effectivePopupMenuWidth(width);
|
|
49
|
+
const rightMargin = Math.max(0, width - margin - menuWidth);
|
|
50
|
+
return `${" ".repeat(margin)}${padOrTrimPlain(line.text, menuWidth)}${" ".repeat(rightMargin)}`;
|
|
51
|
+
}
|
|
52
|
+
renderInlineUserMessageMenu(options, menu) {
|
|
53
|
+
const headerLine = options.userLine(formatPopupMenuHeader("Message actions", options.userContentWidth));
|
|
54
|
+
headerLine.target = { kind: "popup-menu-close" };
|
|
55
|
+
headerLine.segments = [{
|
|
56
|
+
start: options.userContentLeft,
|
|
57
|
+
end: options.userContentLeft + options.userContentWidth,
|
|
58
|
+
foreground: this.host.theme.colors.accent,
|
|
59
|
+
background: this.host.theme.colors.popupHeaderBackground,
|
|
60
|
+
bold: true,
|
|
61
|
+
}];
|
|
62
|
+
const lines = [headerLine];
|
|
63
|
+
for (const item of menu.visibleItems()) {
|
|
64
|
+
const label = item.label.padEnd(18, " ");
|
|
65
|
+
const description = item.description ?? "";
|
|
66
|
+
const marker = item.selected ? "▶" : " ";
|
|
67
|
+
const rawText = `${marker} ${label}${description}`;
|
|
68
|
+
const text = ellipsizeDisplay(rawText, options.userContentWidth);
|
|
69
|
+
const line = options.userLine(text);
|
|
70
|
+
line.target = { kind: "popup-menu", index: item.index };
|
|
71
|
+
const contentStart = options.userContentLeft;
|
|
72
|
+
const labelStart = contentStart + 2;
|
|
73
|
+
const labelEnd = Math.min(contentStart + text.length, labelStart + item.label.length);
|
|
74
|
+
const descriptionStart = contentStart + 2 + label.length;
|
|
75
|
+
line.segments = [
|
|
76
|
+
...(item.selected ? [{ start: contentStart, end: contentStart + 1, foreground: this.host.theme.colors.accent, bold: true }] : []),
|
|
77
|
+
{
|
|
78
|
+
start: labelStart,
|
|
79
|
+
end: labelEnd,
|
|
80
|
+
foreground: this.userMessageActionForeground(item.value),
|
|
81
|
+
bold: item.selected,
|
|
82
|
+
},
|
|
83
|
+
...(descriptionStart < contentStart + text.length
|
|
84
|
+
? [{ start: descriptionStart, end: contentStart + text.length, foreground: this.host.theme.colors.muted }]
|
|
85
|
+
: []),
|
|
86
|
+
];
|
|
87
|
+
lines.push(line);
|
|
88
|
+
}
|
|
89
|
+
return lines;
|
|
90
|
+
}
|
|
91
|
+
renderSlashCommandMenu(width, menu) {
|
|
92
|
+
const lines = [this.popupMenuHeader("Commands", width)];
|
|
93
|
+
const visibleItems = menu.visibleItems();
|
|
94
|
+
if (!this.hasPopupActionItems(menu.items)) {
|
|
95
|
+
lines.push({ text: " No matching slash commands", variant: "muted" });
|
|
96
|
+
}
|
|
97
|
+
for (const item of visibleItems) {
|
|
98
|
+
const marker = item.selected ? "▶ " : " ";
|
|
99
|
+
const text = `${marker}${this.labelDescriptionText(item.label, item.description, width - 2)}`;
|
|
100
|
+
lines.push({
|
|
101
|
+
text,
|
|
102
|
+
variant: "normal",
|
|
103
|
+
segments: this.itemHighlightSegments(item, text),
|
|
104
|
+
target: { kind: "popup-menu", index: item.index },
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return lines;
|
|
108
|
+
}
|
|
109
|
+
renderModelMenu(width, menu) {
|
|
110
|
+
const lines = [this.popupMenuHeader("Select model", width)];
|
|
111
|
+
const visibleItems = menu.visibleItems();
|
|
112
|
+
if (!this.hasPopupActionItems(menu.items)) {
|
|
113
|
+
lines.push({
|
|
114
|
+
text: this.host.session ? " No matching favorite models" : " Model menu unavailable",
|
|
115
|
+
variant: "muted",
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
for (const item of visibleItems) {
|
|
119
|
+
const marker = item.selected ? "▶ " : " ";
|
|
120
|
+
const text = `${marker}${this.labelDescriptionText(item.label, item.description, width - 2)}`;
|
|
121
|
+
lines.push({
|
|
122
|
+
text,
|
|
123
|
+
variant: this.selectableItemVariant(item.value),
|
|
124
|
+
segments: [...this.modelMenuItemSegments(item.value), ...this.itemHighlightSegments(item, text)],
|
|
125
|
+
target: { kind: "popup-menu", index: item.index },
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return lines;
|
|
129
|
+
}
|
|
130
|
+
renderThinkingMenu(width, menu) {
|
|
131
|
+
const lines = [this.popupMenuHeader("Thinking level", width)];
|
|
132
|
+
const visibleItems = menu.visibleItems();
|
|
133
|
+
if (!this.hasPopupActionItems(menu.items)) {
|
|
134
|
+
lines.push({ text: " No matching thinking levels", variant: "muted" });
|
|
135
|
+
}
|
|
136
|
+
for (const item of visibleItems) {
|
|
137
|
+
const marker = item.selected ? "▶ " : " ";
|
|
138
|
+
const text = `${marker}${this.labelDescriptionText(item.label, item.description, width - 2)}`;
|
|
139
|
+
lines.push({
|
|
140
|
+
text,
|
|
141
|
+
variant: this.selectableItemVariant(item.value),
|
|
142
|
+
segments: this.thinkingMenuItemSegments(item.value),
|
|
143
|
+
target: { kind: "popup-menu", index: item.index },
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return lines;
|
|
147
|
+
}
|
|
148
|
+
renderResumeMenu(width, menu, state) {
|
|
149
|
+
const title = this.host.resumeLoading ? `Resume session ${APP_ICONS.timerSand}` : "Resume session";
|
|
150
|
+
const lines = [this.popupMenuHeader(title, width)];
|
|
151
|
+
const visibleItems = menu.visibleItems();
|
|
152
|
+
if (!this.host.resumeLoading && !this.hasPopupActionItems(menu.items)) {
|
|
153
|
+
lines.push({
|
|
154
|
+
text: this.host.resumeSessionCount === 0 ? " No sessions found" : " No matching sessions",
|
|
155
|
+
variant: "muted",
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
for (const item of visibleItems) {
|
|
159
|
+
const label = item.label;
|
|
160
|
+
const description = item.description ?? "";
|
|
161
|
+
const marker = item.selected ? "▶ " : " ";
|
|
162
|
+
const text = `${marker}${label} ${description}`;
|
|
163
|
+
const segments = [...(this.resumeMenuItemSegments(item.value, label, description, text) ?? []), ...this.itemHighlightSegments(item, text)];
|
|
164
|
+
lines.push({
|
|
165
|
+
text,
|
|
166
|
+
variant: "normal",
|
|
167
|
+
...(segments.length === 0 ? {} : { segments }),
|
|
168
|
+
target: { kind: "popup-menu", index: item.index },
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (!state.allSessionsLoaded && state.loadedSessionCount > 0) {
|
|
172
|
+
lines.push({ text: ` Loaded ${state.loadedSessionCount} sessions · scroll for more`, variant: "muted" });
|
|
173
|
+
}
|
|
174
|
+
if (state.directQuery) {
|
|
175
|
+
lines.push({ text: ` Search: ${state.directQuery}`, variant: "muted" });
|
|
176
|
+
}
|
|
177
|
+
return lines;
|
|
178
|
+
}
|
|
179
|
+
renderUserMessageJumpMenu(width, menu, directQuery) {
|
|
180
|
+
const lines = [this.popupMenuHeader("Jump to user message", width)];
|
|
181
|
+
if (this.host.userMessageJumpLoading) {
|
|
182
|
+
lines.push({ text: ` ${APP_ICONS.timerSand} Loading user messages`, variant: "muted" });
|
|
183
|
+
}
|
|
184
|
+
else if (!this.hasPopupActionItems(menu.items)) {
|
|
185
|
+
lines.push({
|
|
186
|
+
text: this.host.entries.some((entry) => entry.kind === "user") ? " No matching user messages" : " No user messages yet",
|
|
187
|
+
variant: "muted",
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
const labelWidth = Math.max(1, width - 2);
|
|
191
|
+
for (const item of menu.visibleItems()) {
|
|
192
|
+
const label = ellipsizeDisplay(item.label, labelWidth);
|
|
193
|
+
const marker = item.selected ? "▶ " : " ";
|
|
194
|
+
const text = `${marker}${label}`;
|
|
195
|
+
lines.push({
|
|
196
|
+
text,
|
|
197
|
+
variant: "normal",
|
|
198
|
+
segments: this.itemHighlightSegments(item, text),
|
|
199
|
+
target: { kind: "popup-menu", index: item.index },
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (directQuery) {
|
|
203
|
+
lines.push({ text: ` Search: ${directQuery}`, variant: "muted" });
|
|
204
|
+
}
|
|
205
|
+
return lines;
|
|
206
|
+
}
|
|
207
|
+
renderQueueMessageMenu(width, menu) {
|
|
208
|
+
const lines = [this.popupMenuHeader("Queued message", width)];
|
|
209
|
+
for (const item of menu.visibleItems()) {
|
|
210
|
+
const marker = item.selected ? "▶ " : " ";
|
|
211
|
+
lines.push({
|
|
212
|
+
text: `${marker}${this.labelDescriptionText(item.label, item.description, width - 2, 16)}`,
|
|
213
|
+
variant: this.queueMessageItemVariant(item.value),
|
|
214
|
+
target: { kind: "popup-menu", index: item.index },
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return lines;
|
|
218
|
+
}
|
|
219
|
+
renderSdkMenu(width, menu, request, directQuery) {
|
|
220
|
+
const lines = [this.popupMenuHeader(request?.options.title ?? "Menu", width)];
|
|
221
|
+
if (!this.hasPopupActionItems(menu.items)) {
|
|
222
|
+
lines.push({ text: ` ${request?.options.emptyText ?? "No matching items"}`, variant: "muted" });
|
|
223
|
+
}
|
|
224
|
+
for (const item of menu.visibleItems()) {
|
|
225
|
+
const marker = item.selected ? "▶ " : " ";
|
|
226
|
+
const text = `${marker}${this.labelDescriptionText(item.label, item.description, width - 2)}`;
|
|
227
|
+
const segments = this.sdkMenuItemSegments(item, text);
|
|
228
|
+
lines.push({
|
|
229
|
+
text,
|
|
230
|
+
variant: this.sdkItemVariant(item.value),
|
|
231
|
+
...(segments.length === 0 ? {} : { segments }),
|
|
232
|
+
target: { kind: "popup-menu", index: item.index },
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
if (request?.options.searchable !== false && directQuery) {
|
|
236
|
+
lines.push({ text: ` ${request?.options.placeholder ?? "Search"}: ${directQuery}`, variant: "muted" });
|
|
237
|
+
}
|
|
238
|
+
return lines;
|
|
239
|
+
}
|
|
240
|
+
hasPopupActionItems(items) {
|
|
241
|
+
return items.length > 0;
|
|
242
|
+
}
|
|
243
|
+
labelDescriptionText(label, description, width, labelColumn = SLASH_COMMAND_DESCRIPTION_COLUMN) {
|
|
244
|
+
const safeLabel = sanitizeText(label).replace(/\s+/gu, " ");
|
|
245
|
+
const safeDescription = description ? sanitizeText(description).replace(/\s+/gu, " ") : "";
|
|
246
|
+
if (!safeDescription)
|
|
247
|
+
return ellipsizeDisplay(safeLabel, width);
|
|
248
|
+
const gapWidth = stringDisplayWidth(POPUP_MENU_DESCRIPTION_GAP);
|
|
249
|
+
const labelDisplayWidth = stringDisplayWidth(safeLabel);
|
|
250
|
+
const descriptionDisplayWidth = stringDisplayWidth(safeDescription);
|
|
251
|
+
if (width <= gapWidth + 1)
|
|
252
|
+
return ellipsizeDisplay(safeLabel, width);
|
|
253
|
+
if (labelDisplayWidth <= labelColumn && labelColumn + gapWidth + descriptionDisplayWidth <= width) {
|
|
254
|
+
return `${safeLabel}${" ".repeat(labelColumn - labelDisplayWidth)}${POPUP_MENU_DESCRIPTION_GAP}${safeDescription}`;
|
|
255
|
+
}
|
|
256
|
+
if (labelDisplayWidth + gapWidth + descriptionDisplayWidth <= width) {
|
|
257
|
+
return `${safeLabel}${POPUP_MENU_DESCRIPTION_GAP}${safeDescription}`;
|
|
258
|
+
}
|
|
259
|
+
const labelWidth = descriptionDisplayWidth < width - gapWidth - 1
|
|
260
|
+
? Math.max(1, width - gapWidth - descriptionDisplayWidth)
|
|
261
|
+
: Math.max(1, Math.min(labelColumn, width - gapWidth - 1));
|
|
262
|
+
const visibleLabel = ellipsizeDisplay(safeLabel, labelWidth);
|
|
263
|
+
const padding = " ".repeat(Math.max(0, labelWidth - stringDisplayWidth(visibleLabel)));
|
|
264
|
+
return `${visibleLabel}${padding}${POPUP_MENU_DESCRIPTION_GAP}${safeDescription}`;
|
|
265
|
+
}
|
|
266
|
+
userMessageActionForeground(value) {
|
|
267
|
+
if (value === "undo")
|
|
268
|
+
return this.host.theme.colors.error;
|
|
269
|
+
return this.host.theme.colors.inputForeground;
|
|
270
|
+
}
|
|
271
|
+
selectableItemVariant(value) {
|
|
272
|
+
return value.current ? "muted" : "normal";
|
|
273
|
+
}
|
|
274
|
+
thinkingMenuItemSegments(value) {
|
|
275
|
+
const markerOffset = 2; // "▶ " or " "
|
|
276
|
+
return [{
|
|
277
|
+
start: markerOffset,
|
|
278
|
+
end: markerOffset + value.level.length,
|
|
279
|
+
foreground: thinkingLevelThemeColor(value.level, this.host.theme.colors, this.availableThinkingLevels()),
|
|
280
|
+
}];
|
|
281
|
+
}
|
|
282
|
+
modelMenuItemSegments(value) {
|
|
283
|
+
const markerOffset = 2; // "▶ " or " "
|
|
284
|
+
return [{
|
|
285
|
+
start: markerOffset,
|
|
286
|
+
end: markerOffset + value.ref.length,
|
|
287
|
+
foreground: this.modelMenuItemColor(value),
|
|
288
|
+
}];
|
|
289
|
+
}
|
|
290
|
+
modelMenuItemColor(value) {
|
|
291
|
+
const configuredColor = this.host.modelColors
|
|
292
|
+
? resolveModelColor(value.ref, this.host.modelColors)
|
|
293
|
+
: undefined;
|
|
294
|
+
return configuredColor
|
|
295
|
+
? resolveColor(configuredColor, this.host.theme.colors)
|
|
296
|
+
: modelProviderThemeColor(value.model.provider, this.host.theme.colors);
|
|
297
|
+
}
|
|
298
|
+
availableThinkingLevels() {
|
|
299
|
+
const levels = this.host.session?.getAvailableThinkingLevels();
|
|
300
|
+
return Array.isArray(levels) && levels.length > 0 ? levels.map(String) : ["off", "minimal", "low", "medium", "high", "xhigh"];
|
|
301
|
+
}
|
|
302
|
+
queueMessageItemVariant(value) {
|
|
303
|
+
return value === "cancel" ? "error" : "normal";
|
|
304
|
+
}
|
|
305
|
+
sdkItemVariant(value) {
|
|
306
|
+
return value.variant ?? "normal";
|
|
307
|
+
}
|
|
308
|
+
sdkMenuItemSegments(item, text) {
|
|
309
|
+
return [
|
|
310
|
+
...this.highlightSegments(item.labelHighlightRanges ?? item.value.labelHighlightRanges ?? [], text, 2),
|
|
311
|
+
...this.descriptionHighlightSegments(item.description, item.descriptionHighlightRanges ?? item.value.descriptionHighlightRanges ?? [], text),
|
|
312
|
+
];
|
|
313
|
+
}
|
|
314
|
+
itemHighlightSegments(item, text) {
|
|
315
|
+
return this.highlightSegments(item.labelHighlightRanges ?? [], text, 2);
|
|
316
|
+
}
|
|
317
|
+
highlightSegments(ranges, text, markerOffset) {
|
|
318
|
+
if (ranges.length === 0)
|
|
319
|
+
return [];
|
|
320
|
+
return ranges.flatMap((range) => {
|
|
321
|
+
const start = Math.max(markerOffset, markerOffset + range.start);
|
|
322
|
+
const end = Math.min(text.length, markerOffset + range.end);
|
|
323
|
+
if (end <= start)
|
|
324
|
+
return [];
|
|
325
|
+
return [{
|
|
326
|
+
start,
|
|
327
|
+
end,
|
|
328
|
+
foreground: this.host.theme.colors.accent,
|
|
329
|
+
bold: true,
|
|
330
|
+
}];
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
descriptionHighlightSegments(description, ranges, text) {
|
|
334
|
+
if (!description || ranges.length === 0)
|
|
335
|
+
return [];
|
|
336
|
+
const safeDescription = sanitizeText(description).replace(/\s+/gu, " ");
|
|
337
|
+
const descriptionStart = text.indexOf(safeDescription, 2);
|
|
338
|
+
if (descriptionStart < 0)
|
|
339
|
+
return [];
|
|
340
|
+
return this.highlightSegments(ranges, text, descriptionStart);
|
|
341
|
+
}
|
|
342
|
+
resumeMenuItemSegments(value, label, description, text) {
|
|
343
|
+
if (value.kind !== "session")
|
|
344
|
+
return undefined;
|
|
345
|
+
const sessionLabel = value.session.name ?? value.session.firstMessage.slice(0, 50);
|
|
346
|
+
const markerOffset = 2; // "▶ " or " "
|
|
347
|
+
const sessionLabelStart = Math.max(0, label.length - sessionLabel.length) + markerOffset;
|
|
348
|
+
const muted = this.host.theme.colors.popupMuted;
|
|
349
|
+
const segments = [];
|
|
350
|
+
if (sessionLabelStart > markerOffset)
|
|
351
|
+
segments.push({ start: markerOffset, end: sessionLabelStart, foreground: muted });
|
|
352
|
+
if (description.length > 0)
|
|
353
|
+
segments.push({ start: markerOffset + label.length, end: text.length, foreground: muted });
|
|
354
|
+
return segments.length > 0 ? segments : undefined;
|
|
355
|
+
}
|
|
356
|
+
popupMenuHeader(title, width) {
|
|
357
|
+
return {
|
|
358
|
+
text: formatPopupMenuHeader(title, width),
|
|
359
|
+
variant: "accent",
|
|
360
|
+
backgroundOverride: this.host.theme.colors.popupHeaderBackground,
|
|
361
|
+
target: { kind: "popup-menu-close" },
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
popupLineForeground(line) {
|
|
365
|
+
const colors = this.host.theme.colors;
|
|
366
|
+
if (line.colorOverride)
|
|
367
|
+
return line.colorOverride;
|
|
368
|
+
switch (line.variant) {
|
|
369
|
+
case "accent":
|
|
370
|
+
return colors.accent;
|
|
371
|
+
case "muted":
|
|
372
|
+
return colors.popupMuted;
|
|
373
|
+
case "error":
|
|
374
|
+
return colors.error;
|
|
375
|
+
case "normal":
|
|
376
|
+
case undefined:
|
|
377
|
+
return colors.popupForeground;
|
|
378
|
+
}
|
|
379
|
+
return colors.popupForeground;
|
|
380
|
+
}
|
|
381
|
+
popupLineBackground(line, selected) {
|
|
382
|
+
const colors = this.host.theme.colors;
|
|
383
|
+
if (selected)
|
|
384
|
+
return colors.popupSelectedBackground;
|
|
385
|
+
return line.backgroundOverride ?? colors.popupBackground;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
export function formatPopupMenuHeader(title, width) {
|
|
389
|
+
const safeWidth = Math.max(1, width);
|
|
390
|
+
const sanitizedTitle = sanitizeText(title).replace(/\s+/g, " ").trim() || "Menu";
|
|
391
|
+
const buttonWidth = stringDisplayWidth(POPUP_MENU_ESCAPE_BUTTON);
|
|
392
|
+
if (safeWidth <= buttonWidth + 1)
|
|
393
|
+
return padOrTrimPlain(POPUP_MENU_ESCAPE_BUTTON, safeWidth);
|
|
394
|
+
const sidePadding = safeWidth >= buttonWidth + POPUP_MENU_HEADER_SIDE_PADDING * 2 + 2
|
|
395
|
+
? POPUP_MENU_HEADER_SIDE_PADDING
|
|
396
|
+
: 1;
|
|
397
|
+
const contentWidth = Math.max(1, safeWidth - sidePadding * 2);
|
|
398
|
+
if (contentWidth <= buttonWidth + 1) {
|
|
399
|
+
return padOrTrimPlain(`${" ".repeat(sidePadding)}${POPUP_MENU_ESCAPE_BUTTON}`, safeWidth);
|
|
400
|
+
}
|
|
401
|
+
const titleWidth = contentWidth - buttonWidth - 1;
|
|
402
|
+
const titleText = ellipsizeDisplay(sanitizedTitle, titleWidth);
|
|
403
|
+
const gapWidth = Math.max(1, contentWidth - stringDisplayWidth(titleText) - buttonWidth);
|
|
404
|
+
return `${" ".repeat(sidePadding)}${titleText}${" ".repeat(gapWidth)}${POPUP_MENU_ESCAPE_BUTTON}${" ".repeat(sidePadding)}`;
|
|
405
|
+
}
|
|
@@ -57,7 +57,6 @@ export class AppRenderController {
|
|
|
57
57
|
const underTabsOverlayStartRow = Math.min(rows, topReservedRows + 1);
|
|
58
58
|
const underTabsOverlayLines = menuLines.slice(0, Math.max(0, statusRow - underTabsOverlayStartRow));
|
|
59
59
|
const { lines: visible, metrics: scrollMetrics } = this.deps.scrollController.conversationView(columns, bodyHeight);
|
|
60
|
-
const scrollBar = this.deps.scrollController.scrollBarForMetrics(scrollMetrics);
|
|
61
60
|
const conversationColumns = Math.max(1, Math.min(columns, scrollMetrics.viewportColumns));
|
|
62
61
|
this.deps.mouseController.syncConversationSelectionForRender(scrollMetrics.start, bodyHeight, topReservedRows, conversationColumns);
|
|
63
62
|
this.deps.mouseController.renderedTargets.clear();
|
|
@@ -130,17 +129,6 @@ export class AppRenderController {
|
|
|
130
129
|
setRenderedBackground(row, rendered?.backgroundOverride);
|
|
131
130
|
appendFrameOutput("conversation", row, this.renderFrameRow(row, this.deps.screenStyler.styleBaseLine(row, rendered, conversationColumns)));
|
|
132
131
|
}
|
|
133
|
-
if (scrollBar && columns > 0) {
|
|
134
|
-
for (let layoutRow = 1; layoutRow <= bodyHeight; layoutRow += 1) {
|
|
135
|
-
const row = toScreenRow(layoutRow);
|
|
136
|
-
const isThumb = layoutRow >= scrollBar.thumbStartRow && layoutRow <= scrollBar.thumbEndRow;
|
|
137
|
-
const marker = isThumb ? " " : "│";
|
|
138
|
-
appendFrameOutput("conversation", row, `\x1b[${row};${columns}H${colorize(marker, {
|
|
139
|
-
foreground: this.deps.theme.colors.inputBorder,
|
|
140
|
-
...(isThumb ? { background: this.deps.theme.colors.inputBorder } : {}),
|
|
141
|
-
})}`);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
132
|
const aboveEditorStartRow = inputSeparatorRow + 1;
|
|
145
133
|
for (let index = 0; index < aboveEditorLines.length; index += 1) {
|
|
146
134
|
const rendered = frameRenderedLine(aboveEditorLines[index], columns, this.deps.theme, this.deps.screenStyler);
|
|
@@ -201,18 +189,26 @@ export class AppRenderController {
|
|
|
201
189
|
setRenderedBackground(row, rendered.line?.backgroundOverride);
|
|
202
190
|
appendFrameOutput("inputStatus", row, this.renderFrameRow(row, rendered.output(row)));
|
|
203
191
|
}
|
|
192
|
+
const statusLayout = this.deps.statusLineRenderer.layout(columns);
|
|
193
|
+
const statusLineRenderer = this.deps.statusLineRenderer;
|
|
194
|
+
const inputBorderWidgetsLayout = statusLineRenderer.inputBorderWidgetsLayout?.(columns);
|
|
204
195
|
if (inputBottomSeparatorRow > 1) {
|
|
205
196
|
const separatorText = inputFrameLine(columns, "bottom");
|
|
206
197
|
const row = toScreenRow(inputBottomSeparatorRow);
|
|
207
198
|
if (row < statusRow) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
199
|
+
const text = inputBorderWidgetsLayout
|
|
200
|
+
? overlayText(separatorText, inputBorderWidgetsLayout.inputBorderWidgetStartColumn ?? 1, inputBorderWidgetsLayout.text)
|
|
201
|
+
: separatorText;
|
|
202
|
+
this.deps.mouseController.renderedRowTexts.set(row, text);
|
|
203
|
+
const output = inputBorderWidgetsLayout && statusLineRenderer.renderInputBorderWidgets
|
|
204
|
+
? statusLineRenderer.renderInputBorderWidgets(row, inputBorderWidgetsLayout, separatorText, columns)
|
|
205
|
+
: this.deps.screenStyler.styleLine(row, separatorText, columns, {
|
|
206
|
+
foreground: this.deps.theme.colors.inputBorder,
|
|
207
|
+
});
|
|
208
|
+
appendFrameOutput("inputStatus", row, this.renderFrameRow(row, output));
|
|
212
209
|
}
|
|
213
210
|
}
|
|
214
|
-
|
|
215
|
-
this.updateStatusMouseState(statusLayout, statusRow);
|
|
211
|
+
this.updateStatusMouseState(statusLayout, statusRow, inputBorderWidgetsLayout, toScreenRow(inputBottomSeparatorRow));
|
|
216
212
|
appendFrameOutput("inputStatus", statusRow, this.renderFrameRow(statusRow, this.deps.statusLineRenderer.render(statusRow, statusLayout, columns)));
|
|
217
213
|
const voiceProgressOverlay = this.renderVoiceProgressOverlay(this.deps.voiceProgressOverlayText(), columns, statusRow);
|
|
218
214
|
if (voiceProgressOverlay) {
|
|
@@ -245,7 +241,7 @@ export class AppRenderController {
|
|
|
245
241
|
appendFrameOutput(regionForOverlayRow(row), row, this.renderFrameRow(row, this.deps.popupMenus.styleOverlayLine(row, line ?? { text: "" }, columns)));
|
|
246
242
|
}
|
|
247
243
|
}
|
|
248
|
-
for (const toastOverlay of renderToastOverlays(this.deps.toastController
|
|
244
|
+
for (const toastOverlay of renderToastOverlays(visibleToastStates(this.deps.toastController), columns, Math.max(0, statusRow - topReservedRows - 1), this.deps.theme)) {
|
|
249
245
|
const row = topReservedRows + toastOverlay.row;
|
|
250
246
|
const rowText = this.deps.mouseController.renderedRowTexts.get(row) ?? "";
|
|
251
247
|
if (toastOverlay.target)
|
|
@@ -290,20 +286,22 @@ export class AppRenderController {
|
|
|
290
286
|
const text = fixedCellText(flash.text, width);
|
|
291
287
|
return `\x1b[${flash.y};${flash.startColumn}H\x1b[7m${text}${ANSI_RESET}`;
|
|
292
288
|
}
|
|
293
|
-
updateStatusMouseState(statusLayout, statusRow) {
|
|
289
|
+
updateStatusMouseState(statusLayout, statusRow, inputBorderWidgetsLayout, inputBorderWidgetsRow) {
|
|
290
|
+
const widgetLayout = inputBorderWidgetsLayout;
|
|
291
|
+
const widgetRow = inputBorderWidgetsRow ?? statusRow;
|
|
294
292
|
this.deps.mouseController.statusModelTarget = this.deps.statusLineRenderer.modelTarget(statusLayout.text, statusRow);
|
|
295
293
|
this.deps.mouseController.statusThinkingTarget = this.deps.statusLineRenderer.thinkingTarget(statusLayout.text, statusRow);
|
|
296
294
|
this.deps.mouseController.statusContextTarget = this.deps.statusLineRenderer.contextTarget(statusLayout.text, statusRow, statusLayout);
|
|
297
295
|
this.deps.mouseController.statusModelUsageTarget = this.deps.statusLineRenderer.modelUsageTarget(statusLayout.text, statusRow, statusLayout);
|
|
298
|
-
this.deps.mouseController.statusDraftQueueTarget = this.deps.statusLineRenderer.draftQueueTarget?.(
|
|
299
|
-
this.deps.mouseController.statusUserJumpTarget = this.deps.statusLineRenderer.userJumpTarget?.(
|
|
300
|
-
this.deps.mouseController.statusThinkingExpandTarget = this.deps.statusLineRenderer.thinkingExpandTarget?.(
|
|
301
|
-
this.deps.mouseController.statusCompactToolsTarget = this.deps.statusLineRenderer.compactToolsTarget?.(
|
|
302
|
-
this.deps.mouseController.statusTerminalBellSoundTarget = this.deps.statusLineRenderer.terminalBellSoundTarget?.(
|
|
296
|
+
this.deps.mouseController.statusDraftQueueTarget = widgetLayout ? this.deps.statusLineRenderer.draftQueueTarget?.(widgetLayout, widgetRow) : undefined;
|
|
297
|
+
this.deps.mouseController.statusUserJumpTarget = widgetLayout ? this.deps.statusLineRenderer.userJumpTarget?.(widgetLayout, widgetRow) : undefined;
|
|
298
|
+
this.deps.mouseController.statusThinkingExpandTarget = widgetLayout ? this.deps.statusLineRenderer.thinkingExpandTarget?.(widgetLayout, widgetRow) : undefined;
|
|
299
|
+
this.deps.mouseController.statusCompactToolsTarget = widgetLayout ? this.deps.statusLineRenderer.compactToolsTarget?.(widgetLayout, widgetRow) : undefined;
|
|
300
|
+
this.deps.mouseController.statusTerminalBellSoundTarget = widgetLayout ? this.deps.statusLineRenderer.terminalBellSoundTarget?.(widgetLayout, widgetRow) : undefined;
|
|
303
301
|
this.deps.mouseController.statusSessionTarget = this.deps.statusLineRenderer.sessionTarget(statusLayout.text, statusRow, statusLayout.sessionLabel, statusLayout.workspaceLabel);
|
|
304
|
-
this.deps.mouseController.statusPromptEnhancerTarget = this.deps.statusLineRenderer.promptEnhancerTarget(
|
|
305
|
-
this.deps.mouseController.statusVoiceMicTarget = this.deps.statusLineRenderer.voiceMicTarget(
|
|
306
|
-
this.deps.mouseController.statusVoiceLanguageTarget = this.deps.statusLineRenderer.voiceLanguageTarget(
|
|
302
|
+
this.deps.mouseController.statusPromptEnhancerTarget = widgetLayout ? this.deps.statusLineRenderer.promptEnhancerTarget(widgetLayout, widgetRow) : undefined;
|
|
303
|
+
this.deps.mouseController.statusVoiceMicTarget = widgetLayout ? this.deps.statusLineRenderer.voiceMicTarget(widgetLayout, widgetRow) : undefined;
|
|
304
|
+
this.deps.mouseController.statusVoiceLanguageTarget = widgetLayout ? this.deps.statusLineRenderer.voiceLanguageTarget(widgetLayout, widgetRow) : undefined;
|
|
307
305
|
this.deps.mouseController.renderedRowTexts.set(statusRow, statusLayout.text);
|
|
308
306
|
}
|
|
309
307
|
renderVoiceProgressOverlay(message, width, rows) {
|
|
@@ -326,6 +324,10 @@ export class AppRenderController {
|
|
|
326
324
|
return { row: Math.min(2, rows - 1), text, output };
|
|
327
325
|
}
|
|
328
326
|
}
|
|
327
|
+
function visibleToastStates(toastController) {
|
|
328
|
+
const candidate = toastController;
|
|
329
|
+
return typeof candidate.visibleStates === "function" ? candidate.visibleStates() : candidate.toast?.visibleStates ?? [];
|
|
330
|
+
}
|
|
329
331
|
function inputFrameLine(width, edge) {
|
|
330
332
|
if (width <= 0)
|
|
331
333
|
return "";
|
|
@@ -21,9 +21,12 @@ export function shortHash(text) {
|
|
|
21
21
|
export function hasLspDiagnosticsAfterMutation(output) {
|
|
22
22
|
return /lsp\s+(?:errors?|warnings?|diagnostics?)\s+after\s+mutation/i.test(output) || /lsp\s+diagnostics\s*:/i.test(output);
|
|
23
23
|
}
|
|
24
|
-
const LSP_DIAGNOSTIC_MUTATION_TOOLS = new Set(["apply_patch", "ast_apply"]);
|
|
24
|
+
const LSP_DIAGNOSTIC_MUTATION_TOOLS = new Set(["apply_patch", "ast_apply", "edit", "write"]);
|
|
25
25
|
export function hasToolLspDiagnosticsAfterMutation(entry) {
|
|
26
|
-
return LSP_DIAGNOSTIC_MUTATION_TOOLS.has(entry.toolName
|
|
26
|
+
return LSP_DIAGNOSTIC_MUTATION_TOOLS.has(normalizedToolName(entry.toolName)) && hasLspDiagnosticsAfterMutation(entry.output);
|
|
27
|
+
}
|
|
28
|
+
function normalizedToolName(toolName) {
|
|
29
|
+
return toolName.split(/[.:/]/).filter(Boolean).at(-1)?.trim().toLowerCase() ?? toolName.toLowerCase();
|
|
27
30
|
}
|
|
28
31
|
export function lspDiagnosticSeverityForLine(line) {
|
|
29
32
|
const counts = lspDiagnosticCounts(line);
|
|
@@ -35,7 +35,10 @@ export declare class StatusLineRenderer {
|
|
|
35
35
|
private readonly host;
|
|
36
36
|
constructor(host: StatusLineRendererHost);
|
|
37
37
|
layout(width: number): StatusLineLayout;
|
|
38
|
+
inputBorderWidgetsLayout(width: number): StatusLineLayout | undefined;
|
|
38
39
|
render(row: number, layout: StatusLineLayout, width: number): string;
|
|
40
|
+
renderInputBorderWidgets(row: number, layout: StatusLineLayout, borderText: string, width: number): string;
|
|
41
|
+
private inputBorderWidgetSegments;
|
|
39
42
|
modelTarget(statusText: string, row: number): StatusModelTarget | undefined;
|
|
40
43
|
thinkingTarget(statusText: string, row: number): StatusThinkingTarget | undefined;
|
|
41
44
|
contextTarget(statusText: string, row: number, layout: StatusLineLayout): StatusContextTarget | undefined;
|
|
@@ -66,11 +69,15 @@ export declare class StatusLineRenderer {
|
|
|
66
69
|
private modelUsageProgressColor;
|
|
67
70
|
private modelProviderColor;
|
|
68
71
|
private thinkingLevelColor;
|
|
72
|
+
private statusThinkingDisplayLabel;
|
|
69
73
|
private availableThinkingLevels;
|
|
70
|
-
private thinkingRankColor;
|
|
71
74
|
private contextBarLabel;
|
|
72
75
|
private widgetLayout;
|
|
76
|
+
private iconButtonText;
|
|
77
|
+
private voiceBorderWidgetText;
|
|
73
78
|
private voiceWidgetLayout;
|
|
79
|
+
private voiceBorderWidgetParts;
|
|
74
80
|
private statusDotColor;
|
|
75
81
|
}
|
|
82
|
+
export declare function thinkingLevelThemeColor(label: string, colors: Theme["colors"], availableLevels?: readonly string[]): string;
|
|
76
83
|
export declare function modelProviderThemeColor(provider: string, colors: Theme["colors"]): string;
|