@narumitw/pi-btw 0.42.0 → 0.42.1
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/package.json +9 -1
- package/src/bring-to-main.ts +0 -238
- package/src/btw.ts +108 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@narumitw/pi-btw",
|
|
3
|
-
"version": "0.42.
|
|
3
|
+
"version": "0.42.1",
|
|
4
4
|
"description": "Pi extension that adds a /btw side-question command.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,5 +38,13 @@
|
|
|
38
38
|
"type": "git",
|
|
39
39
|
"url": "https://github.com/narumiruna/pi-extensions",
|
|
40
40
|
"directory": "extensions/pi-btw"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@narumitw/pi-tui-kit": "^0.42.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@earendil-works/pi-ai": "*",
|
|
47
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
48
|
+
"@earendil-works/pi-tui": "*"
|
|
41
49
|
}
|
|
42
50
|
}
|
package/src/bring-to-main.ts
CHANGED
|
@@ -47,11 +47,6 @@ export type BtwQuickBringToMainScope =
|
|
|
47
47
|
| { kind: "from"; answeredTurnIndex: number }
|
|
48
48
|
| { kind: "entire" };
|
|
49
49
|
|
|
50
|
-
export type BtwMenuSelectorAction =
|
|
51
|
-
| { kind: "select"; value: string }
|
|
52
|
-
| { kind: "back" }
|
|
53
|
-
| { kind: "close" };
|
|
54
|
-
|
|
55
50
|
export type BtwTextRangeSelectorAction =
|
|
56
51
|
| { kind: "confirm"; segments: BtwBringToMainSegment[] }
|
|
57
52
|
| { kind: "back" }
|
|
@@ -175,210 +170,6 @@ export function formatBtwBringToMain(segments: readonly BtwBringToMainSegment[])
|
|
|
175
170
|
].join("\n");
|
|
176
171
|
}
|
|
177
172
|
|
|
178
|
-
export type BtwBringToMainPreviewAction = { kind: "bring" } | { kind: "back" } | { kind: "close" };
|
|
179
|
-
|
|
180
|
-
export class BtwBringToMainPreview implements Component {
|
|
181
|
-
private readonly lines: string[];
|
|
182
|
-
private displayLines: string[];
|
|
183
|
-
private scrollOffset = 0;
|
|
184
|
-
private finished = false;
|
|
185
|
-
|
|
186
|
-
constructor(
|
|
187
|
-
private readonly tui: TUI,
|
|
188
|
-
private readonly theme: Theme,
|
|
189
|
-
private readonly keybindings: KeybindingsManager,
|
|
190
|
-
draft: string,
|
|
191
|
-
private readonly summary: BtwBringToMainSummary,
|
|
192
|
-
private readonly onAction: (action: BtwBringToMainPreviewAction) => void,
|
|
193
|
-
) {
|
|
194
|
-
this.lines = draft.split("\n").map(escapeTerminalControls);
|
|
195
|
-
this.displayLines = this.lines;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
render(width: number): string[] {
|
|
199
|
-
const safeWidth = Math.max(1, width);
|
|
200
|
-
this.displayLines = this.lines.flatMap((line) => wrapPreviewLine(line, safeWidth));
|
|
201
|
-
const availableRows = Math.max(1, this.tui.terminal.rows - RESERVED_APP_ROWS);
|
|
202
|
-
const showFooter = availableRows >= 3;
|
|
203
|
-
const viewportHeight = Math.max(1, availableRows - 1 - (showFooter ? 1 : 0));
|
|
204
|
-
this.clampScroll(viewportHeight);
|
|
205
|
-
const count = this.summary.messages === 1 ? "1 message" : `${this.summary.messages} messages`;
|
|
206
|
-
const lineCount = this.summary.lines === 1 ? "1 line" : `${this.summary.lines} lines`;
|
|
207
|
-
const firstVisible = Math.min(this.displayLines.length, this.scrollOffset + 1);
|
|
208
|
-
const lastVisible = Math.min(this.displayLines.length, this.scrollOffset + viewportHeight);
|
|
209
|
-
const scrollable = this.displayLines.length > viewportHeight;
|
|
210
|
-
const position = `${firstVisible}–${lastVisible}/${this.displayLines.length}`;
|
|
211
|
-
const header = `Preview${scrollable ? ` ${position}` : ""} · ${count} · ${lineCount} · ~${this.summary.tokens} tokens`;
|
|
212
|
-
const actions = `${confirmKeyLabel(this.keybindings)} bring • ${keybindingLabel(this.keybindings, "tui.select.cancel", ["ctrl+c"])} back • Ctrl+C close`;
|
|
213
|
-
const scroll = `${position} • ${keybindingLabel(this.keybindings, "tui.select.pageUp")}/${keybindingLabel(this.keybindings, "tui.select.pageDown")} scroll`;
|
|
214
|
-
const detailedFooter = scrollable ? `${scroll} • ${actions}` : actions;
|
|
215
|
-
const footer = visibleWidth(detailedFooter) <= safeWidth ? detailedFooter : actions;
|
|
216
|
-
return fitRows(
|
|
217
|
-
[
|
|
218
|
-
truncateToWidth(this.theme.fg("accent", this.theme.bold(header)), safeWidth, ""),
|
|
219
|
-
...this.displayLines.slice(this.scrollOffset, this.scrollOffset + viewportHeight),
|
|
220
|
-
...(showFooter ? [truncateToWidth(this.theme.fg("muted", footer), safeWidth, "")] : []),
|
|
221
|
-
],
|
|
222
|
-
availableRows,
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
handleInput(data: string): void {
|
|
227
|
-
if (this.finished) return;
|
|
228
|
-
if (matchesKey(data, Key.ctrl("c"))) {
|
|
229
|
-
this.finish({ kind: "close" });
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
if (this.keybindings.matches(data, "tui.select.cancel")) {
|
|
233
|
-
this.finish({ kind: "back" });
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
if (matchesConfirm(data, this.keybindings)) {
|
|
237
|
-
this.finish({ kind: "bring" });
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
if (this.keybindings.matches(data, "tui.select.pageUp")) {
|
|
241
|
-
this.scrollOffset -= Math.max(1, this.tui.terminal.rows - RESERVED_APP_ROWS - 2);
|
|
242
|
-
this.clampScroll(Math.max(0, this.tui.terminal.rows - RESERVED_APP_ROWS - 2));
|
|
243
|
-
this.tui.requestRender();
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
if (this.keybindings.matches(data, "tui.select.pageDown")) {
|
|
247
|
-
this.scrollOffset += Math.max(1, this.tui.terminal.rows - RESERVED_APP_ROWS - 2);
|
|
248
|
-
this.clampScroll(Math.max(0, this.tui.terminal.rows - RESERVED_APP_ROWS - 2));
|
|
249
|
-
this.tui.requestRender();
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
invalidate(): void {}
|
|
254
|
-
|
|
255
|
-
private clampScroll(viewportHeight: number): void {
|
|
256
|
-
this.scrollOffset = Math.max(
|
|
257
|
-
0,
|
|
258
|
-
Math.min(this.scrollOffset, Math.max(0, this.displayLines.length - viewportHeight)),
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
private finish(action: BtwBringToMainPreviewAction): void {
|
|
263
|
-
if (this.finished) return;
|
|
264
|
-
this.finished = true;
|
|
265
|
-
this.onAction(action);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
export class BtwMenuSelector implements Component {
|
|
270
|
-
private cursor = 0;
|
|
271
|
-
private scrollOffset = 0;
|
|
272
|
-
private finished = false;
|
|
273
|
-
|
|
274
|
-
constructor(
|
|
275
|
-
private readonly tui: TUI,
|
|
276
|
-
private readonly theme: Theme,
|
|
277
|
-
private readonly keybindings: KeybindingsManager,
|
|
278
|
-
private readonly title: string,
|
|
279
|
-
private readonly options: readonly string[],
|
|
280
|
-
private readonly onAction: (action: BtwMenuSelectorAction) => void,
|
|
281
|
-
initialValue?: string,
|
|
282
|
-
) {
|
|
283
|
-
const initialIndex = initialValue === undefined ? -1 : options.indexOf(initialValue);
|
|
284
|
-
if (initialIndex >= 0) this.cursor = initialIndex;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
render(width: number): string[] {
|
|
288
|
-
const safeWidth = Math.max(1, width);
|
|
289
|
-
const availableRows = Math.max(1, this.tui.terminal.rows - RESERVED_APP_ROWS);
|
|
290
|
-
const showFooter = availableRows >= 3;
|
|
291
|
-
const viewportHeight = Math.max(1, availableRows - 1 - (showFooter ? 1 : 0));
|
|
292
|
-
this.keepCursorVisible(viewportHeight);
|
|
293
|
-
const rows = this.options
|
|
294
|
-
.slice(this.scrollOffset, this.scrollOffset + viewportHeight)
|
|
295
|
-
.map((option, visibleIndex) => {
|
|
296
|
-
const index = this.scrollOffset + visibleIndex;
|
|
297
|
-
const raw = `${index === this.cursor ? ">" : " "} ${escapeTerminalControls(option)}`;
|
|
298
|
-
const toned = option.startsWith("⚠") ? this.theme.fg("warning", raw) : raw;
|
|
299
|
-
const styled =
|
|
300
|
-
index === this.cursor ? this.theme.bg("selectedBg", this.theme.fg("text", toned)) : toned;
|
|
301
|
-
return truncateToWidth(styled, safeWidth, "");
|
|
302
|
-
});
|
|
303
|
-
return fitRows(
|
|
304
|
-
[
|
|
305
|
-
truncateToWidth(
|
|
306
|
-
this.theme.fg("accent", this.theme.bold(escapeTerminalControls(this.title))),
|
|
307
|
-
safeWidth,
|
|
308
|
-
"",
|
|
309
|
-
),
|
|
310
|
-
...rows,
|
|
311
|
-
...(showFooter
|
|
312
|
-
? [
|
|
313
|
-
truncateToWidth(
|
|
314
|
-
this.theme.fg(
|
|
315
|
-
"muted",
|
|
316
|
-
`${confirmKeyLabel(this.keybindings)} confirm • ${keybindingLabel(this.keybindings, "tui.select.cancel", ["ctrl+c"])} back • Ctrl+C close`,
|
|
317
|
-
),
|
|
318
|
-
safeWidth,
|
|
319
|
-
"",
|
|
320
|
-
),
|
|
321
|
-
]
|
|
322
|
-
: []),
|
|
323
|
-
],
|
|
324
|
-
availableRows,
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
handleInput(data: string): void {
|
|
329
|
-
if (this.finished) return;
|
|
330
|
-
if (matchesKey(data, Key.ctrl("c"))) {
|
|
331
|
-
this.finish({ kind: "close" });
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
if (this.keybindings.matches(data, "tui.select.cancel")) {
|
|
335
|
-
this.finish({ kind: "back" });
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
if (matchesConfirm(data, this.keybindings)) {
|
|
339
|
-
const value = this.options[this.cursor];
|
|
340
|
-
if (value !== undefined) this.finish({ kind: "select", value });
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
if (this.keybindings.matches(data, "tui.select.up")) {
|
|
344
|
-
this.cursor = Math.max(0, this.cursor - 1);
|
|
345
|
-
this.tui.requestRender();
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
if (this.keybindings.matches(data, "tui.select.down")) {
|
|
349
|
-
this.cursor = Math.min(Math.max(0, this.options.length - 1), this.cursor + 1);
|
|
350
|
-
this.tui.requestRender();
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
if (this.keybindings.matches(data, "tui.select.pageUp")) {
|
|
354
|
-
this.cursor = Math.max(0, this.cursor - 10);
|
|
355
|
-
this.tui.requestRender();
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
if (this.keybindings.matches(data, "tui.select.pageDown")) {
|
|
359
|
-
this.cursor = Math.min(Math.max(0, this.options.length - 1), this.cursor + 10);
|
|
360
|
-
this.tui.requestRender();
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
invalidate(): void {}
|
|
366
|
-
|
|
367
|
-
private keepCursorVisible(height: number): void {
|
|
368
|
-
if (height <= 0) return;
|
|
369
|
-
if (this.cursor < this.scrollOffset) this.scrollOffset = this.cursor;
|
|
370
|
-
if (this.cursor >= this.scrollOffset + height) {
|
|
371
|
-
this.scrollOffset = this.cursor - height + 1;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
private finish(action: BtwMenuSelectorAction): void {
|
|
376
|
-
if (this.finished) return;
|
|
377
|
-
this.finished = true;
|
|
378
|
-
this.onAction(action);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
173
|
export class BtwTextRangeSelector implements Component {
|
|
383
174
|
private readonly lines: BtwSelectionLine[];
|
|
384
175
|
private cursor: BtwTextPosition = { line: 0, column: 0 };
|
|
@@ -788,35 +579,6 @@ function splitGraphemes(text: string): string[] {
|
|
|
788
579
|
return [...GRAPHEME_SEGMENTER.segment(text)].map(({ segment }) => segment);
|
|
789
580
|
}
|
|
790
581
|
|
|
791
|
-
function wrapPreviewLine(text: string, width: number): string[] {
|
|
792
|
-
if (!text) return [""];
|
|
793
|
-
const rows: string[] = [];
|
|
794
|
-
let row = "";
|
|
795
|
-
let rowWidth = 0;
|
|
796
|
-
const append = (value: string, valueWidth: number) => {
|
|
797
|
-
if (row && rowWidth + valueWidth > width) {
|
|
798
|
-
rows.push(row);
|
|
799
|
-
row = "";
|
|
800
|
-
rowWidth = 0;
|
|
801
|
-
}
|
|
802
|
-
row += value;
|
|
803
|
-
rowWidth += valueWidth;
|
|
804
|
-
};
|
|
805
|
-
for (const character of splitGraphemes(text)) {
|
|
806
|
-
const characterWidth = visibleWidth(character);
|
|
807
|
-
if (characterWidth <= width) {
|
|
808
|
-
append(character, characterWidth);
|
|
809
|
-
continue;
|
|
810
|
-
}
|
|
811
|
-
const codePoints = [...character]
|
|
812
|
-
.map((value) => `\\u{${value.codePointAt(0)?.toString(16) ?? "0"}}`)
|
|
813
|
-
.join("");
|
|
814
|
-
for (const value of codePoints) append(value, 1);
|
|
815
|
-
}
|
|
816
|
-
rows.push(row);
|
|
817
|
-
return rows;
|
|
818
|
-
}
|
|
819
|
-
|
|
820
582
|
function compareTextPositions(first: BtwTextPosition, second: BtwTextPosition): number {
|
|
821
583
|
return first.line === second.line ? first.column - second.column : first.line - second.line;
|
|
822
584
|
}
|
package/src/btw.ts
CHANGED
|
@@ -10,13 +10,10 @@ import {
|
|
|
10
10
|
type Theme,
|
|
11
11
|
} from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import type { Component, TUI } from "@earendil-works/pi-tui";
|
|
13
|
+
import { defineMenu, type MenuContext, type RunMenuResult, runMenu } from "@narumitw/pi-tui-kit";
|
|
13
14
|
import {
|
|
14
|
-
BtwBringToMainPreview,
|
|
15
|
-
type BtwBringToMainPreviewAction,
|
|
16
15
|
type BtwBringToMainSegment,
|
|
17
16
|
type BtwBringToMainSummary,
|
|
18
|
-
BtwMenuSelector,
|
|
19
|
-
type BtwMenuSelectorAction,
|
|
20
17
|
BtwTextRangeSelector,
|
|
21
18
|
type BtwTextRangeSelectorState,
|
|
22
19
|
buildQuickBringToMainSegments,
|
|
@@ -391,15 +388,19 @@ type BtwCustomFactory<T> = (
|
|
|
391
388
|
async function showBtwCustomPreservingEditor<T>(
|
|
392
389
|
ctx: ExtensionCommandContext,
|
|
393
390
|
factory: BtwCustomFactory<T>,
|
|
394
|
-
): Promise<T> {
|
|
391
|
+
): Promise<T | undefined> {
|
|
395
392
|
let liveEditorText = ctx.ui.getEditorText();
|
|
393
|
+
let completed = false;
|
|
396
394
|
const result = await ctx.ui.custom<T>((tui, theme, keybindings, done) =>
|
|
397
395
|
factory(tui, theme, keybindings, (value) => {
|
|
398
396
|
liveEditorText = ctx.ui.getEditorText();
|
|
397
|
+
completed = true;
|
|
399
398
|
done(value);
|
|
400
399
|
}),
|
|
401
400
|
);
|
|
402
|
-
if (ctx.ui.getEditorText() !== liveEditorText)
|
|
401
|
+
if (completed && ctx.ui.getEditorText() !== liveEditorText) {
|
|
402
|
+
ctx.ui.setEditorText(liveEditorText);
|
|
403
|
+
}
|
|
403
404
|
return result;
|
|
404
405
|
}
|
|
405
406
|
|
|
@@ -501,6 +502,7 @@ export async function chooseBringToMain(
|
|
|
501
502
|
return selector;
|
|
502
503
|
},
|
|
503
504
|
);
|
|
505
|
+
if (!selectedRange) return { kind: "closed" };
|
|
504
506
|
if (selectedRange.kind === "closed") return selectedRange;
|
|
505
507
|
if (selectedRange.kind === "back") break;
|
|
506
508
|
const preview = await showPreview(ctx, selectedRange.draft, selectedRange.summary);
|
|
@@ -518,16 +520,47 @@ export async function chooseBringToMain(
|
|
|
518
520
|
}
|
|
519
521
|
}
|
|
520
522
|
|
|
523
|
+
type BtwMenuSelectorAction =
|
|
524
|
+
| { kind: "select"; value: string }
|
|
525
|
+
| { kind: "back" }
|
|
526
|
+
| { kind: "close" };
|
|
527
|
+
|
|
528
|
+
type BtwBringToMainPreviewAction = { kind: "bring" } | { kind: "back" } | { kind: "close" };
|
|
529
|
+
|
|
521
530
|
async function showBringToMainPreview(
|
|
522
531
|
ctx: ExtensionCommandContext,
|
|
523
532
|
draft: string,
|
|
524
533
|
summary: BtwBringToMainSummary,
|
|
525
534
|
): Promise<BtwBringToMainPreviewAction> {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
535
|
+
let confirmed = false;
|
|
536
|
+
const count = summary.messages === 1 ? "1 message" : `${summary.messages} messages`;
|
|
537
|
+
const lineCount = summary.lines === 1 ? "1 line" : `${summary.lines} lines`;
|
|
538
|
+
const menu = defineMenu<void, "preview", "bring", MenuContext>({
|
|
539
|
+
start: "preview",
|
|
540
|
+
screens: {
|
|
541
|
+
preview: () => ({
|
|
542
|
+
kind: "review",
|
|
543
|
+
title: `Preview · ${count} · ${lineCount} · ~${summary.tokens} tokens`,
|
|
544
|
+
content: draft,
|
|
545
|
+
viewportSize: "adaptive",
|
|
546
|
+
hint: "back",
|
|
547
|
+
confirm: { id: "bring", label: "Bring", action: "bring" },
|
|
548
|
+
}),
|
|
549
|
+
},
|
|
550
|
+
actions: {
|
|
551
|
+
bring: async () => {
|
|
552
|
+
confirmed = true;
|
|
553
|
+
return { kind: "close" } as const;
|
|
554
|
+
},
|
|
555
|
+
},
|
|
556
|
+
});
|
|
557
|
+
const result = await runBtwMenuPreservingEditor(ctx, (menuContext) =>
|
|
558
|
+
runMenu(menuContext, menu, { getState: () => undefined }),
|
|
530
559
|
);
|
|
560
|
+
if (confirmed && result.kind === "closed" && result.reason === "close") {
|
|
561
|
+
return { kind: "bring" };
|
|
562
|
+
}
|
|
563
|
+
return terminalBtwMenuAction(result);
|
|
531
564
|
}
|
|
532
565
|
|
|
533
566
|
async function showBtwMenu(
|
|
@@ -536,11 +569,72 @@ async function showBtwMenu(
|
|
|
536
569
|
options: readonly string[],
|
|
537
570
|
initialValue?: string,
|
|
538
571
|
): Promise<BtwMenuSelectorAction> {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
572
|
+
const items = options.map((label, index) => ({ id: `option-${index}`, label }));
|
|
573
|
+
const initialIndex = initialValue === undefined ? -1 : options.indexOf(initialValue);
|
|
574
|
+
let selectedValue: string | undefined;
|
|
575
|
+
const menu = defineMenu<void, "choices", "select", MenuContext>({
|
|
576
|
+
start: "choices",
|
|
577
|
+
screens: {
|
|
578
|
+
choices: () => ({
|
|
579
|
+
kind: "choice",
|
|
580
|
+
title,
|
|
581
|
+
items,
|
|
582
|
+
action: "select",
|
|
583
|
+
initialItemId: initialIndex >= 0 ? `option-${initialIndex}` : undefined,
|
|
584
|
+
hint: "back",
|
|
585
|
+
}),
|
|
586
|
+
},
|
|
587
|
+
actions: {
|
|
588
|
+
select: async ({ itemId }: { itemId: string }) => {
|
|
589
|
+
const index = Number.parseInt(itemId.slice("option-".length), 10);
|
|
590
|
+
selectedValue = options[index];
|
|
591
|
+
return selectedValue === undefined
|
|
592
|
+
? ({ kind: "stay" } as const)
|
|
593
|
+
: ({ kind: "close" } as const);
|
|
594
|
+
},
|
|
595
|
+
},
|
|
596
|
+
});
|
|
597
|
+
const result = await runBtwMenuPreservingEditor(ctx, (menuContext) =>
|
|
598
|
+
runMenu(menuContext, menu, { getState: () => undefined }),
|
|
543
599
|
);
|
|
600
|
+
return selectedValue !== undefined && result.kind === "closed" && result.reason === "close"
|
|
601
|
+
? { kind: "select", value: selectedValue }
|
|
602
|
+
: terminalBtwMenuAction(result);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
async function runBtwMenuPreservingEditor(
|
|
606
|
+
ctx: ExtensionCommandContext,
|
|
607
|
+
run: (menuContext: MenuContext) => Promise<RunMenuResult>,
|
|
608
|
+
): Promise<RunMenuResult> {
|
|
609
|
+
let liveEditorText = ctx.ui.getEditorText();
|
|
610
|
+
let completed = false;
|
|
611
|
+
const ui = new Proxy(ctx.ui, {
|
|
612
|
+
get(target, property) {
|
|
613
|
+
if (property === "custom") {
|
|
614
|
+
return <Value>(factory: BtwCustomFactory<Value>) =>
|
|
615
|
+
target.custom<Value>((tui, theme, keybindings, done) =>
|
|
616
|
+
factory(tui, theme, keybindings, (value) => {
|
|
617
|
+
liveEditorText = target.getEditorText();
|
|
618
|
+
completed = true;
|
|
619
|
+
done(value);
|
|
620
|
+
}),
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
const value = Reflect.get(target, property, target) as unknown;
|
|
624
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
625
|
+
},
|
|
626
|
+
});
|
|
627
|
+
const result = await run({ mode: ctx.mode, hasUI: ctx.hasUI, ui });
|
|
628
|
+
if (result.kind !== "stale" && completed && ctx.ui.getEditorText() !== liveEditorText) {
|
|
629
|
+
ctx.ui.setEditorText(liveEditorText);
|
|
630
|
+
}
|
|
631
|
+
return result;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function terminalBtwMenuAction(result: RunMenuResult): { kind: "back" } | { kind: "close" } {
|
|
635
|
+
if (result.kind === "closed") return { kind: result.reason };
|
|
636
|
+
if (result.kind === "error") throw result.error;
|
|
637
|
+
return { kind: "close" };
|
|
544
638
|
}
|
|
545
639
|
|
|
546
640
|
export async function loadBringToMainDraft(
|