@oh-my-pi/pi-tui 18.13.2 → 18.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -54
- package/README.md +34 -34
- package/dist/types/app-keybindings.d.ts +15 -0
- package/dist/types/apps/session-picker.d.ts +5 -0
- package/dist/types/charts/chart-plan.d.ts +9 -7
- package/dist/types/chat/assistant-message.d.ts +15 -3
- package/dist/types/chat/bash-execution.d.ts +2 -0
- package/dist/types/chat/display-preferences.d.ts +2 -0
- package/dist/types/chat/extension-types.d.ts +31 -0
- package/dist/types/chat/reply-write.d.ts +48 -0
- package/dist/types/chat/table-chart.d.ts +4 -3
- package/dist/types/chat/tool-execution.d.ts +1 -2
- package/dist/types/chat/transcript-outline.d.ts +7 -0
- package/dist/types/chat/transcript-render-helpers.d.ts +2 -0
- package/dist/types/chrome/index.d.ts +1 -0
- package/dist/types/chrome/transcript-notice.d.ts +35 -0
- package/dist/types/chrome/transcript-status.d.ts +20 -8
- package/dist/types/components/editor.d.ts +8 -0
- package/dist/types/components/markdown.d.ts +7 -0
- package/dist/types/native/blobs.d.ts +2 -1
- package/dist/types/native/picker.d.ts +3 -3
- package/dist/types/overlays/annotation-overlay.d.ts +7 -1
- package/dist/types/overlays/annotation-types.d.ts +28 -0
- package/dist/types/overlays/login-dialog.d.ts +5 -1
- package/dist/types/overlays/model-browser.d.ts +10 -1
- package/dist/types/overlays/oauth-selector.d.ts +23 -0
- package/dist/types/overlays/plan-review-overlay.d.ts +2 -0
- package/dist/types/overlays/rewind-selector.d.ts +1 -1
- package/dist/types/overlays/session-selector.d.ts +22 -2
- package/dist/types/prompt/composer.d.ts +41 -4
- package/dist/types/prompt/interactive-context-helpers.d.ts +10 -3
- package/dist/types/prompt/welcome.d.ts +6 -2
- package/dist/types/render/extension-links.d.ts +65 -0
- package/dist/types/render/hyperlink.d.ts +25 -7
- package/dist/types/render/index.d.ts +1 -0
- package/dist/types/setup/scenes/model.d.ts +1 -1
- package/dist/types/setup/scenes/outro.d.ts +5 -4
- package/dist/types/setup/scenes/sign-in.d.ts +13 -8
- package/dist/types/setup/scenes/splash.d.ts +4 -4
- package/dist/types/setup/scenes/types.d.ts +16 -1
- package/dist/types/setup/startup-splash.d.ts +1 -1
- package/dist/types/setup/wizard-overlay.d.ts +11 -7
- package/dist/types/setup/wizard.d.ts +5 -1
- package/dist/types/status-line/component.d.ts +1 -1
- package/dist/types/status-line/context-usage.d.ts +28 -6
- package/dist/types/status-line/host.d.ts +6 -2
- package/dist/types/theme/theme.d.ts +0 -23
- package/dist/types/tools/bash-interactive.d.ts +6 -0
- package/dist/types/tools/bash.d.ts +2 -0
- package/dist/types/tools/default-renderer.d.ts +6 -4
- package/dist/types/tools/irc.d.ts +6 -0
- package/dist/types/tools/json-tree.d.ts +23 -4
- package/dist/types/tools/lsp.d.ts +2 -0
- package/dist/types/tools/mcp.d.ts +6 -5
- package/dist/types/tools/native-view.d.ts +1 -1
- package/dist/types/tools/renderer.d.ts +2 -0
- package/dist/types/tools/streaming-output.d.ts +11 -0
- package/dist/types/tools/task.d.ts +9 -1
- package/dist/types/tools/url-scheme-host.d.ts +17 -0
- package/dist/types/tools/yield.d.ts +1 -1
- package/dist/types/tui.d.ts +7 -2
- package/package.json +10 -15
- package/src/app-keybindings.ts +15 -0
- package/src/apps/session-picker.ts +7 -0
- package/src/charts/chart-plan.ts +20 -50
- package/src/chat/advisor-message.ts +2 -0
- package/src/chat/assistant-message.ts +137 -19
- package/src/chat/background-tan-message.ts +4 -0
- package/src/chat/bash-execution.ts +6 -0
- package/src/chat/chat-transcript-builder.ts +58 -28
- package/src/chat/compaction-summary-message.ts +2 -1
- package/src/chat/display-preferences.ts +3 -0
- package/src/chat/extension-types.ts +34 -0
- package/src/chat/reply-write.ts +127 -0
- package/src/chat/table-chart.ts +10 -4
- package/src/chat/tool-execution.ts +20 -13
- package/src/chat/transcript-outline.ts +27 -0
- package/src/chat/transcript-render-helpers.ts +66 -24
- package/src/chrome/index.ts +1 -0
- package/src/chrome/transcript-notice.ts +83 -0
- package/src/chrome/transcript-status.ts +29 -29
- package/src/components/editor.ts +10 -1
- package/src/components/markdown.ts +116 -6
- package/src/hotkeys-markdown.ts +1 -0
- package/src/native/backend.ts +5 -66
- package/src/native/blobs.ts +3 -2
- package/src/native/picker.ts +3 -3
- package/src/overlays/advisor-config.ts +1 -10
- package/src/overlays/annotation-overlay.ts +125 -37
- package/src/overlays/annotation-types.ts +24 -0
- package/src/overlays/hook-selector.ts +2 -2
- package/src/overlays/login-dialog.ts +31 -5
- package/src/overlays/model-browser.ts +37 -0
- package/src/overlays/model-hub.ts +1 -7
- package/src/overlays/model-picker.ts +1 -10
- package/src/overlays/oauth-selector.ts +44 -6
- package/src/overlays/plan-review-overlay.ts +13 -2
- package/src/overlays/rewind-selector.ts +29 -12
- package/src/overlays/session-selector.ts +305 -39
- package/src/overlays/usage-dashboard.ts +5 -12
- package/src/prompt/composer.ts +213 -5
- package/src/prompt/custom-editor.ts +6 -1
- package/src/prompt/emoji-autocomplete.ts +0 -2
- package/src/prompt/interactive-context-helpers.ts +29 -4
- package/src/prompt/welcome.ts +31 -45
- package/src/render/extension-links.ts +214 -0
- package/src/render/hyperlink.ts +65 -9
- package/src/render/index.ts +1 -0
- package/src/setup/scenes/composer.ts +2 -0
- package/src/setup/scenes/glyph.ts +2 -0
- package/src/setup/scenes/model.ts +192 -29
- package/src/setup/scenes/outro.ts +30 -17
- package/src/setup/scenes/sign-in.ts +196 -400
- package/src/setup/scenes/splash.ts +25 -21
- package/src/setup/scenes/theme.ts +2 -0
- package/src/setup/scenes/types.ts +18 -1
- package/src/setup/startup-splash.ts +8 -7
- package/src/setup/wizard-overlay.ts +164 -33
- package/src/setup/wizard.ts +11 -2
- package/src/status-line/component.ts +4 -10
- package/src/status-line/context-usage.ts +72 -21
- package/src/status-line/host.ts +6 -2
- package/src/terminal.ts +24 -2
- package/src/theme/defaults.json +1 -0
- package/src/theme/loader.ts +1 -1
- package/src/theme/theme.ts +2 -75
- package/src/tools/bash-interactive.ts +6 -1
- package/src/tools/bash.ts +2 -0
- package/src/tools/default-renderer.ts +9 -6
- package/src/tools/eval.ts +14 -4
- package/src/tools/irc.ts +6 -0
- package/src/tools/job-widget.ts +13 -1
- package/src/tools/json-tree.ts +132 -42
- package/src/tools/lsp.ts +36 -15
- package/src/tools/mcp.ts +20 -12
- package/src/tools/native-view.ts +1 -1
- package/src/tools/renderer.ts +2 -0
- package/src/tools/streaming-output.ts +41 -0
- package/src/tools/task.ts +59 -17
- package/src/tools/url-scheme-host.ts +21 -0
- package/src/tools/yield.ts +18 -9
- package/src/tui.ts +43 -15
- package/THIRD-PARTY-NOTICES.txt +0 -22901
- package/dist/types/theme/defaults/index.d.ts +0 -10289
- package/src/theme/defaults/alabaster.json +0 -103
- package/src/theme/defaults/amethyst.json +0 -106
- package/src/theme/defaults/anthracite.json +0 -103
- package/src/theme/defaults/basalt.json +0 -101
- package/src/theme/defaults/birch.json +0 -105
- package/src/theme/defaults/dark-abyss.json +0 -101
- package/src/theme/defaults/dark-arctic.json +0 -114
- package/src/theme/defaults/dark-aurora.json +0 -105
- package/src/theme/defaults/dark-catppuccin.json +0 -117
- package/src/theme/defaults/dark-cavern.json +0 -101
- package/src/theme/defaults/dark-celestial.json +0 -116
- package/src/theme/defaults/dark-copper.json +0 -105
- package/src/theme/defaults/dark-cosmos.json +0 -100
- package/src/theme/defaults/dark-cyberpunk.json +0 -112
- package/src/theme/defaults/dark-dracula.json +0 -108
- package/src/theme/defaults/dark-eclipse.json +0 -101
- package/src/theme/defaults/dark-ember.json +0 -105
- package/src/theme/defaults/dark-equinox.json +0 -100
- package/src/theme/defaults/dark-forest.json +0 -106
- package/src/theme/defaults/dark-github.json +0 -115
- package/src/theme/defaults/dark-gruvbox.json +0 -122
- package/src/theme/defaults/dark-lavender.json +0 -105
- package/src/theme/defaults/dark-lunar.json +0 -99
- package/src/theme/defaults/dark-midnight.json +0 -105
- package/src/theme/defaults/dark-monochrome.json +0 -104
- package/src/theme/defaults/dark-monokai.json +0 -108
- package/src/theme/defaults/dark-nebula.json +0 -100
- package/src/theme/defaults/dark-neon-noir.json +0 -110
- package/src/theme/defaults/dark-nord.json +0 -108
- package/src/theme/defaults/dark-ocean.json +0 -111
- package/src/theme/defaults/dark-one.json +0 -110
- package/src/theme/defaults/dark-poimandres.json +0 -125
- package/src/theme/defaults/dark-rainforest.json +0 -101
- package/src/theme/defaults/dark-reef.json +0 -101
- package/src/theme/defaults/dark-retro.json +0 -102
- package/src/theme/defaults/dark-rose-pine.json +0 -106
- package/src/theme/defaults/dark-sakura.json +0 -105
- package/src/theme/defaults/dark-slate.json +0 -105
- package/src/theme/defaults/dark-solarized.json +0 -107
- package/src/theme/defaults/dark-solstice.json +0 -100
- package/src/theme/defaults/dark-starfall.json +0 -101
- package/src/theme/defaults/dark-sunset.json +0 -109
- package/src/theme/defaults/dark-swamp.json +0 -100
- package/src/theme/defaults/dark-synthwave.json +0 -113
- package/src/theme/defaults/dark-taiga.json +0 -101
- package/src/theme/defaults/dark-terminal.json +0 -105
- package/src/theme/defaults/dark-tokyo-night.json +0 -111
- package/src/theme/defaults/dark-tundra.json +0 -101
- package/src/theme/defaults/dark-twilight.json +0 -101
- package/src/theme/defaults/dark-volcanic.json +0 -101
- package/src/theme/defaults/graphite.json +0 -102
- package/src/theme/defaults/index.ts +0 -203
- package/src/theme/defaults/light-arctic.json +0 -117
- package/src/theme/defaults/light-aurora-day.json +0 -101
- package/src/theme/defaults/light-canyon.json +0 -101
- package/src/theme/defaults/light-catppuccin.json +0 -116
- package/src/theme/defaults/light-cirrus.json +0 -100
- package/src/theme/defaults/light-coral.json +0 -105
- package/src/theme/defaults/light-cyberpunk.json +0 -106
- package/src/theme/defaults/light-dawn.json +0 -100
- package/src/theme/defaults/light-dunes.json +0 -101
- package/src/theme/defaults/light-eucalyptus.json +0 -105
- package/src/theme/defaults/light-forest.json +0 -110
- package/src/theme/defaults/light-frost.json +0 -105
- package/src/theme/defaults/light-github.json +0 -125
- package/src/theme/defaults/light-glacier.json +0 -101
- package/src/theme/defaults/light-gruvbox.json +0 -118
- package/src/theme/defaults/light-haze.json +0 -100
- package/src/theme/defaults/light-honeycomb.json +0 -105
- package/src/theme/defaults/light-lagoon.json +0 -101
- package/src/theme/defaults/light-lavender.json +0 -105
- package/src/theme/defaults/light-meadow.json +0 -101
- package/src/theme/defaults/light-mint.json +0 -105
- package/src/theme/defaults/light-monochrome.json +0 -111
- package/src/theme/defaults/light-ocean.json +0 -109
- package/src/theme/defaults/light-one.json +0 -109
- package/src/theme/defaults/light-opal.json +0 -101
- package/src/theme/defaults/light-orchard.json +0 -101
- package/src/theme/defaults/light-paper.json +0 -105
- package/src/theme/defaults/light-poimandres.json +0 -125
- package/src/theme/defaults/light-prism.json +0 -100
- package/src/theme/defaults/light-retro.json +0 -108
- package/src/theme/defaults/light-sand.json +0 -105
- package/src/theme/defaults/light-savanna.json +0 -101
- package/src/theme/defaults/light-solarized.json +0 -112
- package/src/theme/defaults/light-soleil.json +0 -100
- package/src/theme/defaults/light-sunset.json +0 -109
- package/src/theme/defaults/light-synthwave.json +0 -108
- package/src/theme/defaults/light-tokyo-night.json +0 -121
- package/src/theme/defaults/light-wetland.json +0 -101
- package/src/theme/defaults/light-zenith.json +0 -99
- package/src/theme/defaults/limestone.json +0 -104
- package/src/theme/defaults/mahogany.json +0 -107
- package/src/theme/defaults/marble.json +0 -103
- package/src/theme/defaults/obsidian.json +0 -101
- package/src/theme/defaults/onyx.json +0 -101
- package/src/theme/defaults/pearl.json +0 -103
- package/src/theme/defaults/porcelain.json +0 -101
- package/src/theme/defaults/quartz.json +0 -106
- package/src/theme/defaults/sandstone.json +0 -105
- package/src/theme/defaults/titanium.json +0 -100
package/src/hotkeys-markdown.ts
CHANGED
|
@@ -135,6 +135,7 @@ function hotkeyGroups(bindings: HotkeysMarkdownBindings): HotkeyGroup[] {
|
|
|
135
135
|
{ keys: [act("app.history.search")], action: "Search prompt history" },
|
|
136
136
|
{ keys: [act("app.tools.expand")], action: "Toggle tool output expansion" },
|
|
137
137
|
{ keys: [act("app.tools.toggleVisibility")], action: "Toggle tool activity visibility" },
|
|
138
|
+
{ keys: [act("app.links.hint")], action: "Pick an extension link on screen by its tag" },
|
|
138
139
|
{ keys: [act("app.ask.hide")], action: "Hide/show a pending ask question" },
|
|
139
140
|
{ keys: [act("app.approval.approve")], action: "Approve a pending tool call" },
|
|
140
141
|
{ keys: [act("app.thinking.toggle")], action: "Toggle thinking block visibility" },
|
package/src/native/backend.ts
CHANGED
|
@@ -39,15 +39,7 @@ import {
|
|
|
39
39
|
type TspOp,
|
|
40
40
|
} from "@oh-my-pi/pi-wire";
|
|
41
41
|
import type { Terminal } from "../terminal";
|
|
42
|
-
import {
|
|
43
|
-
bindTheme,
|
|
44
|
-
getCurrentThemeName,
|
|
45
|
-
getNativeThemePalette,
|
|
46
|
-
getNativeThemePaletteKey,
|
|
47
|
-
isLightTheme,
|
|
48
|
-
type NativeThemePalette,
|
|
49
|
-
setNativeSymbolPreset,
|
|
50
|
-
} from "../theme/theme";
|
|
42
|
+
import { getCurrentThemeName, isLightTheme, setNativeSymbolPreset } from "../theme/theme";
|
|
51
43
|
import type { Component, OverlayOptions, RenderScheduler, RenderTimer } from "../tui";
|
|
52
44
|
import { TspDocument } from "./apply";
|
|
53
45
|
import { getNativeBlob, type NativeBlob } from "./blobs";
|
|
@@ -206,9 +198,10 @@ function sameSet(a: ReadonlySet<string>, b: ReadonlySet<string>): boolean {
|
|
|
206
198
|
/**
|
|
207
199
|
* Kinds newer than some Tern builds that answer `TERM_PROGRAM=tern`: used only
|
|
208
200
|
* once the real `hello` lists them, so the optimistic start never emits a node
|
|
209
|
-
* an older terminal cannot draw (`media`: files Tern loads by path
|
|
201
|
+
* an older terminal cannot draw (`media`: files Tern loads by path; `json`:
|
|
202
|
+
* raw JSON values Tern draws as its own tree).
|
|
210
203
|
*/
|
|
211
|
-
const UNASSUMED_KINDS: Partial<Record<TspKind, true>> = { media: true };
|
|
204
|
+
const UNASSUMED_KINDS: Partial<Record<TspKind, true>> = { media: true, json: true };
|
|
212
205
|
|
|
213
206
|
/**
|
|
214
207
|
* The reply a v1 terminal is assumed to give before its real `hello` arrives
|
|
@@ -313,11 +306,6 @@ export class NativeBackend {
|
|
|
313
306
|
#sawResize = false;
|
|
314
307
|
#live = false;
|
|
315
308
|
#overlayNodes = new Map<Component, { key: string; node: NativeNode }>();
|
|
316
|
-
#unbindTheme: (() => void) | undefined;
|
|
317
|
-
#palette: NativeThemePalette | undefined;
|
|
318
|
-
#paletteKey: string | undefined;
|
|
319
|
-
/** Serialized palette last sent, to skip resends of an unchanged theme. */
|
|
320
|
-
#paletteSent: string | undefined;
|
|
321
309
|
/** Pending delivery owns bytes until sent or confirmed held; settled descriptions may already be released. */
|
|
322
310
|
#blobs = new Map<string, NativeBlob | "sent">();
|
|
323
311
|
/** `blobs` queries sent, oldest first: the terminal answers each once, in order. */
|
|
@@ -366,54 +354,11 @@ export class NativeBackend {
|
|
|
366
354
|
if (this.#live) return;
|
|
367
355
|
this.#live = true;
|
|
368
356
|
this.#useNerdSymbols(true);
|
|
369
|
-
this.#watchTheme();
|
|
370
357
|
this.#open(this.#inline);
|
|
371
358
|
setNativeRendering(true);
|
|
372
359
|
this.render();
|
|
373
360
|
}
|
|
374
361
|
|
|
375
|
-
/**
|
|
376
|
-
* Any theme swap (switch, selector preview, auto light/dark, file reload)
|
|
377
|
-
* schedules a render; `render()` resends the palette when it changed, so
|
|
378
|
-
* bursts coalesce into one `t` and no frame is needed for it.
|
|
379
|
-
*/
|
|
380
|
-
#watchTheme(): void {
|
|
381
|
-
this.#unbindTheme?.();
|
|
382
|
-
let initial = true;
|
|
383
|
-
this.#unbindTheme = bindTheme(() => {
|
|
384
|
-
if (initial) return;
|
|
385
|
-
this.#host.requestRender();
|
|
386
|
-
});
|
|
387
|
-
initial = false;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/** The current palette, recomputed only when its inputs changed. */
|
|
391
|
-
#currentPalette(): NativeThemePalette {
|
|
392
|
-
const key = getNativeThemePaletteKey();
|
|
393
|
-
if (this.#palette === undefined || key !== this.#paletteKey) {
|
|
394
|
-
this.#paletteKey = key;
|
|
395
|
-
this.#palette = getNativeThemePalette();
|
|
396
|
-
}
|
|
397
|
-
return this.#palette;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/** Send the palette to `surface` (right after its `o`, before any `f`). */
|
|
401
|
-
#sendPalette(surface: Surface): void {
|
|
402
|
-
const palette = this.#currentPalette();
|
|
403
|
-
if (!palette.dark && !palette.light) return;
|
|
404
|
-
this.#paletteSent = JSON.stringify(palette);
|
|
405
|
-
this.#write("t", { sf: surface.id, ...palette });
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/** Resend the palette to every open surface when the theme changed since it was sent. */
|
|
409
|
-
#refreshPalette(): void {
|
|
410
|
-
const key = this.#paletteKey;
|
|
411
|
-
const palette = this.#currentPalette();
|
|
412
|
-
if (key === this.#paletteKey || JSON.stringify(palette) === this.#paletteSent) return;
|
|
413
|
-
this.#sendPalette(this.#inline);
|
|
414
|
-
if (this.#screen) this.#sendPalette(this.#screen);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
362
|
/**
|
|
418
363
|
* Tern's font carries every Nerd Font glyph, and it never answers the Glyph
|
|
419
364
|
* Protocol, so icons come from the nerd preset (process-local, not the
|
|
@@ -435,8 +380,6 @@ export class NativeBackend {
|
|
|
435
380
|
this.#close(this.#inline, keep);
|
|
436
381
|
this.#inline.doc?.close();
|
|
437
382
|
setNativeRendering(false);
|
|
438
|
-
this.#unbindTheme?.();
|
|
439
|
-
this.#unbindTheme = undefined;
|
|
440
383
|
this.#useNerdSymbols(false);
|
|
441
384
|
this.#clearStallTimer();
|
|
442
385
|
// Replies no longer reach the backend: forget the queries outright.
|
|
@@ -455,7 +398,6 @@ export class NativeBackend {
|
|
|
455
398
|
this.#applyHello(hello);
|
|
456
399
|
this.#live = true;
|
|
457
400
|
this.#useNerdSymbols(true);
|
|
458
|
-
this.#watchTheme();
|
|
459
401
|
this.#resetBlobs();
|
|
460
402
|
const surface = this.#inline;
|
|
461
403
|
surface.reconciler.detachLive();
|
|
@@ -466,7 +408,6 @@ export class NativeBackend {
|
|
|
466
408
|
surface.focus = null;
|
|
467
409
|
surface.dirty = false;
|
|
468
410
|
this.#write("o", { id: surface.id, mode: "inline", title: "omp", role: SESSION_ROLE, adopt: true });
|
|
469
|
-
this.#sendPalette(surface);
|
|
470
411
|
// After the `o`, as in `start()`.
|
|
471
412
|
setNativeRendering(true);
|
|
472
413
|
this.render();
|
|
@@ -508,7 +449,6 @@ export class NativeBackend {
|
|
|
508
449
|
/** Reconcile and send a frame, unless the target surface is out of credits. */
|
|
509
450
|
render(): void {
|
|
510
451
|
if (!this.#live) return;
|
|
511
|
-
this.#refreshPalette();
|
|
512
452
|
const overlays = this.#host.overlays();
|
|
513
453
|
let fullscreen = -1;
|
|
514
454
|
for (let i = overlays.length - 1; i >= 0; i--) {
|
|
@@ -608,7 +548,6 @@ export class NativeBackend {
|
|
|
608
548
|
|
|
609
549
|
#open(surface: Surface): void {
|
|
610
550
|
this.#write("o", { id: surface.id, mode: surface.mode, title: "omp", role: surface.role });
|
|
611
|
-
this.#sendPalette(surface);
|
|
612
551
|
}
|
|
613
552
|
|
|
614
553
|
#close(surface: Surface, keep: boolean): void {
|
|
@@ -776,7 +715,7 @@ export class NativeBackend {
|
|
|
776
715
|
}
|
|
777
716
|
}
|
|
778
717
|
|
|
779
|
-
#write(verb: "o" | "f" | "
|
|
718
|
+
#write(verb: "o" | "f" | "x" | "q", body: unknown): void {
|
|
780
719
|
this.#record("out", verb, undefined, body);
|
|
781
720
|
this.#host.terminal.write(encodeTspJson(verb, body, undefined, this.#limit));
|
|
782
721
|
}
|
package/src/native/blobs.ts
CHANGED
|
@@ -97,10 +97,11 @@ export function base64ImageNode(
|
|
|
97
97
|
/**
|
|
98
98
|
* Props that make an `image` node open the file at `filePath` (as a `file://` URL, the TSP
|
|
99
99
|
* open-target form): a click zooms the image in the terminal, and the file opens from its
|
|
100
|
-
* context menu.
|
|
100
|
+
* context menu. Always uses a local file URL because the open action runs locally,
|
|
101
|
+
* even when remote file handlers are configured. Empty without a file.
|
|
101
102
|
*/
|
|
102
103
|
export function openableImageProps(filePath: string | undefined): Pick<TspProps<"image">, "href" | "actions"> {
|
|
103
|
-
const href = filePath ? tryFileUri(filePath) : undefined;
|
|
104
|
+
const href = filePath ? tryFileUri(filePath, { local: true }) : undefined;
|
|
104
105
|
return href ? { href, actions: { menu: ["open"] } } : {};
|
|
105
106
|
}
|
|
106
107
|
|
package/src/native/picker.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* the item id and `action` with an action id (plus `value` for
|
|
17
17
|
* `scope`/`tab`/`strip`). {@link pickerEvent} folds them into one shape.
|
|
18
18
|
*/
|
|
19
|
-
import type { TspPickerAction, TspPickerItem, TspPickerProps } from "@oh-my-pi/pi-wire";
|
|
19
|
+
import type { TspPickerAction, TspPickerItem, TspPickerProps, TspProps } from "@oh-my-pi/pi-wire";
|
|
20
20
|
import type { KeyName } from "../key-hint-format";
|
|
21
21
|
import { getKeybindings, type Keybinding } from "../keybindings";
|
|
22
22
|
import type { Input } from "../components/input";
|
|
@@ -25,7 +25,7 @@ import { col, node } from "./describe";
|
|
|
25
25
|
import type { NativeChild, NativeNode, NativeUiEvent } from "./node";
|
|
26
26
|
|
|
27
27
|
/** The picker sheet; `preview` children fill its preview pane. */
|
|
28
|
-
export function picker(p:
|
|
28
|
+
export function picker(p: TspProps<"picker">, preview?: readonly NativeChild[]): NativeNode {
|
|
29
29
|
return node("picker", p, preview);
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -46,7 +46,7 @@ export const PICKER_KEY = "picker";
|
|
|
46
46
|
* the picker as child {@link PICKER_KEY}, which the reconciler hoists into
|
|
47
47
|
* `layer`. Route its events with `pickerEvent(event, PICKER_KEY)`.
|
|
48
48
|
*/
|
|
49
|
-
export function dockedPicker(p:
|
|
49
|
+
export function dockedPicker(p: TspProps<"picker">, preview?: readonly NativeChild[]): NativeNode {
|
|
50
50
|
return col([node("picker", p, preview, PICKER_KEY)]);
|
|
51
51
|
}
|
|
52
52
|
|
|
@@ -36,7 +36,6 @@ import { editorKey, editorKeys } from "../chrome/keybinding-hints";
|
|
|
36
36
|
import { HookEditorComponent } from "./hook-editor";
|
|
37
37
|
import {
|
|
38
38
|
buildBrowserItems,
|
|
39
|
-
MODEL_PICKER_COLUMNS,
|
|
40
39
|
ModelBrowser,
|
|
41
40
|
type ModelBrowserItem,
|
|
42
41
|
type ModelBrowserSource,
|
|
@@ -749,16 +748,8 @@ export class AdvisorConfigOverlayComponent implements Component {
|
|
|
749
748
|
size: "md",
|
|
750
749
|
layout: "rows",
|
|
751
750
|
preview: "below",
|
|
752
|
-
query: browser.query,
|
|
753
|
-
cursor: browser.cursor,
|
|
754
751
|
placeholder: "Search models…",
|
|
755
|
-
|
|
756
|
-
...catalogue,
|
|
757
|
-
order: order.order,
|
|
758
|
-
...(order.hits ? { hits: order.hits } : {}),
|
|
759
|
-
selected: browser.pickerSelected,
|
|
760
|
-
...(this.#modelCurrent ? { current: [this.#modelCurrent] } : {}),
|
|
761
|
-
total: browser.baseCount,
|
|
752
|
+
...browser.pickerRows(catalogue, order, this.#modelCurrent),
|
|
762
753
|
empty: "No models available",
|
|
763
754
|
actions: [
|
|
764
755
|
pickerAction("use", "Use for advisor", "enter", { primary: true }),
|
|
@@ -19,6 +19,7 @@ import type { Keybinding } from "../keybindings";
|
|
|
19
19
|
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
20
20
|
import { type Theme } from "../theme/theme";
|
|
21
21
|
import type {
|
|
22
|
+
AnnotationOverlayDraft,
|
|
22
23
|
CodeReviewAnnotation,
|
|
23
24
|
CodeReviewOverlayResult,
|
|
24
25
|
ReviewDiffFile,
|
|
@@ -125,8 +126,14 @@ const MIN_BODY_ROWS = 3;
|
|
|
125
126
|
const SIDEBAR_MIN_TOTAL_WIDTH = 64;
|
|
126
127
|
const SIDEBAR_MIN_BODY_WIDTH = 40;
|
|
127
128
|
const MAX_ANNOTATION_EDITOR_ROWS = 6;
|
|
128
|
-
|
|
129
|
-
const
|
|
129
|
+
/** Clears every note and closes; the host then has nothing to keep. Needs a second confirm. */
|
|
130
|
+
export const DISCARD_ANNOTATIONS_ACTION = "Discard notes and close";
|
|
131
|
+
const CODE_REVIEW_ACTIONS = [
|
|
132
|
+
CONTINUE_CODE_REVIEW_ACTION,
|
|
133
|
+
PASTE_CODE_REVIEW_ACTION,
|
|
134
|
+
DISCARD_ANNOTATIONS_ACTION,
|
|
135
|
+
] as const;
|
|
136
|
+
const TEXT_REVIEW_ACTIONS = [PASTE_CODE_REVIEW_ACTION, DISCARD_ANNOTATIONS_ACTION] as const;
|
|
130
137
|
|
|
131
138
|
function isSourceRow(row: ReviewDiffRow): row is ReviewSourceRow {
|
|
132
139
|
return row.kind === "context" || row.kind === "added" || row.kind === "removed";
|
|
@@ -229,6 +236,8 @@ export class AnnotationOverlay implements Focusable {
|
|
|
229
236
|
/** Set once {@link replaceTextSource} runs; the paste result then carries the edited text. */
|
|
230
237
|
#textSourceEdited = false;
|
|
231
238
|
#finished = false;
|
|
239
|
+
/** Discard was chosen once; choosing it again discards, any other input disarms it. */
|
|
240
|
+
#discardArmed = false;
|
|
232
241
|
#annotations: CommittedAnnotation[] = [];
|
|
233
242
|
#textAnnotations: CommittedTextAnnotation[] = [];
|
|
234
243
|
/** Annotation lists before each create/edit/delete, restored by `u`. */
|
|
@@ -295,6 +304,8 @@ export class AnnotationOverlay implements Focusable {
|
|
|
295
304
|
this.#files = filesOrSource;
|
|
296
305
|
this.#mode = modeOrCallbacks as string;
|
|
297
306
|
this.#callbacks = maybeCallbacks!;
|
|
307
|
+
// Start on Paste, not the LLM review: a stray Enter must not submit (it does nothing until a note exists).
|
|
308
|
+
this.#actionIndex = 1;
|
|
298
309
|
}
|
|
299
310
|
const symbols = {
|
|
300
311
|
cursor: theme.nav.cursor,
|
|
@@ -357,6 +368,60 @@ export class AnnotationOverlay implements Focusable {
|
|
|
357
368
|
this.#finished = true;
|
|
358
369
|
}
|
|
359
370
|
|
|
371
|
+
/** Notes, the unfinished note, and any source edit, in the form {@link restoreDraft} replays. */
|
|
372
|
+
getDraft(): AnnotationOverlayDraft {
|
|
373
|
+
const editing =
|
|
374
|
+
this.#editingAnnotationIndex === undefined ? {} : { editingAnnotationIndex: this.#editingAnnotationIndex };
|
|
375
|
+
const text = this.#editor.getExpandedText();
|
|
376
|
+
if (this.#textSource) {
|
|
377
|
+
const scope = this.#annotationScope === "text" ? "text" : "line";
|
|
378
|
+
const sourceIndex = scope === "text" ? -1 : this.#sourceIndex;
|
|
379
|
+
return {
|
|
380
|
+
kind: "text",
|
|
381
|
+
annotations: this.#textAnnotations.map(entry => ({ ...entry, annotation: { ...entry.annotation } })),
|
|
382
|
+
...(this.#textSourceEdited ? { editedText: this.#textSource.text } : {}),
|
|
383
|
+
...(this.#annotating ? { editor: { sourceIndex, scope, ...editing, text } } : {}),
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
const scope = this.#annotationScope === "file" ? "file" : "line";
|
|
387
|
+
const editor = { fileIndex: this.#fileIndex, sourceIndex: this.#sourceIndex, scope, ...editing, text } as const;
|
|
388
|
+
return {
|
|
389
|
+
kind: "diff",
|
|
390
|
+
annotations: this.#annotations.map(entry => ({ ...entry, annotation: { ...entry.annotation } })),
|
|
391
|
+
...(this.#annotating ? { editor } : {}),
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** Replay a draft taken from this same source; anything anchored outside it is dropped. */
|
|
396
|
+
restoreDraft(draft: AnnotationOverlayDraft): void {
|
|
397
|
+
const rows = (fileIndex: number) =>
|
|
398
|
+
this.#textSource ? this.#textLines.length : (this.#files[fileIndex]?.rows.filter(isSourceRow).length ?? -1);
|
|
399
|
+
const fits = (fileIndex: number, sourceIndex: number, whole: boolean) =>
|
|
400
|
+
rows(fileIndex) >= 0 && (whole ? sourceIndex === -1 : sourceIndex >= 0 && sourceIndex < rows(fileIndex));
|
|
401
|
+
if (draft.kind === "text" && this.#textSource) {
|
|
402
|
+
if (draft.editedText !== undefined) this.replaceTextSource(draft.editedText);
|
|
403
|
+
this.#textAnnotations = draft.annotations.filter(e => fits(0, e.sourceIndex, e.annotation.scope === "text"));
|
|
404
|
+
} else if (draft.kind === "diff" && !this.#textSource) {
|
|
405
|
+
this.#annotations = draft.annotations.filter(e =>
|
|
406
|
+
fits(e.fileIndex, e.sourceIndex, e.annotation.scope === "file"),
|
|
407
|
+
);
|
|
408
|
+
} else return;
|
|
409
|
+
const editor = draft.editor;
|
|
410
|
+
const fileIndex = editor && "fileIndex" in editor ? editor.fileIndex : 0;
|
|
411
|
+
if (editor && fits(fileIndex, editor.sourceIndex, editor.scope === "text" || editor.scope === "file")) {
|
|
412
|
+
this.#fileIndex = fileIndex;
|
|
413
|
+
if (editor.sourceIndex >= 0) this.#sourceIndex = editor.sourceIndex;
|
|
414
|
+
this.#annotationScope = editor.scope;
|
|
415
|
+
const count = this.#textSource ? this.#textAnnotations.length : this.#annotations.length;
|
|
416
|
+
this.#editingAnnotationIndex =
|
|
417
|
+
(editor.editingAnnotationIndex ?? count) < count ? editor.editingAnnotationIndex : undefined;
|
|
418
|
+
this.#annotating = true;
|
|
419
|
+
this.#editor.setText(editor.text);
|
|
420
|
+
}
|
|
421
|
+
this.#annotationRev++;
|
|
422
|
+
this.#nativeBody = undefined;
|
|
423
|
+
}
|
|
424
|
+
|
|
360
425
|
getAnnotations(): CodeReviewAnnotation[] {
|
|
361
426
|
return this.#annotations.map(entry => ({ ...entry.annotation }));
|
|
362
427
|
}
|
|
@@ -422,6 +487,8 @@ export class AnnotationOverlay implements Focusable {
|
|
|
422
487
|
|
|
423
488
|
handleInput(data: string): void {
|
|
424
489
|
if (this.#finished || this.#externalOperation) return;
|
|
490
|
+
const discardArmed = this.#discardArmed;
|
|
491
|
+
this.#discardArmed = false;
|
|
425
492
|
if (this.#annotationChooser) {
|
|
426
493
|
this.#handleAnnotationChooser(data);
|
|
427
494
|
return;
|
|
@@ -431,8 +498,9 @@ export class AnnotationOverlay implements Focusable {
|
|
|
431
498
|
void this.#openAnnotationEditor();
|
|
432
499
|
return;
|
|
433
500
|
}
|
|
501
|
+
// Close, keeping the unfinished note in the draft so it reopens in the editor.
|
|
434
502
|
if (this.#keybindings.matches(data, "tui.select.cancel")) {
|
|
435
|
-
this.#
|
|
503
|
+
this.#finish(undefined);
|
|
436
504
|
return;
|
|
437
505
|
}
|
|
438
506
|
this.#editor.handleInput(data);
|
|
@@ -476,7 +544,7 @@ export class AnnotationOverlay implements Focusable {
|
|
|
476
544
|
}
|
|
477
545
|
if (this.#focus === "files") this.#handleFiles(data);
|
|
478
546
|
else if (this.#focus === "diff") this.#handleDiff(data);
|
|
479
|
-
else this.#handleActions(data);
|
|
547
|
+
else this.#handleActions(data, discardArmed);
|
|
480
548
|
}
|
|
481
549
|
|
|
482
550
|
/** Primary key bound to `action` in this overlay's keybindings, formatted for footer hints. */
|
|
@@ -495,10 +563,9 @@ export class AnnotationOverlay implements Focusable {
|
|
|
495
563
|
}
|
|
496
564
|
}
|
|
497
565
|
|
|
498
|
-
/** Esc, by state: leave the note chooser,
|
|
566
|
+
/** Esc, by state: leave the note chooser, else close the review; the host keeps the draft. */
|
|
499
567
|
#escape(): void {
|
|
500
568
|
if (this.#annotationChooser) this.#annotationChooser = undefined;
|
|
501
|
-
else if (this.#annotating) this.#cancelAnnotation();
|
|
502
569
|
else this.#finish(undefined);
|
|
503
570
|
}
|
|
504
571
|
|
|
@@ -602,33 +669,41 @@ export class AnnotationOverlay implements Focusable {
|
|
|
602
669
|
}
|
|
603
670
|
}
|
|
604
671
|
|
|
605
|
-
#handleActions(data: string): void {
|
|
606
|
-
const
|
|
607
|
-
if (
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
if (this.#keybindings.matches(data, "tui.select.down") || matchesKey(data, "j")) {
|
|
614
|
-
if (this.#textSource) {
|
|
615
|
-
this.#actionIndex = (this.#actionIndex + 1) % actionCount;
|
|
616
|
-
} else if (hasAnnotations) {
|
|
617
|
-
this.#actionIndex = Math.min(actionCount - 1, this.#actionIndex + 1);
|
|
672
|
+
#handleActions(data: string, discardArmed: boolean): void {
|
|
673
|
+
const up = this.#keybindings.matches(data, "tui.select.up") || matchesKey(data, "k");
|
|
674
|
+
if (up || this.#keybindings.matches(data, "tui.select.down") || matchesKey(data, "j")) {
|
|
675
|
+
const step = up ? -1 : 1;
|
|
676
|
+
for (let index = this.#actionIndex + step; index >= 0 && index < this.#actions.length; index += step) {
|
|
677
|
+
if (this.#actionDisabled(index)) continue;
|
|
678
|
+
this.#actionIndex = index;
|
|
679
|
+
break;
|
|
618
680
|
}
|
|
619
681
|
return;
|
|
620
682
|
}
|
|
621
|
-
if (this.#keybindings.matches(data, "tui.select.confirm")) this.#confirmAction();
|
|
683
|
+
if (this.#keybindings.matches(data, "tui.select.confirm")) this.#confirmAction(discardArmed);
|
|
622
684
|
}
|
|
623
685
|
|
|
624
686
|
#actionDisabled(index: number): boolean {
|
|
687
|
+
if (this.#actions[index] === DISCARD_ANNOTATIONS_ACTION) return false;
|
|
625
688
|
const hasAnnotations = this.#textSource ? this.#textAnnotations.length > 0 : this.#annotations.length > 0;
|
|
626
689
|
return this.#textSource ? !hasAnnotations : index === 1 && !hasAnnotations;
|
|
627
690
|
}
|
|
628
691
|
|
|
629
|
-
#confirmAction(): void {
|
|
692
|
+
#confirmAction(discardArmed = false): void {
|
|
630
693
|
if (this.#actionDisabled(this.#actionIndex)) return;
|
|
631
|
-
if (this.#
|
|
694
|
+
if (this.#actions[this.#actionIndex] === DISCARD_ANNOTATIONS_ACTION) {
|
|
695
|
+
if (!discardArmed) {
|
|
696
|
+
this.#discardArmed = true;
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
this.#annotations = [];
|
|
700
|
+
this.#textAnnotations = [];
|
|
701
|
+
this.#textSourceEdited = false;
|
|
702
|
+
this.#annotating = false;
|
|
703
|
+
this.#editingAnnotationIndex = undefined;
|
|
704
|
+
this.#editor.setText("");
|
|
705
|
+
this.#finish(undefined);
|
|
706
|
+
} else if (this.#textSource) {
|
|
632
707
|
this.#finish({
|
|
633
708
|
action: "paste",
|
|
634
709
|
annotations: this.getTextAnnotations(),
|
|
@@ -721,12 +796,6 @@ export class AnnotationOverlay implements Focusable {
|
|
|
721
796
|
this.#editor.setText(existing?.annotation.note ?? "");
|
|
722
797
|
}
|
|
723
798
|
|
|
724
|
-
#cancelAnnotation(): void {
|
|
725
|
-
this.#annotating = false;
|
|
726
|
-
this.#editingAnnotationIndex = undefined;
|
|
727
|
-
this.#editor.setText("");
|
|
728
|
-
}
|
|
729
|
-
|
|
730
799
|
#commitAnnotation(value: string): void {
|
|
731
800
|
if (this.#textSource) {
|
|
732
801
|
this.#commitTextAnnotation(value);
|
|
@@ -925,7 +994,6 @@ export class AnnotationOverlay implements Focusable {
|
|
|
925
994
|
this.#annotationRev++;
|
|
926
995
|
this.#annotations = snapshot.annotations;
|
|
927
996
|
this.#textAnnotations = snapshot.textAnnotations;
|
|
928
|
-
if (this.#annotations.length === 0 && this.#actionIndex === 1) this.#actionIndex = 0;
|
|
929
997
|
}
|
|
930
998
|
|
|
931
999
|
async #openAnnotationEditor(): Promise<void> {
|
|
@@ -1146,9 +1214,10 @@ export class AnnotationOverlay implements Focusable {
|
|
|
1146
1214
|
}
|
|
1147
1215
|
|
|
1148
1216
|
#renderActions(): string[] {
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1217
|
+
return this.#actions.map((action, index) => {
|
|
1218
|
+
const label =
|
|
1219
|
+
action === DISCARD_ANNOTATIONS_ACTION && this.#discardArmed ? "Confirm: discard all notes" : action;
|
|
1220
|
+
const disabled = this.#actionDisabled(index);
|
|
1152
1221
|
const selected = index === this.#actionIndex;
|
|
1153
1222
|
const cursor = selected ? `${this.#theme.nav.cursor} ` : " ";
|
|
1154
1223
|
const text = disabled
|
|
@@ -1224,7 +1293,7 @@ export class AnnotationOverlay implements Focusable {
|
|
|
1224
1293
|
const hints = [
|
|
1225
1294
|
`${editorKey("tui.input.submit")} save`,
|
|
1226
1295
|
`${editorKey("tui.input.newLine")} newline`,
|
|
1227
|
-
`${this.#key("tui.select.cancel")}
|
|
1296
|
+
`${this.#key("tui.select.cancel")} close, keep note`,
|
|
1228
1297
|
];
|
|
1229
1298
|
hints.push(`${formatKeyHint(appExternalEditorKey())} editor`);
|
|
1230
1299
|
return [caption, ...this.#editor.render(width), this.#theme.fg("dim", hints.join(" · "))];
|
|
@@ -1428,6 +1497,7 @@ export class AnnotationOverlay implements Focusable {
|
|
|
1428
1497
|
this.#bodyHeight,
|
|
1429
1498
|
this.#annotating ? `annotating:${this.#annotationScope}:${this.#editingAnnotationIndex}` : "",
|
|
1430
1499
|
chooser ? `chooser:${chooser.selected}:${chooser.entries.join()}` : "",
|
|
1500
|
+
this.#discardArmed ? "discard-armed" : "",
|
|
1431
1501
|
].join("|");
|
|
1432
1502
|
if (this.#nativeRoot && sig === this.#nativeSig && body === this.#nativeRootBody) return this.#nativeRoot;
|
|
1433
1503
|
|
|
@@ -1462,6 +1532,8 @@ export class AnnotationOverlay implements Focusable {
|
|
|
1462
1532
|
|
|
1463
1533
|
handleNativeEvent(event: NativeUiEvent): void {
|
|
1464
1534
|
if (this.#finished || this.#externalOperation) return;
|
|
1535
|
+
const discardArmed = this.#discardArmed;
|
|
1536
|
+
this.#discardArmed = false;
|
|
1465
1537
|
if (event.type === "action") {
|
|
1466
1538
|
if (event.act === "close") {
|
|
1467
1539
|
this.#escape();
|
|
@@ -1470,10 +1542,18 @@ export class AnnotationOverlay implements Focusable {
|
|
|
1470
1542
|
// A draft or the note chooser is open: finish or leave it first.
|
|
1471
1543
|
if (this.#annotating || this.#annotationChooser) return;
|
|
1472
1544
|
const index =
|
|
1473
|
-
event.act === "
|
|
1545
|
+
event.act === "discard"
|
|
1546
|
+
? this.#actions.indexOf(DISCARD_ANNOTATIONS_ACTION)
|
|
1547
|
+
: event.act === "review" && !this.#textSource
|
|
1548
|
+
? 0
|
|
1549
|
+
: event.act === "paste"
|
|
1550
|
+
? this.#textSource
|
|
1551
|
+
? 0
|
|
1552
|
+
: 1
|
|
1553
|
+
: -1;
|
|
1474
1554
|
if (index < 0 || this.#actionDisabled(index)) return;
|
|
1475
1555
|
this.#actionIndex = index;
|
|
1476
|
-
this.#confirmAction();
|
|
1556
|
+
this.#confirmAction(discardArmed);
|
|
1477
1557
|
return;
|
|
1478
1558
|
}
|
|
1479
1559
|
if (event.type !== "select" && event.type !== "activate") return;
|
|
@@ -1824,7 +1904,15 @@ export class AnnotationOverlay implements Focusable {
|
|
|
1824
1904
|
mark ? ({ ...button, p: { ...button.p, mark } } as NativeNode) : button;
|
|
1825
1905
|
const hasNotes = this.#textSource ? this.#textAnnotations.length > 0 : this.#annotations.length > 0;
|
|
1826
1906
|
const pasteTitle = hasNotes ? PASTE_CODE_REVIEW_ACTION : "Add a note to paste it into the prompt";
|
|
1827
|
-
const buttons: NativeNode[] = [
|
|
1907
|
+
const buttons: NativeNode[] = [
|
|
1908
|
+
actionButton("Cancel", "close", { keys: "escape" }),
|
|
1909
|
+
marked(
|
|
1910
|
+
actionButton(this.#discardArmed ? "Confirm discard" : "Discard", "discard", {
|
|
1911
|
+
title: DISCARD_ANNOTATIONS_ACTION,
|
|
1912
|
+
}),
|
|
1913
|
+
pick(this.#actions.indexOf(DISCARD_ANNOTATIONS_ACTION)),
|
|
1914
|
+
),
|
|
1915
|
+
];
|
|
1828
1916
|
if (this.#textSource) {
|
|
1829
1917
|
buttons.push(
|
|
1830
1918
|
marked(actionButton("Paste into prompt", "paste", { tone: "accent", title: pasteTitle }), pick(0)),
|
|
@@ -1860,7 +1948,7 @@ export class AnnotationOverlay implements Focusable {
|
|
|
1860
1948
|
return [
|
|
1861
1949
|
this.#hint("tui.input.submit", "save"),
|
|
1862
1950
|
this.#hint("tui.input.newLine", "new line"),
|
|
1863
|
-
this.#hint("tui.select.cancel", "
|
|
1951
|
+
this.#hint("tui.select.cancel", "close, keep note"),
|
|
1864
1952
|
this.#callbacks.onAnnotationExternalEditor ? key(appExternalEditorKey(), "editor") : undefined,
|
|
1865
1953
|
];
|
|
1866
1954
|
}
|
|
@@ -78,3 +78,27 @@ export interface TextReviewOverlayResult {
|
|
|
78
78
|
/** Source text after an external-editor edit; absent when the source was not edited. */
|
|
79
79
|
editedText?: string;
|
|
80
80
|
}
|
|
81
|
+
|
|
82
|
+
export type AnnotationOverlayDraft =
|
|
83
|
+
| {
|
|
84
|
+
kind: "diff";
|
|
85
|
+
annotations: { fileIndex: number; sourceIndex: number; annotation: CodeReviewAnnotation }[];
|
|
86
|
+
editor?: {
|
|
87
|
+
fileIndex: number;
|
|
88
|
+
sourceIndex: number;
|
|
89
|
+
scope: "file" | "line";
|
|
90
|
+
editingAnnotationIndex?: number;
|
|
91
|
+
text: string;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
| {
|
|
95
|
+
kind: "text";
|
|
96
|
+
annotations: { sourceIndex: number; annotation: TextReviewAnnotation }[];
|
|
97
|
+
editedText?: string;
|
|
98
|
+
editor?: {
|
|
99
|
+
sourceIndex: number;
|
|
100
|
+
scope: "text" | "line";
|
|
101
|
+
editingAnnotationIndex?: number;
|
|
102
|
+
text: string;
|
|
103
|
+
};
|
|
104
|
+
};
|
|
@@ -647,9 +647,9 @@ export class HookSelectorComponent extends OverlayPanel {
|
|
|
647
647
|
return;
|
|
648
648
|
}
|
|
649
649
|
|
|
650
|
-
if (matchesSelectUp(keyData)
|
|
650
|
+
if (matchesSelectUp(keyData)) {
|
|
651
651
|
this.#moveSelection(-1);
|
|
652
|
-
} else if (matchesSelectDown(keyData)
|
|
652
|
+
} else if (matchesSelectDown(keyData)) {
|
|
653
653
|
this.#moveSelection(1);
|
|
654
654
|
} else if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
655
655
|
const selected = this.#menu.selectedItem;
|
|
@@ -64,7 +64,9 @@ function urlPlace(url: string): string {
|
|
|
64
64
|
export class LoginDialogComponent extends OverlayPanel {
|
|
65
65
|
#contentContainer: Container;
|
|
66
66
|
#input: TextFormField;
|
|
67
|
-
#tui: TUI
|
|
67
|
+
#tui: Pick<TUI, "requestRender">;
|
|
68
|
+
#focused = false;
|
|
69
|
+
#useTerminalCursor = false;
|
|
68
70
|
#onComplete: (success: boolean, message?: string) => void;
|
|
69
71
|
#openUrl: (url: string) => void;
|
|
70
72
|
#abortController = new AbortController();
|
|
@@ -78,7 +80,7 @@ export class LoginDialogComponent extends OverlayPanel {
|
|
|
78
80
|
#nativeRoot: NativeNode | undefined;
|
|
79
81
|
|
|
80
82
|
constructor(
|
|
81
|
-
tui: TUI,
|
|
83
|
+
tui: Pick<TUI, "requestRender">,
|
|
82
84
|
providerId: string,
|
|
83
85
|
onComplete: (success: boolean, message?: string) => void,
|
|
84
86
|
openUrl: (url: string) => void,
|
|
@@ -98,8 +100,29 @@ export class LoginDialogComponent extends OverlayPanel {
|
|
|
98
100
|
this.#input = this.#createInput();
|
|
99
101
|
}
|
|
100
102
|
|
|
103
|
+
/** Focus reaches the live field: it draws the caret (the hardware cursor when the terminal's is on). */
|
|
104
|
+
get focused(): boolean {
|
|
105
|
+
return this.#focused;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
set focused(value: boolean) {
|
|
109
|
+
this.#focused = value;
|
|
110
|
+
this.#input.focused = value;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
setUseTerminalCursor(useTerminalCursor: boolean): void {
|
|
114
|
+
this.#useTerminalCursor = useTerminalCursor;
|
|
115
|
+
this.#input.input.setUseTerminalCursor(useTerminalCursor);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** A fresh field for the next answer; the previous one stops drawing a caret. */
|
|
119
|
+
#replaceInput(secret = false): void {
|
|
120
|
+
this.#input.focused = false;
|
|
121
|
+
this.#input = this.#createInput(secret);
|
|
122
|
+
}
|
|
123
|
+
|
|
101
124
|
#createInput(secret = false): TextFormField {
|
|
102
|
-
|
|
125
|
+
const field = new TextFormField({
|
|
103
126
|
theme: formTheme,
|
|
104
127
|
secret,
|
|
105
128
|
empty: "submit",
|
|
@@ -116,6 +139,9 @@ export class LoginDialogComponent extends OverlayPanel {
|
|
|
116
139
|
},
|
|
117
140
|
requestRender: () => this.#tui.requestRender(),
|
|
118
141
|
});
|
|
142
|
+
field.focused = this.#focused;
|
|
143
|
+
field.input.setUseTerminalCursor(this.#useTerminalCursor);
|
|
144
|
+
return field;
|
|
119
145
|
}
|
|
120
146
|
|
|
121
147
|
get signal(): AbortSignal {
|
|
@@ -194,7 +220,7 @@ export class LoginDialogComponent extends OverlayPanel {
|
|
|
194
220
|
// Keep retry chrome in place, but discard prior prompt undo/kill history.
|
|
195
221
|
const mounted = this.#contentContainer.children.indexOf(this.#input);
|
|
196
222
|
const previousInput = this.#input;
|
|
197
|
-
this.#
|
|
223
|
+
this.#replaceInput();
|
|
198
224
|
const nativeMounted = this.#nativeSteps.find(step => step.kind === "input" && step.field === previousInput);
|
|
199
225
|
if (mounted !== -1) {
|
|
200
226
|
// The flow asks again only when the last paste carried no usable code.
|
|
@@ -247,7 +273,7 @@ export class LoginDialogComponent extends OverlayPanel {
|
|
|
247
273
|
if (answered?.kind === "input") answered.answer = value;
|
|
248
274
|
}
|
|
249
275
|
// A new prompt must not recover a previous secret through undo or yank.
|
|
250
|
-
this.#
|
|
276
|
+
this.#replaceInput(prompt.secret === true);
|
|
251
277
|
if (prompt.placeholder) this.#input.input.placeholder = plainText(prompt.placeholder);
|
|
252
278
|
this.#contentContainer.addChild(new Spacer(1));
|
|
253
279
|
this.#contentContainer.addChild(new Text(theme.fg("text", prompt.message), 0, 0));
|