@linxiraos/pi-tui 1.2.0 → 1.3.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 +188 -120
- package/README.md +36 -0
- package/THIRD-PARTY-NOTICES.txt +55 -5
- package/dist/types/app-keybindings.d.ts +18 -8
- package/dist/types/apps/git/diff-pane.d.ts +5 -0
- package/dist/types/apps/git/git-tui.d.ts +16 -2
- package/dist/types/apps/git/state.d.ts +11 -0
- package/dist/types/autocomplete.d.ts +2 -0
- package/dist/types/charts/chart-svg.d.ts +7 -13
- package/dist/types/chat/advisor-message.d.ts +4 -1
- package/dist/types/chat/assistant-message.d.ts +9 -4
- package/dist/types/chat/figure-markdown.d.ts +7 -2
- package/dist/types/chat/messages.d.ts +3 -1
- package/dist/types/chat/read-tool-group.d.ts +2 -2
- package/dist/types/chat/table-chart.d.ts +11 -15
- package/dist/types/chat/tool-execution.d.ts +32 -20
- package/dist/types/chat/user-message.d.ts +3 -2
- package/dist/types/chrome/countdown-timer.d.ts +3 -2
- package/dist/types/chrome/diff.d.ts +2 -3
- package/dist/types/chrome/transcript-container.d.ts +2 -0
- package/dist/types/components/image.d.ts +2 -0
- package/dist/types/components/markdown.d.ts +49 -3
- package/dist/types/components/text.d.ts +2 -0
- package/dist/types/debug-server.d.ts +2 -0
- package/dist/types/keybinding-matchers.d.ts +6 -0
- package/dist/types/multiplexers/tmux.d.ts +1 -0
- package/dist/types/multiplexers/types.d.ts +6 -0
- package/dist/types/native/backend.d.ts +5 -4
- package/dist/types/native/blobs.d.ts +24 -12
- package/dist/types/overlays/agent-hub-projection.d.ts +10 -5
- package/dist/types/overlays/annotation-overlay.d.ts +19 -0
- package/dist/types/overlays/approval-dialog.d.ts +79 -0
- package/dist/types/overlays/ask-dialog.d.ts +11 -6
- package/dist/types/overlays/btw-history-panel.d.ts +2 -6
- package/dist/types/overlays/hook-editor.d.ts +7 -2
- package/dist/types/overlays/hook-input.d.ts +12 -4
- package/dist/types/overlays/job-tree.d.ts +50 -0
- package/dist/types/overlays/jobs-panel.d.ts +25 -11
- package/dist/types/overlays/login-dialog.d.ts +8 -3
- package/dist/types/overlays/mcp-add-wizard.d.ts +9 -1
- package/dist/types/overlays/model-browser.d.ts +6 -1
- package/dist/types/overlays/model-picker.d.ts +2 -0
- package/dist/types/overlays/pause-screen.d.ts +5 -0
- package/dist/types/overlays/plan-review-overlay.d.ts +4 -0
- package/dist/types/overlays/report-panel.d.ts +5 -1
- package/dist/types/overlays/reset-usage-selector.d.ts +7 -5
- package/dist/types/overlays/session-account-selector.d.ts +4 -0
- package/dist/types/overlays/session-info-overlay.d.ts +7 -3
- package/dist/types/overlays/session-selector.d.ts +3 -1
- package/dist/types/overlays/usage-dashboard.d.ts +6 -6
- package/dist/types/prompt/composer.d.ts +22 -26
- package/dist/types/prompt/custom-editor.d.ts +20 -10
- package/dist/types/prompt/image-references.d.ts +22 -0
- package/dist/types/prompt/internal-url-autocomplete.d.ts +1 -0
- package/dist/types/prompt/macos-spelling.d.ts +2 -1
- package/dist/types/prompt/prose-gate.d.ts +3 -0
- package/dist/types/prompt/queued-messages.d.ts +1 -1
- package/dist/types/prompt/typing-word.d.ts +25 -0
- package/dist/types/prompt/welcome.d.ts +35 -43
- package/dist/types/render/hanging-wrap.d.ts +7 -0
- package/dist/types/render/hyperlink.d.ts +2 -0
- package/dist/types/render/render-utils.d.ts +9 -1
- package/dist/types/render/utils.d.ts +1 -1
- package/dist/types/setup/scenes/splash.d.ts +6 -5
- package/dist/types/status-line/context-usage.d.ts +4 -1
- package/dist/types/status-line/host.d.ts +6 -4
- package/dist/types/status-line/startup.d.ts +6 -6
- package/dist/types/status-line/types.d.ts +3 -0
- package/dist/types/stdin-buffer.d.ts +7 -0
- package/dist/types/terminal-capabilities.d.ts +16 -1
- package/dist/types/terminal-multiplexer.d.ts +7 -0
- package/dist/types/terminal.d.ts +8 -4
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme.d.ts +11 -0
- package/dist/types/thinking.d.ts +5 -0
- package/dist/types/tmux.d.ts +39 -4
- package/dist/types/tools/ast-edit.d.ts +2 -2
- package/dist/types/tools/ast-grep.d.ts +2 -2
- package/dist/types/tools/bash.d.ts +6 -1
- package/dist/types/tools/edit.d.ts +2 -0
- package/dist/types/tools/eval.d.ts +53 -4
- package/dist/types/tools/find.d.ts +14 -4
- package/dist/types/tools/glob.d.ts +3 -3
- package/dist/types/tools/grep.d.ts +2 -2
- package/dist/types/tools/grouped-file-output.d.ts +9 -4
- package/dist/types/tools/native-view.d.ts +1 -1
- package/dist/types/tools/read.d.ts +32 -10
- package/dist/types/tools/renderer.d.ts +2 -0
- package/dist/types/tools/task.d.ts +31 -0
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/tui.d.ts +20 -3
- package/package.json +10 -10
- package/src/app-keybindings.d.ts +18 -8
- package/src/app-keybindings.ts +20 -8
- package/src/apps/autoresearch-dashboard.ts +444 -27
- package/src/apps/debug/raw-sse.ts +2 -2
- package/src/apps/git/diff-pane.ts +39 -41
- package/src/apps/git/git-tui.ts +80 -14
- package/src/apps/git/sidebar.ts +10 -1
- package/src/apps/git/state.ts +12 -0
- package/src/autocomplete.d.ts +2 -0
- package/src/autocomplete.ts +21 -19
- package/src/bracketed-paste.ts +9 -2
- package/src/charts/chart-svg.d.ts +7 -13
- package/src/charts/chart-svg.ts +19 -22
- package/src/chat/advisor-message.d.ts +4 -1
- package/src/chat/advisor-message.ts +77 -24
- package/src/chat/assistant-message.d.ts +9 -4
- package/src/chat/assistant-message.ts +127 -31
- package/src/chat/bash-execution.ts +7 -2
- package/src/chat/figure-markdown.d.ts +7 -2
- package/src/chat/figure-markdown.ts +10 -3
- package/src/chat/image-loading.ts +9 -6
- package/src/chat/messages.d.ts +3 -1
- package/src/chat/messages.ts +4 -1
- package/src/chat/read-tool-group.d.ts +2 -2
- package/src/chat/read-tool-group.ts +97 -42
- package/src/chat/svg-figure.ts +3 -1
- package/src/chat/svg-source.ts +6 -3
- package/src/chat/table-chart.d.ts +11 -15
- package/src/chat/table-chart.ts +24 -57
- package/src/chat/tool-execution.d.ts +32 -20
- package/src/chat/tool-execution.ts +103 -36
- package/src/chat/user-message.d.ts +3 -2
- package/src/chat/user-message.ts +54 -34
- package/src/chrome/countdown-timer.ts +3 -3
- package/src/chrome/diff.d.ts +2 -3
- package/src/chrome/diff.ts +4 -9
- package/src/chrome/transcript-container.d.ts +2 -0
- package/src/chrome/transcript-container.ts +212 -28
- package/src/components/editor.ts +12 -12
- package/src/components/image.d.ts +2 -0
- package/src/components/image.ts +54 -20
- package/src/components/markdown.d.ts +49 -3
- package/src/components/markdown.ts +461 -11
- package/src/components/settings-list.ts +2 -4
- package/src/components/text.d.ts +2 -0
- package/src/components/text.ts +11 -1
- package/src/debug-server.d.ts +2 -0
- package/src/debug-server.ts +51 -69
- package/src/hotkeys-markdown.ts +2 -0
- package/src/keybinding-matchers.d.ts +6 -0
- package/src/keybinding-matchers.ts +19 -0
- package/src/multiplexers/tmux.d.ts +1 -0
- package/src/multiplexers/tmux.ts +3 -0
- package/src/multiplexers/types.d.ts +6 -0
- package/src/multiplexers/types.ts +6 -0
- package/src/native/backend.d.ts +5 -4
- package/src/native/backend.ts +13 -5
- package/src/native/blobs.d.ts +24 -12
- package/src/native/blobs.ts +56 -7
- package/src/native/reconcile.ts +43 -21
- package/src/overlays/advisor-config.ts +259 -8
- package/src/overlays/agent-hub-projection.d.ts +10 -5
- package/src/overlays/agent-hub-projection.ts +63 -12
- package/src/overlays/agent-hub.ts +2 -2
- package/src/overlays/annotation-overlay.ts +422 -168
- package/src/overlays/approval-dialog.ts +389 -0
- package/src/overlays/ask-dialog.ts +244 -105
- package/src/overlays/btw-history-panel.ts +208 -147
- package/src/overlays/extensions/extension-list.ts +4 -6
- package/src/overlays/hook-editor.ts +90 -32
- package/src/overlays/hook-input.ts +105 -19
- package/src/overlays/job-tree.ts +177 -0
- package/src/overlays/jobs-panel.ts +222 -142
- package/src/overlays/login-dialog.ts +201 -91
- package/src/overlays/mcp-add-wizard.ts +562 -279
- package/src/overlays/model-browser.ts +32 -7
- package/src/overlays/model-hub.ts +4 -10
- package/src/overlays/model-picker.ts +58 -21
- package/src/overlays/pause-screen.ts +81 -23
- package/src/overlays/plan-review-overlay.ts +16 -0
- package/src/overlays/plugin-selector.ts +17 -1
- package/src/overlays/plugin-settings.ts +8 -2
- package/src/overlays/queue-mode-selector.ts +10 -0
- package/src/overlays/report-panel.ts +6 -2
- package/src/overlays/reset-usage-selector.ts +130 -21
- package/src/overlays/session-account-selector.ts +36 -0
- package/src/overlays/session-info-overlay.ts +199 -76
- package/src/overlays/session-selector.ts +15 -10
- package/src/overlays/settings-selector.ts +3 -0
- package/src/overlays/show-images-selector.ts +1 -0
- package/src/overlays/snapcompact-shape-preview.ts +13 -6
- package/src/overlays/theme-selector.ts +6 -1
- package/src/overlays/thinking-selector.ts +8 -4
- package/src/overlays/tree-selector.ts +117 -2
- package/src/overlays/usage-dashboard.ts +498 -144
- package/src/prompt/composer-cache.ts +4 -6
- package/src/prompt/composer.ts +196 -96
- package/src/prompt/custom-editor.ts +168 -65
- package/src/prompt/image-references.d.ts +22 -0
- package/src/prompt/image-references.ts +79 -2
- package/src/prompt/internal-url-autocomplete.ts +1 -0
- package/src/prompt/macos-spelling.ts +28 -5
- package/src/prompt/prose-gate.ts +3 -0
- package/src/prompt/queued-messages.ts +9 -2
- package/src/prompt/tips.txt +1 -2
- package/src/prompt/typing-word.ts +59 -0
- package/src/prompt/welcome.ts +256 -423
- package/src/render/code-cell.ts +18 -5
- package/src/render/hanging-wrap.d.ts +7 -0
- package/src/render/hanging-wrap.ts +58 -0
- package/src/render/hyperlink.d.ts +2 -0
- package/src/render/hyperlink.ts +5 -0
- package/src/render/render-utils.d.ts +9 -1
- package/src/render/render-utils.ts +16 -2
- package/src/render/utils.d.ts +1 -1
- package/src/render/utils.ts +1 -1
- package/src/render/width-aware-text.ts +6 -5
- package/src/setup/scenes/splash.ts +137 -60
- package/src/status-line/component.ts +23 -10
- package/src/status-line/context-usage.ts +176 -73
- package/src/status-line/footer.ts +18 -35
- package/src/status-line/host.ts +6 -1
- package/src/status-line/segments.ts +16 -5
- package/src/status-line/startup.ts +6 -6
- package/src/status-line/types.ts +1 -0
- package/src/stdin-buffer.d.ts +7 -0
- package/src/stdin-buffer.ts +11 -1
- package/src/terminal-capabilities.d.ts +16 -1
- package/src/terminal-capabilities.ts +66 -28
- package/src/terminal-multiplexer.d.ts +7 -0
- package/src/terminal-multiplexer.ts +14 -0
- package/src/terminal.d.ts +8 -4
- package/src/terminal.ts +160 -46
- package/src/theme/symbols.d.ts +1 -1
- package/src/theme/symbols.ts +1 -0
- package/src/theme/theme-class.ts +1 -0
- package/src/theme/theme.d.ts +11 -0
- package/src/theme/theme.ts +46 -14
- package/src/thinking.d.ts +5 -0
- package/src/thinking.ts +9 -1
- package/src/tmux.d.ts +39 -4
- package/src/tmux.ts +110 -16
- package/src/tools/ask.ts +20 -9
- package/src/tools/ast-edit.d.ts +2 -2
- package/src/tools/ast-edit.ts +30 -7
- package/src/tools/ast-grep.d.ts +2 -2
- package/src/tools/ast-grep.ts +39 -35
- package/src/tools/bash.d.ts +6 -1
- package/src/tools/bash.ts +15 -9
- package/src/tools/debug.ts +2 -1
- package/src/tools/default-renderer.ts +3 -2
- package/src/tools/edit.d.ts +2 -0
- package/src/tools/edit.ts +100 -21
- package/src/tools/eval.d.ts +53 -4
- package/src/tools/eval.ts +173 -137
- package/src/tools/find.d.ts +14 -4
- package/src/tools/find.ts +71 -18
- package/src/tools/glob.d.ts +3 -3
- package/src/tools/glob.ts +51 -17
- package/src/tools/grep.d.ts +2 -2
- package/src/tools/grep.ts +99 -22
- package/src/tools/grouped-file-output.d.ts +9 -4
- package/src/tools/grouped-file-output.ts +21 -6
- package/src/tools/lsp.ts +30 -18
- package/src/tools/mcp.ts +2 -2
- package/src/tools/native-view.d.ts +1 -1
- package/src/tools/native-view.ts +2 -2
- package/src/tools/read.d.ts +32 -10
- package/src/tools/read.ts +89 -43
- package/src/tools/renderer.d.ts +2 -0
- package/src/tools/renderer.ts +2 -0
- package/src/tools/task.d.ts +31 -0
- package/src/tools/task.ts +40 -7
- package/src/tools/todo.ts +3 -2
- package/src/tools/wait.ts +16 -30
- package/src/tools/web-search.ts +2 -1
- package/src/tools/write.d.ts +2 -0
- package/src/tools/write.ts +36 -12
- package/src/tools/yield.ts +2 -2
- package/src/tui.d.ts +20 -3
- package/src/tui.ts +140 -36
- package/src/utils.ts +5 -3
- package/dist/types/tools/eval-format/code-mode-declarations.d.ts +0 -5
- package/src/tools/eval-format/code-mode-declarations.ts +0 -61
|
@@ -104,12 +104,37 @@ interface TranscriptEntry {
|
|
|
104
104
|
* so a hit is exact.
|
|
105
105
|
*/
|
|
106
106
|
imageScan: { width: number; protocol: string | null; hasImageRows: boolean } | undefined;
|
|
107
|
+
/**
|
|
108
|
+
* Head rows of a settled block already in native history while the rest
|
|
109
|
+
* stays live: pressure commits only the rows that left the screen, so no
|
|
110
|
+
* committed row is ever still visible above the viewport.
|
|
111
|
+
*/
|
|
112
|
+
split: RowSplit | undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* A committed physical row prefix of one block. `rows` counts trimmed render
|
|
117
|
+
* rows at `width` (any emitted stable prefix included) and `hash` fingerprints
|
|
118
|
+
* them; `text` is their letters and digits in order. A settled block can still
|
|
119
|
+
* change (a tool pane expanding) or rewrap at another width without a replay,
|
|
120
|
+
* so every render re-locates the seam: the committed rows when they render
|
|
121
|
+
* unchanged, else by continuing `text` — per-row decoration (quote bars,
|
|
122
|
+
* borders, rules) is punctuation it ignores.
|
|
123
|
+
*/
|
|
124
|
+
interface RowSplit {
|
|
125
|
+
readonly rows: number;
|
|
126
|
+
readonly width: number;
|
|
127
|
+
readonly hash: number | bigint;
|
|
128
|
+
readonly text: string;
|
|
129
|
+
/** Seam of the last render it was located in; renders are memoized per frame. */
|
|
130
|
+
located?: { readonly rendered: readonly string[]; readonly count: number };
|
|
107
131
|
}
|
|
108
132
|
|
|
109
133
|
type RetirementPolicy = "pressure" | "flush";
|
|
110
134
|
type Offered =
|
|
111
135
|
| { batch: HistoryBatch; kind: "append"; entry: number; emittedEnd: number }
|
|
112
|
-
|
|
136
|
+
/** Whole blocks `[frontier, end)`, then optionally the head rows of block `end`. */
|
|
137
|
+
| { batch: HistoryBatch; kind: "commit"; end: number; split?: RowSplit }
|
|
113
138
|
| { batch: HistoryBatch; kind: "replay" };
|
|
114
139
|
|
|
115
140
|
/** Rows a progressive-append retirement offers, and the stable count they bring the head to. */
|
|
@@ -148,6 +173,24 @@ function isPlainBlank(line: string): boolean {
|
|
|
148
173
|
return !/\S/.test(line);
|
|
149
174
|
}
|
|
150
175
|
|
|
176
|
+
/** Letters and digits of `rows` in reading order: the content rewrapping preserves. */
|
|
177
|
+
function contentOf(rows: readonly string[]): string {
|
|
178
|
+
let text = "";
|
|
179
|
+
for (const row of rows) text += Bun.stripANSI(row).replace(/[^\p{L}\p{N}]/gu, "");
|
|
180
|
+
return text;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Split committing the first `rows` rows of `scrollback` at `width`. The
|
|
185
|
+
* fingerprint covers the scrollback bytes history receives, which can differ
|
|
186
|
+
* from the measured render at the same row count (a SIXEL image encodes
|
|
187
|
+
* synchronously instead of returning its reserved rows).
|
|
188
|
+
*/
|
|
189
|
+
function rowSplit(scrollback: readonly string[], rows: number, width: number): RowSplit {
|
|
190
|
+
const committed = scrollback.slice(0, rows);
|
|
191
|
+
return { rows, width, hash: Bun.hash(committed.join("\n")), text: contentOf(committed) };
|
|
192
|
+
}
|
|
193
|
+
|
|
151
194
|
/** Whether `prefix` matches `rows` byte-for-byte from the top. */
|
|
152
195
|
export function isRowPrefix(prefix: readonly string[], rows: readonly string[]): boolean {
|
|
153
196
|
if (prefix.length > rows.length) return false;
|
|
@@ -235,6 +278,7 @@ export class TranscriptContainer extends Container {
|
|
|
235
278
|
emitted: 0,
|
|
236
279
|
stableFrozen: false,
|
|
237
280
|
imageScan: undefined,
|
|
281
|
+
split: undefined,
|
|
238
282
|
});
|
|
239
283
|
// `children` is public and may be edited externally; re-check the whole
|
|
240
284
|
// list once instead of trusting positions. Insert-after-anchor callers
|
|
@@ -309,13 +353,16 @@ export class TranscriptContainer extends Container {
|
|
|
309
353
|
*/
|
|
310
354
|
resetStableEmission(): void {
|
|
311
355
|
this.#syncEntries();
|
|
312
|
-
if (this.#offered?.kind === "append"
|
|
356
|
+
if (this.#offered?.kind === "append" || (this.#offered?.kind === "commit" && this.#offered.split)) {
|
|
357
|
+
this.#offered = undefined;
|
|
358
|
+
}
|
|
313
359
|
for (const entry of this.#entries) {
|
|
314
360
|
entry.emitted = 0;
|
|
315
361
|
entry.stableRows = EMPTY_STABLE_ROWS;
|
|
316
362
|
entry.renderedStableByWidth = new Map();
|
|
317
363
|
entry.stableRowCountByWidth = new Map();
|
|
318
364
|
entry.stableFrozen = false;
|
|
365
|
+
entry.split = undefined;
|
|
319
366
|
if (entry.mode === "appendOnly") {
|
|
320
367
|
(entry.component as Component & AppendOnlyTranscriptBlock).resetTranscriptStableRows?.();
|
|
321
368
|
}
|
|
@@ -338,8 +385,8 @@ export class TranscriptContainer extends Container {
|
|
|
338
385
|
const index = this.#entries.findLastIndex(entry => entry.component === component);
|
|
339
386
|
if (index < 0) return -1;
|
|
340
387
|
const entry = this.#entries[index]!;
|
|
341
|
-
if (entry.state === "committed" || entry.emitted > 0) return -1;
|
|
342
|
-
if (this.#offered?.kind === "commit" && index < this.#offered.end) return -1;
|
|
388
|
+
if (entry.state === "committed" || entry.emitted > 0 || entry.split !== undefined) return -1;
|
|
389
|
+
if (this.#offered?.kind === "commit" && index < this.#offered.end + (this.#offered.split ? 1 : 0)) return -1;
|
|
343
390
|
if (this.#offered?.kind === "append" && index === this.#offered.entry) return -1;
|
|
344
391
|
return index;
|
|
345
392
|
}
|
|
@@ -374,6 +421,16 @@ export class TranscriptContainer extends Container {
|
|
|
374
421
|
return this.#entries.map(entry => entry.emitted);
|
|
375
422
|
}
|
|
376
423
|
|
|
424
|
+
/** Whether any block is still streaming or otherwise unfinalized. */
|
|
425
|
+
hasActiveBlock(): boolean {
|
|
426
|
+
this.#syncEntries();
|
|
427
|
+
this.#settleFinalized();
|
|
428
|
+
for (let index = this.#frontier; index < this.#entries.length; index++) {
|
|
429
|
+
if (this.#entries[index]!.state === "active") return true;
|
|
430
|
+
}
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
|
|
377
434
|
/** Whether visible active capacity and live-block memory permit another admission. */
|
|
378
435
|
canAdmit(rows: number): boolean {
|
|
379
436
|
const active = this.#entries.filter(entry => entry.state === "active").length;
|
|
@@ -443,17 +500,21 @@ export class TranscriptContainer extends Container {
|
|
|
443
500
|
|
|
444
501
|
/**
|
|
445
502
|
* One block's blank-trimmed render at its full-height allocation. Inside an
|
|
446
|
-
* open frame the first measurement of each block is replayed to later ones
|
|
503
|
+
* open frame the first measurement of each block is replayed to later ones,
|
|
504
|
+
* leaving whatever allocation the viewport has since rendered it at.
|
|
447
505
|
*/
|
|
448
506
|
#measuredRows(entry: TranscriptEntry, width: number): readonly string[] {
|
|
449
|
-
this.#
|
|
450
|
-
|
|
507
|
+
if (this.#openFrame === undefined) {
|
|
508
|
+
this.#setAllocation(entry.component, Number.MAX_SAFE_INTEGER, this.#lastFrame);
|
|
509
|
+
return this.#renderEntry(entry, width);
|
|
510
|
+
}
|
|
451
511
|
if (this.#frameRowsWidth !== width) {
|
|
452
512
|
this.#frameRows.clear();
|
|
453
513
|
this.#frameRowsWidth = width;
|
|
454
514
|
}
|
|
455
515
|
let rows = this.#frameRows.get(entry);
|
|
456
516
|
if (rows === undefined) {
|
|
517
|
+
this.#setAllocation(entry.component, Number.MAX_SAFE_INTEGER, this.#lastFrame);
|
|
457
518
|
rows = this.#renderEntry(entry, width);
|
|
458
519
|
this.#frameRows.set(entry, rows);
|
|
459
520
|
}
|
|
@@ -655,6 +716,20 @@ export class TranscriptContainer extends Container {
|
|
|
655
716
|
rows = after.slice(before.length);
|
|
656
717
|
} else if (offered.kind === "commit") {
|
|
657
718
|
rows = this.#renderRange(this.#frontier, offered.end, width, true).rows;
|
|
719
|
+
const head = offered.split ? this.#entries[offered.end] : undefined;
|
|
720
|
+
if (head !== undefined && offered.split !== undefined) {
|
|
721
|
+
// The offer's fingerprint covers bytes of a discarded frame history
|
|
722
|
+
// never received (an image-budget retry demotes graphics to text),
|
|
723
|
+
// so the split keeps its cut and re-fingerprints the rows it now
|
|
724
|
+
// delivers. Only another width relocates the cut.
|
|
725
|
+
const start = this.#emittedRowCount(head, width);
|
|
726
|
+
const scrollback = renderForScrollback(() => this.#renderEntry(head, width));
|
|
727
|
+
const cut =
|
|
728
|
+
width === offered.split.width ? offered.split.rows : this.#splitRowCount(head, offered.split, width);
|
|
729
|
+
const end = Math.min(cut, scrollback.length);
|
|
730
|
+
offered.split = rowSplit(scrollback, end, width);
|
|
731
|
+
rows = [...rows, ...scrollback.slice(start, end)];
|
|
732
|
+
}
|
|
658
733
|
} else {
|
|
659
734
|
rows = this.#renderReplay(width);
|
|
660
735
|
}
|
|
@@ -683,6 +758,7 @@ export class TranscriptContainer extends Container {
|
|
|
683
758
|
head?.mode === "appendOnly" &&
|
|
684
759
|
!head.stableFrozen &&
|
|
685
760
|
head.state !== "committed" &&
|
|
761
|
+
head.split === undefined &&
|
|
686
762
|
head.emitted < head.stableRows.length
|
|
687
763
|
? head
|
|
688
764
|
: undefined;
|
|
@@ -762,9 +838,26 @@ export class TranscriptContainer extends Container {
|
|
|
762
838
|
limit = Math.max(limit, this.#frontier + (this.#liveCount() - (MAX_LIVE_BLOCKS - 1)));
|
|
763
839
|
}
|
|
764
840
|
if (policy === "flush") limit = this.#entries.length;
|
|
841
|
+
// The block straddling the top edge commits only the rows that left the
|
|
842
|
+
// screen; the rest stays live, so a later shrink of the chrome or the
|
|
843
|
+
// tail refills the screen from live rows instead of opening a band no
|
|
844
|
+
// frame can fill (rows still on screen cannot return from scrollback).
|
|
845
|
+
let split: { index: number; rows: readonly string[]; split: RowSplit } | undefined;
|
|
846
|
+
if (policy === "pressure" && keep > 0 && limit === this.#frontier + keep) {
|
|
847
|
+
const boundary = live[keep - 1]!;
|
|
848
|
+
const visible = room - tailRows - (tailRows > 0 ? 1 : 0);
|
|
849
|
+
if (boundary.entry.state === "settled" && visible > 0) {
|
|
850
|
+
const offer = this.#splitOffer(boundary.entry, width, visible);
|
|
851
|
+
if (offer !== undefined) {
|
|
852
|
+
split = { index: boundary.index, ...offer };
|
|
853
|
+
limit = boundary.index;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}
|
|
765
857
|
let end = this.#frontier;
|
|
766
858
|
while (end < limit && end < this.#entries.length && this.#entries[end]!.state === "settled") end++;
|
|
767
|
-
if (end
|
|
859
|
+
if (split !== undefined && end !== split.index) split = undefined;
|
|
860
|
+
if (end === this.#frontier && split === undefined) {
|
|
768
861
|
if (policy === "pressure") this.#notePinnedFrontier();
|
|
769
862
|
return undefined;
|
|
770
863
|
}
|
|
@@ -784,15 +877,35 @@ export class TranscriptContainer extends Container {
|
|
|
784
877
|
} finally {
|
|
785
878
|
popLoopPhase();
|
|
786
879
|
}
|
|
880
|
+
if (split !== undefined && retirement.end !== split.index) split = undefined;
|
|
787
881
|
const batch: HistoryBatch = {
|
|
788
882
|
id: this.#nextBatchId++,
|
|
789
|
-
rows: retirement.rows,
|
|
883
|
+
rows: split === undefined ? retirement.rows : [...retirement.rows, ...split.rows],
|
|
790
884
|
kind: "append",
|
|
791
885
|
};
|
|
792
|
-
this.#offered = { batch, end: retirement.end, kind: "commit" };
|
|
886
|
+
this.#offered = { batch, end: retirement.end, kind: "commit", split: split?.split };
|
|
793
887
|
return batch;
|
|
794
888
|
}
|
|
795
889
|
|
|
890
|
+
/**
|
|
891
|
+
* The head rows of settled `entry` beyond its committed prefix that leave
|
|
892
|
+
* `visible` rows live, or undefined when nothing would remain committed
|
|
893
|
+
* past that prefix or the scrollback render would not line up row for row.
|
|
894
|
+
*/
|
|
895
|
+
#splitOffer(
|
|
896
|
+
entry: TranscriptEntry,
|
|
897
|
+
width: number,
|
|
898
|
+
visible: number,
|
|
899
|
+
): { rows: readonly string[]; split: RowSplit } | undefined {
|
|
900
|
+
const measured = this.#measuredRows(entry, width);
|
|
901
|
+
const start = this.#emittedRowCount(entry, width);
|
|
902
|
+
const total = measured.length - visible;
|
|
903
|
+
if (total <= start) return undefined;
|
|
904
|
+
const scrollback = renderForScrollback(() => this.#renderEntry(entry, width));
|
|
905
|
+
if (scrollback.length !== measured.length) return undefined;
|
|
906
|
+
return { rows: scrollback.slice(start, total), split: rowSplit(scrollback, total, width) };
|
|
907
|
+
}
|
|
908
|
+
|
|
796
909
|
/** Acknowledges exactly the most recently offered append, commit, or replay transaction. */
|
|
797
910
|
acknowledgeFinalizedBatch(id: number): void {
|
|
798
911
|
const offered = this.#offered;
|
|
@@ -809,6 +922,8 @@ export class TranscriptContainer extends Container {
|
|
|
809
922
|
this.#retireEntry(this.#entries[index]!);
|
|
810
923
|
}
|
|
811
924
|
this.#frontier = offered.end;
|
|
925
|
+
const head = offered.split ? this.#entries[offered.end] : undefined;
|
|
926
|
+
if (head !== undefined) head.split = offered.split;
|
|
812
927
|
} else {
|
|
813
928
|
this.#releaseCommittedRenderCaches();
|
|
814
929
|
}
|
|
@@ -951,14 +1066,32 @@ export class TranscriptContainer extends Container {
|
|
|
951
1066
|
}
|
|
952
1067
|
|
|
953
1068
|
/**
|
|
954
|
-
*
|
|
955
|
-
*
|
|
956
|
-
*
|
|
957
|
-
* purely to read `.length` off the result.
|
|
1069
|
+
* Rows of `entry` at `width` already in native history or in the
|
|
1070
|
+
* unacknowledged offer: the row split when one is pending or committed,
|
|
1071
|
+
* else its emitted stable prefix.
|
|
958
1072
|
*/
|
|
959
1073
|
#projectedEmittedRowCount(entry: TranscriptEntry, index: number, width: number): number {
|
|
960
1074
|
const offered = this.#offered;
|
|
961
|
-
|
|
1075
|
+
if (offered?.kind === "commit" && offered.split !== undefined && offered.end === index) {
|
|
1076
|
+
return this.#splitRowCount(entry, offered.split, width);
|
|
1077
|
+
}
|
|
1078
|
+
if (offered?.kind === "append" && offered.entry === index && entry.split === undefined) {
|
|
1079
|
+
return this.#stablePrefixRowCount(entry, offered.emittedEnd, width);
|
|
1080
|
+
}
|
|
1081
|
+
return this.#emittedRowCount(entry, width);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
/** Rows of `entry` at `width` already acknowledged into native history. */
|
|
1085
|
+
#emittedRowCount(entry: TranscriptEntry, width: number): number {
|
|
1086
|
+
if (entry.split !== undefined) return this.#splitRowCount(entry, entry.split, width);
|
|
1087
|
+
return this.#stablePrefixRowCount(entry, entry.emitted, width);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* Length-only variant of `#renderStablePrefix`: answers the emitted row
|
|
1092
|
+
* count without re-rendering the prefix when this width already recorded it.
|
|
1093
|
+
*/
|
|
1094
|
+
#stablePrefixRowCount(entry: TranscriptEntry, count: number, width: number): number {
|
|
962
1095
|
if (count === 0) return 0;
|
|
963
1096
|
const perCount = entry.stableRowCountByWidth.get(width);
|
|
964
1097
|
const memo = perCount?.get(Math.min(count, entry.stableRows.length));
|
|
@@ -966,6 +1099,39 @@ export class TranscriptContainer extends Container {
|
|
|
966
1099
|
return this.#renderStablePrefix(entry, count, width).length;
|
|
967
1100
|
}
|
|
968
1101
|
|
|
1102
|
+
/**
|
|
1103
|
+
* Rows of `entry`'s current render at `width` that `split` committed: all
|
|
1104
|
+
* of them while they render unchanged, else the longest prefix whose
|
|
1105
|
+
* content continues the committed text exactly. A row the seam falls
|
|
1106
|
+
* inside, a row the block changed, a row with no content (a rule or other
|
|
1107
|
+
* punctuation the block revealed), or one whose decoration reads as
|
|
1108
|
+
* content stays live: it can repeat committed text but never drop any.
|
|
1109
|
+
*/
|
|
1110
|
+
#splitRowCount(entry: TranscriptEntry, split: RowSplit, width: number): number {
|
|
1111
|
+
const rendered = this.#measuredRows(entry, width);
|
|
1112
|
+
if (split.located?.rendered === rendered) return split.located.count;
|
|
1113
|
+
let count = 0;
|
|
1114
|
+
if (
|
|
1115
|
+
width === split.width &&
|
|
1116
|
+
rendered.length >= split.rows &&
|
|
1117
|
+
Bun.hash(rendered.slice(0, split.rows).join("\n")) === split.hash
|
|
1118
|
+
) {
|
|
1119
|
+
count = split.rows;
|
|
1120
|
+
} else {
|
|
1121
|
+
let consumed = 0;
|
|
1122
|
+
while (count < rendered.length && consumed < split.text.length) {
|
|
1123
|
+
const row = contentOf([rendered[count]!]);
|
|
1124
|
+
// An empty-content row matches at any offset, so the walk cannot
|
|
1125
|
+
// tell it apart from a revealed row history never received.
|
|
1126
|
+
if (row.length === 0 || !split.text.startsWith(row, consumed)) break;
|
|
1127
|
+
consumed += row.length;
|
|
1128
|
+
count++;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
split.located = { rendered, count };
|
|
1132
|
+
return count;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
969
1135
|
/**
|
|
970
1136
|
* Size a progressive-append batch from the stable renders `#renderEntry`
|
|
971
1137
|
* recorded at this width, without rendering any prefix. Each recorded count
|
|
@@ -973,7 +1139,8 @@ export class TranscriptContainer extends Container {
|
|
|
973
1139
|
* `renderedStableByWidth` holds the newest, so each count's rows are a
|
|
974
1140
|
* prefix of it and the batch is one slice. Undefined when a count the walk
|
|
975
1141
|
* needs was never recorded here (published between renders, or before a
|
|
976
|
-
* resize); the rendered walk then decides.
|
|
1142
|
+
* resize); the rendered walk then decides. Like the rendered walk, it never
|
|
1143
|
+
* passes `overflow`: a prefix reaching onto the screen waits.
|
|
977
1144
|
*/
|
|
978
1145
|
#measuredAppendBatch(entry: TranscriptEntry, width: number, overflow: number): AppendBatch | undefined {
|
|
979
1146
|
const counts = entry.stableRowCountByWidth.get(width);
|
|
@@ -984,7 +1151,7 @@ export class TranscriptContainer extends Container {
|
|
|
984
1151
|
if (start === undefined) return undefined;
|
|
985
1152
|
let emittedEnd = entry.emitted;
|
|
986
1153
|
let end = start;
|
|
987
|
-
while (emittedEnd < target
|
|
1154
|
+
while (emittedEnd < target) {
|
|
988
1155
|
const next = counts.get(emittedEnd + 1);
|
|
989
1156
|
if (next === undefined) return undefined;
|
|
990
1157
|
if (next <= start) {
|
|
@@ -993,6 +1160,7 @@ export class TranscriptContainer extends Container {
|
|
|
993
1160
|
}
|
|
994
1161
|
break;
|
|
995
1162
|
}
|
|
1163
|
+
if (next - start > overflow) break;
|
|
996
1164
|
end = next;
|
|
997
1165
|
emittedEnd += 1;
|
|
998
1166
|
}
|
|
@@ -1001,15 +1169,18 @@ export class TranscriptContainer extends Container {
|
|
|
1001
1169
|
|
|
1002
1170
|
/**
|
|
1003
1171
|
* Size a progressive-append batch by rendering each further stable prefix:
|
|
1004
|
-
* extend the emitted prefix
|
|
1005
|
-
*
|
|
1006
|
-
*
|
|
1172
|
+
* extend the emitted prefix while its new rows stay within `overflow` — the
|
|
1173
|
+
* rows that left the screen — stopping at the first prefix that would reach
|
|
1174
|
+
* onto it, adds no row, or stops extending the emitted one (freezing the
|
|
1175
|
+
* block when that is the very next prefix). A prefix still partly on screen
|
|
1176
|
+
* stays live: committing it would strand those rows above the live tail,
|
|
1177
|
+
* where a later shrink of the chrome below opens a band no frame can fill.
|
|
1007
1178
|
*/
|
|
1008
1179
|
#renderedAppendBatch(entry: TranscriptEntry, width: number, overflow: number): AppendBatch {
|
|
1009
1180
|
const before = this.#renderStablePrefix(entry, entry.emitted, width);
|
|
1010
1181
|
let emittedEnd = entry.emitted;
|
|
1011
1182
|
let after = before;
|
|
1012
|
-
while (emittedEnd < entry.stableRows.length
|
|
1183
|
+
while (emittedEnd < entry.stableRows.length) {
|
|
1013
1184
|
const next = this.#renderStablePrefix(entry, emittedEnd + 1, width);
|
|
1014
1185
|
if (!isRowPrefix(before, next) || next.length === before.length) {
|
|
1015
1186
|
if (emittedEnd === entry.emitted) {
|
|
@@ -1017,6 +1188,7 @@ export class TranscriptContainer extends Container {
|
|
|
1017
1188
|
}
|
|
1018
1189
|
break;
|
|
1019
1190
|
}
|
|
1191
|
+
if (next.length - before.length > overflow) break;
|
|
1020
1192
|
after = next;
|
|
1021
1193
|
emittedEnd += 1;
|
|
1022
1194
|
}
|
|
@@ -1078,7 +1250,7 @@ export class TranscriptContainer extends Container {
|
|
|
1078
1250
|
const rendered = renderForScrollback(() =>
|
|
1079
1251
|
index === start ? this.#renderEntry(entry, width) : trimBlankEdges(entry.component.render(width)),
|
|
1080
1252
|
);
|
|
1081
|
-
const emittedRows = index === start ? this.#
|
|
1253
|
+
const emittedRows = index === start ? this.#emittedRowCount(entry, width) : 0;
|
|
1082
1254
|
const block = rendered.slice(emittedRows);
|
|
1083
1255
|
reached = index + 1;
|
|
1084
1256
|
if (block.length > 0) {
|
|
@@ -1094,7 +1266,11 @@ export class TranscriptContainer extends Container {
|
|
|
1094
1266
|
#renderReplay(width: number): readonly string[] {
|
|
1095
1267
|
const rows = Array.from(this.#renderRange(0, this.#frontier, width, true).rows);
|
|
1096
1268
|
const head = this.#entries[this.#frontier];
|
|
1097
|
-
if (head?.
|
|
1269
|
+
if (head?.split !== undefined) {
|
|
1270
|
+
this.#setAllocation(head.component, Number.MAX_SAFE_INTEGER, this.#lastFrame);
|
|
1271
|
+
const committed = this.#splitRowCount(head, head.split, width);
|
|
1272
|
+
rows.push(...renderForScrollback(() => this.#renderEntry(head, width)).slice(0, committed));
|
|
1273
|
+
} else if (head?.mode === "appendOnly" && head.emitted > 0) {
|
|
1098
1274
|
this.#setAllocation(head.component, Number.MAX_SAFE_INTEGER, this.#lastFrame);
|
|
1099
1275
|
this.#renderEntry(head, width);
|
|
1100
1276
|
rows.push(...this.#renderStablePrefix(head, head.emitted, width));
|
|
@@ -1105,10 +1281,15 @@ export class TranscriptContainer extends Container {
|
|
|
1105
1281
|
#completeFullyEmittedHeads(width: number): void {
|
|
1106
1282
|
while (this.#frontier < this.#entries.length) {
|
|
1107
1283
|
const entry = this.#entries[this.#frontier]!;
|
|
1108
|
-
if (entry.
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1284
|
+
if (entry.state !== "settled") return;
|
|
1285
|
+
if (entry.split !== undefined) {
|
|
1286
|
+
if (this.#splitRowCount(entry, entry.split, width) < this.#measuredRows(entry, width).length) return;
|
|
1287
|
+
} else {
|
|
1288
|
+
if (entry.mode !== "appendOnly") return;
|
|
1289
|
+
const rendered = this.#measuredRows(entry, width);
|
|
1290
|
+
if (entry.emitted !== entry.stableRows.length) return;
|
|
1291
|
+
if (this.#renderStablePrefix(entry, entry.emitted, width).length !== rendered.length) return;
|
|
1292
|
+
}
|
|
1112
1293
|
this.#retireEntry(entry);
|
|
1113
1294
|
this.#frontier++;
|
|
1114
1295
|
}
|
|
@@ -1117,6 +1298,7 @@ export class TranscriptContainer extends Container {
|
|
|
1117
1298
|
#retireEntry(entry: TranscriptEntry): void {
|
|
1118
1299
|
entry.state = "committed";
|
|
1119
1300
|
entry.emitted = 0;
|
|
1301
|
+
entry.split = undefined;
|
|
1120
1302
|
entry.stableRows = EMPTY_STABLE_ROWS;
|
|
1121
1303
|
entry.renderedStableByWidth = new Map();
|
|
1122
1304
|
entry.stableRowCountByWidth = new Map();
|
|
@@ -1147,7 +1329,8 @@ export class TranscriptContainer extends Container {
|
|
|
1147
1329
|
|
|
1148
1330
|
#startReplay(): void {
|
|
1149
1331
|
const head = this.#entries[this.#frontier];
|
|
1150
|
-
this.#replayPending =
|
|
1332
|
+
this.#replayPending =
|
|
1333
|
+
this.#frontier > 0 || head?.split !== undefined || (head?.mode === "appendOnly" && head.emitted > 0);
|
|
1151
1334
|
this.#replayRequested = false;
|
|
1152
1335
|
}
|
|
1153
1336
|
|
|
@@ -1355,6 +1538,7 @@ export class TranscriptContainer extends Container {
|
|
|
1355
1538
|
emitted: 0,
|
|
1356
1539
|
stableFrozen: false,
|
|
1357
1540
|
imageScan: undefined,
|
|
1541
|
+
split: undefined,
|
|
1358
1542
|
},
|
|
1359
1543
|
);
|
|
1360
1544
|
this.#frontier = this.#entries.findIndex(entry => entry.state !== "committed");
|
package/src/components/editor.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type AutocompleteItem,
|
|
5
5
|
type AutocompleteProvider,
|
|
6
6
|
atCompletionMatches,
|
|
7
|
+
extractAtFilePrefix,
|
|
7
8
|
findLeadingSlashCommandStart,
|
|
8
9
|
findTrailingSlashCommandStart,
|
|
9
10
|
isDirectoryCompletionValue,
|
|
@@ -99,7 +100,6 @@ function sameDecorations(a: readonly TspEditorDecoration[], b: readonly TspEdito
|
|
|
99
100
|
return true;
|
|
100
101
|
}
|
|
101
102
|
const MENTION_CONTEXT_RE = /(?:^|\s)\^[^\s]*$/;
|
|
102
|
-
const AT_TOKEN_RE = /(?:^|\s)(@[^\s]*)$/;
|
|
103
103
|
|
|
104
104
|
const AUTOCOMPLETE_SELECT_LIST_LAYOUT: SelectListLayoutOptions = {
|
|
105
105
|
overflowSearch: false,
|
|
@@ -1769,7 +1769,7 @@ export class Editor implements Component, Focusable {
|
|
|
1769
1769
|
let typed = this.#autocompletePrefix;
|
|
1770
1770
|
if (typed.startsWith("@")) {
|
|
1771
1771
|
const line = this.#state.lines[this.#state.cursorLine] ?? "";
|
|
1772
|
-
typed =
|
|
1772
|
+
typed = extractAtFilePrefix(line.slice(0, this.#state.cursorCol)) ?? typed;
|
|
1773
1773
|
}
|
|
1774
1774
|
typed = typed.replace(/^[/@:#$^]"?/, "");
|
|
1775
1775
|
return typed.slice(typed.lastIndexOf("/") + 1);
|
|
@@ -3353,7 +3353,7 @@ export class Editor implements Component, Focusable {
|
|
|
3353
3353
|
this.#tryTriggerAutocomplete();
|
|
3354
3354
|
}
|
|
3355
3355
|
// Check if we're in an @ file reference context
|
|
3356
|
-
else if (textBeforeCursor
|
|
3356
|
+
else if (extractAtFilePrefix(textBeforeCursor)) {
|
|
3357
3357
|
this.#tryTriggerAutocomplete();
|
|
3358
3358
|
}
|
|
3359
3359
|
// Check if we're in a model mention context
|
|
@@ -3477,7 +3477,7 @@ export class Editor implements Component, Focusable {
|
|
|
3477
3477
|
const textBeforeCursor = currentLine.slice(0, this.#state.cursorCol);
|
|
3478
3478
|
if (this.#isInSlashAutocompleteContext()) {
|
|
3479
3479
|
this.#tryTriggerAutocomplete();
|
|
3480
|
-
} else if (textBeforeCursor
|
|
3480
|
+
} else if (extractAtFilePrefix(textBeforeCursor)) {
|
|
3481
3481
|
this.#tryTriggerAutocomplete();
|
|
3482
3482
|
} else if (MENTION_CONTEXT_RE.test(textBeforeCursor)) {
|
|
3483
3483
|
this.#tryTriggerAutocomplete();
|
|
@@ -3680,7 +3680,7 @@ export class Editor implements Component, Focusable {
|
|
|
3680
3680
|
this.#tryTriggerAutocomplete();
|
|
3681
3681
|
}
|
|
3682
3682
|
// @ file reference context
|
|
3683
|
-
else if (textBeforeCursor
|
|
3683
|
+
else if (extractAtFilePrefix(textBeforeCursor)) {
|
|
3684
3684
|
this.#tryTriggerAutocomplete();
|
|
3685
3685
|
}
|
|
3686
3686
|
// model mention context
|
|
@@ -3855,7 +3855,7 @@ export class Editor implements Component, Focusable {
|
|
|
3855
3855
|
const textBeforeCursor = currentLine.slice(0, this.#state.cursorCol);
|
|
3856
3856
|
if (this.#isInSlashAutocompleteContext()) {
|
|
3857
3857
|
this.#tryTriggerAutocomplete();
|
|
3858
|
-
} else if (textBeforeCursor
|
|
3858
|
+
} else if (extractAtFilePrefix(textBeforeCursor)) {
|
|
3859
3859
|
this.#tryTriggerAutocomplete();
|
|
3860
3860
|
} else if (MENTION_CONTEXT_RE.test(textBeforeCursor)) {
|
|
3861
3861
|
this.#tryTriggerAutocomplete();
|
|
@@ -4192,7 +4192,7 @@ export class Editor implements Component, Focusable {
|
|
|
4192
4192
|
this.#tryTriggerAutocomplete();
|
|
4193
4193
|
}
|
|
4194
4194
|
// @ file reference context
|
|
4195
|
-
else if (textBeforeCursor
|
|
4195
|
+
else if (extractAtFilePrefix(textBeforeCursor)) {
|
|
4196
4196
|
this.#tryTriggerAutocomplete();
|
|
4197
4197
|
}
|
|
4198
4198
|
// model mention context
|
|
@@ -4453,8 +4453,8 @@ export class Editor implements Component, Focusable {
|
|
|
4453
4453
|
* - Mid-prompt skill branch re-anchors when the popup item is a skill and the
|
|
4454
4454
|
* current text still ends in a matching trailing slash token, preventing a
|
|
4455
4455
|
* stale selection from replacing a newer skill prefix.
|
|
4456
|
-
* - `@`-file branch re-anchors via
|
|
4457
|
-
* still ends in a whitespace-anchored
|
|
4456
|
+
* - `@`-file branch re-anchors via `extractAtFilePrefix`; safe when the current text
|
|
4457
|
+
* still ends in a whitespace-anchored or double-quoted `@` token.
|
|
4458
4458
|
* - Everything else is stale — accepting it would corrupt the buffer (issue #4295).
|
|
4459
4459
|
*/
|
|
4460
4460
|
#autocompletePrefixMatchesCursorText(currentTextBeforeCursor: string, item?: SelectItem | null): boolean {
|
|
@@ -4489,7 +4489,7 @@ export class Editor implements Component, Focusable {
|
|
|
4489
4489
|
}
|
|
4490
4490
|
|
|
4491
4491
|
if (this.#autocompletePrefix.startsWith("@")) {
|
|
4492
|
-
return
|
|
4492
|
+
return extractAtFilePrefix(currentTextBeforeCursor) !== null;
|
|
4493
4493
|
}
|
|
4494
4494
|
|
|
4495
4495
|
if (this.#autocompletePrefix.startsWith("^")) {
|
|
@@ -4884,8 +4884,8 @@ export class Editor implements Component, Focusable {
|
|
|
4884
4884
|
#narrowAtFileList(): void {
|
|
4885
4885
|
if (!this.#autocompleteList || !this.#autocompletePrefix.startsWith("@")) return;
|
|
4886
4886
|
const currentLine = this.#state.lines[this.#state.cursorLine] ?? "";
|
|
4887
|
-
const token =
|
|
4888
|
-
if (token !==
|
|
4887
|
+
const token = extractAtFilePrefix(currentLine.slice(0, this.#state.cursorCol));
|
|
4888
|
+
if (token !== null) this.#autocompleteList.setFilter(token);
|
|
4889
4889
|
}
|
|
4890
4890
|
|
|
4891
4891
|
#invalidateAutocompleteRequests(): void {
|
|
@@ -8,6 +8,8 @@ export interface ImageOptions {
|
|
|
8
8
|
maxWidthCells?: number;
|
|
9
9
|
maxHeightCells?: number;
|
|
10
10
|
filename?: string;
|
|
11
|
+
/** File the image was read from; its link target. Images without one link to a blob copy of their bytes. */
|
|
12
|
+
sourcePath?: string;
|
|
11
13
|
/** Shared budget that caps how many inline images render as live graphics. */
|
|
12
14
|
budget?: ImageBudget;
|
|
13
15
|
/**
|