@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
package/src/render/code-cell.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { code, md, span } from "../native/describe";
|
|
|
8
8
|
import type { NativeNode } from "../native/node";
|
|
9
9
|
import { getMarkdownTheme, highlightCode, type Theme } from "../theme/theme";
|
|
10
10
|
import { formatDuration, formatExpandHint, formatMoreItems, formatStatusIcon, replaceTabs } from "./render-utils";
|
|
11
|
+
import { wrapPrefixedLine } from "./hanging-wrap";
|
|
11
12
|
import {
|
|
12
13
|
describeOutputBlock,
|
|
13
14
|
type NativeOutputBlockSection,
|
|
@@ -167,6 +168,7 @@ export function renderCodeCell(options: CodeCellOptions, theme: Theme): string[]
|
|
|
167
168
|
}
|
|
168
169
|
}
|
|
169
170
|
|
|
171
|
+
const numberedCodeLines: string[] = [];
|
|
170
172
|
if (lineNumberWidth > 0 && visibleLineNumbers) {
|
|
171
173
|
for (let i = 0; i < codeLines.length; i++) {
|
|
172
174
|
const lineNum = visibleLineNumbers[i];
|
|
@@ -174,10 +176,14 @@ export function renderCodeCell(options: CodeCellOptions, theme: Theme): string[]
|
|
|
174
176
|
lineNum !== null && lineNum !== undefined
|
|
175
177
|
? String(lineNum).padStart(lineNumberWidth, " ")
|
|
176
178
|
: " ".repeat(lineNumberWidth);
|
|
177
|
-
|
|
179
|
+
const prefix = theme.fg("dim", `${gutter} `);
|
|
180
|
+
numberedCodeLines.push(
|
|
181
|
+
...wrapPrefixedLine(prefix, " ".repeat(lineNumberWidth + 1), codeLines[i]!, outputBlockContentWidth(width)),
|
|
182
|
+
);
|
|
178
183
|
}
|
|
179
184
|
}
|
|
180
185
|
|
|
186
|
+
const displayCodeLines = lineNumberWidth > 0 ? numberedCodeLines : codeLines;
|
|
181
187
|
if (hiddenCodeLines > 0) {
|
|
182
188
|
const hint = formatExpandHint(theme, expanded, hiddenCodeLines > 0);
|
|
183
189
|
const gutterPad = lineNumberWidth > 0 ? " ".repeat(lineNumberWidth + 1) : "";
|
|
@@ -185,16 +191,16 @@ export function renderCodeCell(options: CodeCellOptions, theme: Theme): string[]
|
|
|
185
191
|
// Earlier rows scrolled above the live tail window — mark them on top so
|
|
186
192
|
// the newest streamed line stays pinned to the bottom of the box.
|
|
187
193
|
const earlier = `… ${hiddenCodeLines} earlier line${hiddenCodeLines === 1 ? "" : "s"}${hint ? ` ${hint}` : ""}`;
|
|
188
|
-
|
|
194
|
+
displayCodeLines.unshift(theme.fg("dim", gutterPad + earlier));
|
|
189
195
|
} else {
|
|
190
196
|
const moreLine = `${formatMoreItems(hiddenCodeLines, "line")}${hint ? ` ${hint}` : ""}`;
|
|
191
|
-
|
|
197
|
+
displayCodeLines.push(theme.fg("dim", gutterPad + moreLine));
|
|
192
198
|
}
|
|
193
199
|
}
|
|
194
200
|
|
|
195
201
|
const outputLines = renderCellOutput(output, expanded, outputMaxLines, theme);
|
|
196
202
|
|
|
197
|
-
const sections: Array<{ label?: string; lines: readonly string[] }> = [{ lines:
|
|
203
|
+
const sections: Array<{ label?: string; lines: readonly string[] }> = [{ lines: displayCodeLines }];
|
|
198
204
|
if (outputLines.length > 0) {
|
|
199
205
|
sections.push({ label: theme.fg("toolTitle", "Output"), lines: outputLines });
|
|
200
206
|
}
|
|
@@ -252,7 +258,14 @@ export function describeCodeCell(options: Omit<CodeCellOptions, "width">, theme:
|
|
|
252
258
|
const sections: NativeOutputBlockSection[] = [
|
|
253
259
|
{
|
|
254
260
|
key: "code",
|
|
255
|
-
body: [
|
|
261
|
+
body: [
|
|
262
|
+
code(options.code ?? "", {
|
|
263
|
+
lang: options.language,
|
|
264
|
+
start,
|
|
265
|
+
numbers: start !== undefined || undefined,
|
|
266
|
+
wrap: true,
|
|
267
|
+
}),
|
|
268
|
+
],
|
|
256
269
|
},
|
|
257
270
|
];
|
|
258
271
|
const output = describeCellOutput(options.output, expanded, outputMaxLines);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Theme } from "../theme/theme";
|
|
2
|
+
/** Wrap content under a distinct first-row prefix, preserving its column on continuation rows. */
|
|
3
|
+
export declare function wrapPrefixedLine(prefix: string, continuationPrefix: string, content: string, width: number): string[];
|
|
4
|
+
/** Preserve a tree connector's ancestor guides while hanging a long node beneath its label. */
|
|
5
|
+
export declare function wrapTreeLine(line: string, width: number, theme: Theme): string[] | undefined;
|
|
6
|
+
/** Hang a tree row beneath its connector, or a connector-less detail row beneath its guide rails. */
|
|
7
|
+
export declare function wrapGuidedLine(line: string, width: number, theme: Theme): string[];
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { visibleWidth, wrapTextWithAnsi } from "../utils";
|
|
2
|
+
import type { Theme } from "../theme/theme";
|
|
3
|
+
|
|
4
|
+
/** Wrap content under a distinct first-row prefix, preserving its column on continuation rows. */
|
|
5
|
+
export function wrapPrefixedLine(prefix: string, continuationPrefix: string, content: string, width: number): string[] {
|
|
6
|
+
const prefixWidth = visibleWidth(prefix);
|
|
7
|
+
const continuationWidth = visibleWidth(continuationPrefix);
|
|
8
|
+
if (width <= Math.max(prefixWidth, continuationWidth)) return wrapTextWithAnsi(prefix + content, width);
|
|
9
|
+
const rows = wrapTextWithAnsi(content, width - Math.max(prefixWidth, continuationWidth));
|
|
10
|
+
return rows.map((row, index) => `${index === 0 ? prefix : continuationPrefix}${row}`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Preserve a tree connector's ancestor guides while hanging a long node beneath its label. */
|
|
14
|
+
export function wrapTreeLine(line: string, width: number, theme: Theme): string[] | undefined {
|
|
15
|
+
for (const connector of [theme.tree.branch, theme.tree.last]) {
|
|
16
|
+
const marker = theme.fg("dim", connector);
|
|
17
|
+
const index = line.indexOf(marker);
|
|
18
|
+
if (index < 0) continue;
|
|
19
|
+
const before = line.slice(0, index);
|
|
20
|
+
if (!/^ *$/.test(Bun.stripANSI(before).replaceAll(theme.tree.vertical, ""))) continue;
|
|
21
|
+
const end = index + marker.length + 1;
|
|
22
|
+
if (line[end - 1] !== " ") continue;
|
|
23
|
+
const connectorWidth = visibleWidth(connector);
|
|
24
|
+
const guide =
|
|
25
|
+
connector === theme.tree.last
|
|
26
|
+
? " ".repeat(connectorWidth)
|
|
27
|
+
: `${theme.fg("dim", theme.tree.vertical)}${" ".repeat(Math.max(0, connectorWidth - visibleWidth(theme.tree.vertical)))}`;
|
|
28
|
+
return wrapPrefixedLine(line.slice(0, end), `${before}${guide} `, line.slice(end), width);
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const SGR = /\x1b\[[0-9;]*m/y;
|
|
34
|
+
|
|
35
|
+
/** End of the leading run of spaces, `vertical` guides and SGR escapes that a tree detail row hangs beneath. */
|
|
36
|
+
function guideRunEnd(line: string, vertical: string): number {
|
|
37
|
+
let index = 0;
|
|
38
|
+
while (index < line.length) {
|
|
39
|
+
if (line[index] === " ") {
|
|
40
|
+
index++;
|
|
41
|
+
} else if (vertical && line.startsWith(vertical, index)) {
|
|
42
|
+
index += vertical.length;
|
|
43
|
+
} else {
|
|
44
|
+
SGR.lastIndex = index;
|
|
45
|
+
if (!SGR.test(line)) break;
|
|
46
|
+
index = SGR.lastIndex;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return index;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Hang a tree row beneath its connector, or a connector-less detail row beneath its guide rails. */
|
|
53
|
+
export function wrapGuidedLine(line: string, width: number, theme: Theme): string[] {
|
|
54
|
+
const tree = wrapTreeLine(line, width, theme);
|
|
55
|
+
if (tree) return tree;
|
|
56
|
+
const prefix = line.slice(0, guideRunEnd(line, theme.tree.vertical));
|
|
57
|
+
return wrapPrefixedLine(prefix, prefix, line.slice(prefix.length), width);
|
|
58
|
+
}
|
|
@@ -70,6 +70,8 @@ export declare function urlHyperlinkAlways(url: string, displayText: string): st
|
|
|
70
70
|
* Non-HTTP(S) or unparsable URLs yield a plain span.
|
|
71
71
|
*/
|
|
72
72
|
export declare function urlLinkSpan(target: string, displayText: string, s?: string): TspSpan;
|
|
73
|
+
/** Whether native link spans and nodes may carry an `href`: everything but an explicit `off` policy. */
|
|
74
|
+
export declare function isNativeLinkEnabled(): boolean;
|
|
73
75
|
/**
|
|
74
76
|
* A native link span to a filesystem path as a plain `file://` URI (the
|
|
75
77
|
* terminal opens it through the system; the location stays in the visible
|
package/src/render/hyperlink.ts
CHANGED
|
@@ -196,6 +196,11 @@ export function urlLinkSpan(target: string, displayText: string, s = "link"): Ts
|
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
/** Whether native link spans and nodes may carry an `href`: everything but an explicit `off` policy. */
|
|
200
|
+
export function isNativeLinkEnabled(): boolean {
|
|
201
|
+
return hyperlinkMode !== "off";
|
|
202
|
+
}
|
|
203
|
+
|
|
199
204
|
/**
|
|
200
205
|
* A native link span to a filesystem path as a plain `file://` URI (the
|
|
201
206
|
* terminal opens it through the system; the location stays in the visible
|
|
@@ -105,14 +105,22 @@ export declare function cappedHeadLines(lines: readonly string[], max: number):
|
|
|
105
105
|
};
|
|
106
106
|
/** Get the first nonblank preview lines, trimmed and width-capped. */
|
|
107
107
|
export declare function getPreviewLines(text: string, maxLines: number, maxLineLen: number, ellipsis?: Ellipsis): string[];
|
|
108
|
+
/** Collapse a possibly multi-line string into one line; whitespace runs (tabs included) become one space. */
|
|
109
|
+
export declare function flattenLine(text: string): string;
|
|
108
110
|
/**
|
|
109
111
|
* Collapse a possibly multi-line string into a single line, then truncate it to
|
|
110
112
|
* `maxWidth` display cells. {@link truncateToWidth} alone caps width but
|
|
111
113
|
* newlines are zero-width, so multi-line content (markdown briefs, tool args,
|
|
112
114
|
* provider errors) would otherwise spill a single status row across several
|
|
113
|
-
* visual lines.
|
|
115
|
+
* visual lines.
|
|
114
116
|
*/
|
|
115
117
|
export declare function previewLine(text: string, maxWidth: number, ellipsis?: Ellipsis): string;
|
|
118
|
+
/**
|
|
119
|
+
* Clip a result row to `maxWidth` only while collapsed. Expanded rows stay whole
|
|
120
|
+
* so the frame wraps them: the expand key ({@link formatExpandHint}) must reveal
|
|
121
|
+
* text, not re-clip it.
|
|
122
|
+
*/
|
|
123
|
+
export declare function clipCollapsed(text: string, maxWidth: number, expanded: boolean | undefined): string;
|
|
116
124
|
/**
|
|
117
125
|
* Extract domain from URL, stripping www. prefix.
|
|
118
126
|
*/
|
|
@@ -233,15 +233,29 @@ export function getPreviewLines(text: string, maxLines: number, maxLineLen: numb
|
|
|
233
233
|
return lines.slice(0, maxLines).map(l => truncateToWidth(l.trim(), maxLineLen, ellipsis));
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
/** Collapse a possibly multi-line string into one line; whitespace runs (tabs included) become one space. */
|
|
237
|
+
export function flattenLine(text: string): string {
|
|
238
|
+
return text.replace(/\s+/g, " ").trim();
|
|
239
|
+
}
|
|
240
|
+
|
|
236
241
|
/**
|
|
237
242
|
* Collapse a possibly multi-line string into a single line, then truncate it to
|
|
238
243
|
* `maxWidth` display cells. {@link truncateToWidth} alone caps width but
|
|
239
244
|
* newlines are zero-width, so multi-line content (markdown briefs, tool args,
|
|
240
245
|
* provider errors) would otherwise spill a single status row across several
|
|
241
|
-
* visual lines.
|
|
246
|
+
* visual lines.
|
|
242
247
|
*/
|
|
243
248
|
export function previewLine(text: string, maxWidth: number, ellipsis?: Ellipsis): string {
|
|
244
|
-
return truncateToWidth(text
|
|
249
|
+
return truncateToWidth(flattenLine(text), maxWidth, ellipsis);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Clip a result row to `maxWidth` only while collapsed. Expanded rows stay whole
|
|
254
|
+
* so the frame wraps them: the expand key ({@link formatExpandHint}) must reveal
|
|
255
|
+
* text, not re-clip it.
|
|
256
|
+
*/
|
|
257
|
+
export function clipCollapsed(text: string, maxWidth: number, expanded: boolean | undefined): string {
|
|
258
|
+
return expanded ? text : truncateToWidth(text, maxWidth);
|
|
245
259
|
}
|
|
246
260
|
|
|
247
261
|
// =============================================================================
|
package/src/render/utils.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export interface RenderCache {
|
|
|
32
32
|
lines: string[];
|
|
33
33
|
}
|
|
34
34
|
/** Build indentation and continuing branches for ancestor levels. */
|
|
35
|
-
export declare function buildTreePrefix(ancestors: boolean[], theme: Theme): string;
|
|
35
|
+
export declare function buildTreePrefix(ancestors: readonly boolean[], theme: Theme): string;
|
|
36
36
|
/** Return the branch glyph for a final or continuing tree item. */
|
|
37
37
|
export declare function getTreeBranch(isLast: boolean, theme: Theme): string;
|
|
38
38
|
/** Return the continuation prefix for subsequent lines of a tree item. */
|
package/src/render/utils.ts
CHANGED
|
@@ -76,7 +76,7 @@ export interface RenderCache {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/** Build indentation and continuing branches for ancestor levels. */
|
|
79
|
-
export function buildTreePrefix(ancestors: boolean[], theme: Theme): string {
|
|
79
|
+
export function buildTreePrefix(ancestors: readonly boolean[], theme: Theme): string {
|
|
80
80
|
return ancestors.map(hasNext => (hasNext ? `${theme.tree.vertical} ` : " ")).join("");
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -25,7 +25,6 @@ export class WidthAwareText implements Component {
|
|
|
25
25
|
#cachedContentWidth = -1;
|
|
26
26
|
#cachedText: string | undefined;
|
|
27
27
|
#ignoreTight = false;
|
|
28
|
-
#nativeCols = -1;
|
|
29
28
|
#native: NativeNode | undefined;
|
|
30
29
|
|
|
31
30
|
constructor(format: (contentWidth: number) => string, paddingX = 1, paddingY = 1) {
|
|
@@ -71,10 +70,12 @@ export class WidthAwareText implements Component {
|
|
|
71
70
|
* (the only width a describer knows) and again on `reformat()`.
|
|
72
71
|
*/
|
|
73
72
|
describe(cx: DescribeContext): NativeNode {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
// Native describe emits no padding, so the formatter gets the full
|
|
74
|
+
// column budget — the same width a native Text.describe() would use.
|
|
75
|
+
const contentWidth = Math.max(1, cx.cols);
|
|
76
|
+
if (this.#cachedText === undefined || contentWidth !== this.#cachedContentWidth) {
|
|
77
|
+
this.#cachedContentWidth = contentWidth;
|
|
78
|
+
this.#cachedText = this.#format(contentWidth);
|
|
78
79
|
this.#inner.setText(this.#cachedText);
|
|
79
80
|
}
|
|
80
81
|
this.#native ??= col([this.#inner]);
|
|
@@ -6,7 +6,14 @@ import { formatKeyHint } from "../../app-keybindings";
|
|
|
6
6
|
import { col, node, span, text } from "../../native/describe";
|
|
7
7
|
import type { NativeNode } from "../../native/node";
|
|
8
8
|
import { Memo } from "../../native/memo";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
gradientEscape,
|
|
11
|
+
gradientLogo,
|
|
12
|
+
ZETA_LOGO,
|
|
13
|
+
ZETA_SUBLINE,
|
|
14
|
+
type ShineConfig,
|
|
15
|
+
logoNode,
|
|
16
|
+
} from "../../prompt/welcome";
|
|
10
17
|
|
|
11
18
|
export const SETUP_SPLASH_MS = 2600;
|
|
12
19
|
export const SETUP_TICK_MS = 33;
|
|
@@ -27,18 +34,11 @@ const RESET = "\x1b[0m";
|
|
|
27
34
|
const MIN_SCENE_WIDTH = 56;
|
|
28
35
|
const MIN_SCENE_HEIGHT = 22;
|
|
29
36
|
|
|
37
|
+
/** Skip affordance; built at render time so it follows the live symbol preset. */
|
|
30
38
|
function skipHint(): string {
|
|
31
|
-
return
|
|
39
|
+
return tuiText("setupSkipHint", "press {key} to skip").replace("{key}", formatKeyHint("enter"));
|
|
32
40
|
}
|
|
33
41
|
|
|
34
|
-
/** Density ramp for the rippling water, lightest → heaviest. */
|
|
35
|
-
const WATER_RAMP = [
|
|
36
|
-
{ min: 0.62, char: "█" },
|
|
37
|
-
{ min: 0.5, char: "▓" },
|
|
38
|
-
{ min: 0.36, char: "▒" },
|
|
39
|
-
{ min: 0.24, char: "░" },
|
|
40
|
-
];
|
|
41
|
-
|
|
42
42
|
function starAt(x: number, y: number, frame: number): string {
|
|
43
43
|
const hash = (x * 73856093) ^ (y * 19349663) ^ (frame * 83492791);
|
|
44
44
|
const bucket = Math.abs(hash) % 97;
|
|
@@ -85,39 +85,93 @@ function waterJitter(x: number, y: number): number {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
|
-
*
|
|
89
|
-
* interfere, then a radial edge falloff and a downward
|
|
90
|
-
* ripples beneath the mark and dissolve them toward the
|
|
91
|
-
*
|
|
88
|
+
* Time-invariant terms of the rippling water for one screen size: three
|
|
89
|
+
* travelling sine waves interfere, then a radial edge falloff and a downward
|
|
90
|
+
* fade concentrate the ripples beneath the mark and dissolve them toward the
|
|
91
|
+
* edges/bottom. Only the sine phases move per tick, so everything else is
|
|
92
|
+
* computed once per size.
|
|
92
93
|
*/
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
waterTop:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
94
|
+
interface WaterField {
|
|
95
|
+
readonly width: number;
|
|
96
|
+
readonly height: number;
|
|
97
|
+
readonly waterTop: number;
|
|
98
|
+
/** Per cell (row-major from `waterTop`): `dist * 0.55`. */
|
|
99
|
+
readonly radial: Float64Array;
|
|
100
|
+
/** Per cell: `x * 0.22 + y * 0.45`. */
|
|
101
|
+
readonly diagonal: Float64Array;
|
|
102
|
+
/** Per cell: `|dx| * 0.8 + dy * 0.5`. */
|
|
103
|
+
readonly cross: Float64Array;
|
|
104
|
+
/** Per cell: `(waterJitter(x, y) - 0.5) * 0.06`. */
|
|
105
|
+
readonly jitter: Float64Array;
|
|
106
|
+
/** Per column: `edge ** 0.7`. */
|
|
107
|
+
readonly edge: Float64Array;
|
|
108
|
+
/** Per water row: downward fade. */
|
|
109
|
+
readonly fade: Float64Array;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let waterField: WaterField | undefined;
|
|
113
|
+
|
|
114
|
+
function getWaterField(width: number, height: number, cx: number, waterTop: number): WaterField {
|
|
115
|
+
const cached = waterField;
|
|
116
|
+
if (cached && cached.width === width && cached.height === height && cached.waterTop === waterTop) return cached;
|
|
117
|
+
const waterHeight = Math.max(1, height - waterTop);
|
|
118
|
+
const rows = Math.max(0, height - waterTop);
|
|
119
|
+
const radial = new Float64Array(rows * width);
|
|
120
|
+
const diagonal = new Float64Array(rows * width);
|
|
121
|
+
const cross = new Float64Array(rows * width);
|
|
122
|
+
const jitter = new Float64Array(rows * width);
|
|
123
|
+
const edge = new Float64Array(width);
|
|
124
|
+
const fade = new Float64Array(rows);
|
|
125
|
+
for (let x = 0; x < width; x++) edge[x] = Math.max(0, 1 - Math.abs(x - cx) / (width * 0.5)) ** 0.7;
|
|
126
|
+
for (let row = 0; row < rows; row++) {
|
|
127
|
+
const y = waterTop + row;
|
|
128
|
+
const dy = y - waterTop;
|
|
129
|
+
fade[row] = Math.max(0, 1 - (dy / waterHeight) * 0.55);
|
|
130
|
+
for (let x = 0; x < width; x++) {
|
|
131
|
+
const i = row * width + x;
|
|
132
|
+
const dx = (x - cx) / 2;
|
|
133
|
+
radial[i] = Math.sqrt(dx * dx + dy * dy) * 0.55;
|
|
134
|
+
diagonal[i] = x * 0.22 + y * 0.45;
|
|
135
|
+
cross[i] = Math.abs(dx) * 0.8 + dy * 0.5;
|
|
136
|
+
jitter[i] = (waterJitter(x, y) - 0.5) * 0.06;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
waterField = { width, height, waterTop, radial, diagonal, cross, jitter, edge, fade };
|
|
140
|
+
return waterField;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Water glyph for an amplitude, lightest → heaviest density ramp; undefined below the floor. */
|
|
144
|
+
function waterChar(amp: number): string | undefined {
|
|
145
|
+
if (amp > 0.62) return "█";
|
|
146
|
+
if (amp > 0.5) return "▓";
|
|
147
|
+
if (amp > 0.36) return "▒";
|
|
148
|
+
if (amp > 0.24) return "░";
|
|
149
|
+
return undefined;
|
|
113
150
|
}
|
|
114
151
|
|
|
152
|
+
/** Reused cell grid; resized when the screen changes. */
|
|
153
|
+
let splashCells: string[][] = [];
|
|
154
|
+
|
|
155
|
+
function resetCells(width: number, height: number): string[][] {
|
|
156
|
+
if (splashCells.length !== height || splashCells[0]?.length !== width) {
|
|
157
|
+
// oxlint-disable-next-line unicorn/no-new-array -- length preallocation
|
|
158
|
+
splashCells = Array.from({ length: height }, () => new Array<string>(width).fill(" "));
|
|
159
|
+
} else {
|
|
160
|
+
for (const row of splashCells) row.fill(" ");
|
|
161
|
+
}
|
|
162
|
+
return splashCells;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Per-frame gradient escapes, one per screen diagonal (the gradient only varies along it). */
|
|
166
|
+
let diagonalEscapes: (string | undefined)[] = [];
|
|
167
|
+
|
|
115
168
|
/**
|
|
116
|
-
* Animated setup splash, in the spirit of the
|
|
117
|
-
* mark rendered
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* whole scene; the water
|
|
169
|
+
* Animated setup splash, in the spirit of the Zeta visual identity: the brand
|
|
170
|
+
* mark — the FIGlet-rendered Latin `Z`, the ASCII form of `Ζ` — with the live
|
|
171
|
+
* diagonal gradient + shine sweep, rising out of a rippling, gradient-lit water
|
|
172
|
+
* surface, under a faint twinkling starfield. The mark and water share one
|
|
173
|
+
* continuous gradient so the sweep reads across the whole scene; the water
|
|
174
|
+
* surface drifts each frame.
|
|
121
175
|
*/
|
|
122
176
|
export function renderSetupSplash(width: number, height: number, elapsedMs: number): string[] {
|
|
123
177
|
const w = Math.max(1, width);
|
|
@@ -132,23 +186,46 @@ export function renderSetupSplash(width: number, height: number, elapsedMs: numb
|
|
|
132
186
|
const cx = Math.floor(w / 2);
|
|
133
187
|
const surfaceTime = frame * 0.13;
|
|
134
188
|
|
|
135
|
-
|
|
136
|
-
const cells: string[][] = Array.from({ length: h }, () => new Array<string>(w).fill(" "));
|
|
189
|
+
const cells = resetCells(w, h);
|
|
137
190
|
const put = (x: number, y: number, glyph: string): void => {
|
|
138
191
|
if (y >= 0 && y < h && x >= 0 && x < w) cells[y][x] = glyph;
|
|
139
192
|
};
|
|
193
|
+
// Every cell on a diagonal shares one gradient position, so resolve each
|
|
194
|
+
// escape once per frame instead of once per cell.
|
|
195
|
+
const diagonals = w + h - 1;
|
|
196
|
+
if (diagonalEscapes.length !== diagonals) diagonalEscapes = Array.from({ length: diagonals });
|
|
197
|
+
else diagonalEscapes.fill(undefined);
|
|
198
|
+
const escapes = diagonalEscapes;
|
|
199
|
+
const gradient = (x: number, y: number): string => {
|
|
200
|
+
const d = x + (h - 1 - y);
|
|
201
|
+
let escape = escapes[d];
|
|
202
|
+
if (escape === undefined) {
|
|
203
|
+
escape = gradientEscape(screenGradientT(x, y, w, h, phase), shine);
|
|
204
|
+
escapes[d] = escape;
|
|
205
|
+
}
|
|
206
|
+
return escape;
|
|
207
|
+
};
|
|
140
208
|
|
|
141
209
|
const hx = Math.floor((w - LOGO_WIDTH) / 2);
|
|
142
210
|
const hy = Math.max(2, Math.floor(h * 0.16));
|
|
143
211
|
const waterTop = hy + LOGO_HEIGHT;
|
|
144
|
-
const waterHeight = Math.max(1, h - waterTop);
|
|
145
212
|
|
|
146
213
|
// 1. rippling water surface (shares the screen-wide gradient with the mark)
|
|
214
|
+
const field = getWaterField(w, h, cx, waterTop);
|
|
215
|
+
const tDiagonal = surfaceTime * 0.7;
|
|
216
|
+
const tCross = surfaceTime * 1.4;
|
|
147
217
|
for (let y = waterTop; y < h; y++) {
|
|
218
|
+
const row = y - waterTop;
|
|
219
|
+
const fade = field.fade[row];
|
|
148
220
|
for (let x = 0; x < w; x++) {
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
|
|
221
|
+
const i = row * w + x;
|
|
222
|
+
const wave =
|
|
223
|
+
0.5 * Math.sin(field.radial[i] - surfaceTime) +
|
|
224
|
+
0.3 * Math.sin(field.diagonal[i] - tDiagonal) +
|
|
225
|
+
0.2 * Math.sin(field.cross[i] - tCross);
|
|
226
|
+
const level = 0.5 + 0.5 * wave;
|
|
227
|
+
const char = waterChar(level * field.edge[x] * fade + field.jitter[i]);
|
|
228
|
+
if (char) cells[y][x] = gradient(x, y) + char + RESET;
|
|
152
229
|
}
|
|
153
230
|
}
|
|
154
231
|
// 2. twinkling starfield in the sky above the water
|
|
@@ -162,18 +239,15 @@ export function renderSetupSplash(width: number, height: number, elapsedMs: numb
|
|
|
162
239
|
LARGE_LOGO.forEach((line, row) => {
|
|
163
240
|
let col = 0;
|
|
164
241
|
for (const ch of line) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
hy + row,
|
|
169
|
-
gradientEscape(screenGradientT(hx + col, hy + row, w, h, phase), shine) + ch + RESET,
|
|
170
|
-
);
|
|
171
|
-
}
|
|
242
|
+
const x = hx + col;
|
|
243
|
+
const y = hy + row;
|
|
244
|
+
if (ch !== " " && y >= 0 && y < h && x >= 0 && x < w) cells[y][x] = gradient(x, y) + ch + RESET;
|
|
172
245
|
col++;
|
|
173
246
|
}
|
|
174
247
|
});
|
|
175
|
-
// 4. skip hint on a cleared strip at the bottom so it stays legible over the water
|
|
176
|
-
|
|
248
|
+
// 4. skip hint on a cleared strip at the bottom so it stays legible over the water.
|
|
249
|
+
// `formatKeyHint` yields plain glyphs (no ANSI), so per-character dim styling is safe.
|
|
250
|
+
const hint = skipHint();
|
|
177
251
|
const hintWidth = visibleWidth(hint);
|
|
178
252
|
const hintStart = Math.floor((w - hintWidth) / 2);
|
|
179
253
|
const hintRow = h - 1;
|
|
@@ -199,7 +273,8 @@ export function describeSetupSplash(): NativeNode {
|
|
|
199
273
|
[
|
|
200
274
|
node("spacer", { grow: 1 }),
|
|
201
275
|
logoNode(LARGE_LOGO, true),
|
|
202
|
-
text([span("
|
|
276
|
+
text([span("Zeta", "strong")], { wrap: "none" }),
|
|
277
|
+
text([span("Linxira-Zeta", "dim")], { wrap: "none" }),
|
|
203
278
|
node("spacer", { grow: 1 }),
|
|
204
279
|
text([span(hint, "dim")], { wrap: "none" }),
|
|
205
280
|
],
|
|
@@ -211,7 +286,12 @@ export function describeSetupSplash(): NativeNode {
|
|
|
211
286
|
/** Centered fallback for windows too small to hold the full scene. */
|
|
212
287
|
function renderCompactSplash(width: number, height: number, phase: number, shine: ShineConfig): string[] {
|
|
213
288
|
const art = height >= 14 ? LARGE_LOGO : ZETA_LOGO;
|
|
214
|
-
const content = [
|
|
289
|
+
const content = [
|
|
290
|
+
...gradientLogo(art, phase, shine),
|
|
291
|
+
"",
|
|
292
|
+
theme.bold("Zeta"),
|
|
293
|
+
...ZETA_SUBLINE.map(row => theme.fg("dim", row)),
|
|
294
|
+
];
|
|
215
295
|
const start = Math.max(0, Math.floor((height - content.length) / 2));
|
|
216
296
|
const lines: string[] = [];
|
|
217
297
|
for (let y = 0; y < height; y++) {
|
|
@@ -219,9 +299,6 @@ function renderCompactSplash(width: number, height: number, phase: number, shine
|
|
|
219
299
|
lines.push(width > 0 ? padToWidth(item !== undefined ? centerLine(item, width) : "", width) : "");
|
|
220
300
|
}
|
|
221
301
|
if (height > 2)
|
|
222
|
-
lines[height - 2] =
|
|
223
|
-
width > 0
|
|
224
|
-
? padToWidth(centerLine(theme.fg("dim", tuiText("setupSkipHint", "press enter to skip")), width), width)
|
|
225
|
-
: "";
|
|
302
|
+
lines[height - 2] = width > 0 ? padToWidth(centerLine(theme.fg("dim", skipHint()), width), width) : "";
|
|
226
303
|
return lines;
|
|
227
304
|
}
|
|
@@ -380,6 +380,7 @@ interface ContextUsageMemo {
|
|
|
380
380
|
length: number;
|
|
381
381
|
lastFingerprint: string | undefined;
|
|
382
382
|
modelContextWindow: number;
|
|
383
|
+
toolSchemasInContext: boolean;
|
|
383
384
|
contextUsageRevision: number;
|
|
384
385
|
toolSchemaMetadataRevision: number;
|
|
385
386
|
settingsRevision: number;
|
|
@@ -774,9 +775,12 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
774
775
|
// than poison the fresh cache or advance its throttle.
|
|
775
776
|
#jjCacheGeneration = 0;
|
|
776
777
|
|
|
777
|
-
// PR lookup
|
|
778
|
+
// PR lookup cache. `#cachedPrContext` is the branch/repo `#cachedPr` was
|
|
779
|
+
// resolved for; it is never shown for any other branch. Expiry forces a
|
|
780
|
+
// refetch while keeping that branch's PR visible until the lookup lands.
|
|
778
781
|
#cachedPr: { number: number; url: string } | null | undefined = undefined;
|
|
779
782
|
#cachedPrContext: PrCacheContext | undefined = undefined;
|
|
783
|
+
#cachedPrExpired = false;
|
|
780
784
|
#prLookupInFlight = false;
|
|
781
785
|
#defaultBranch?: string;
|
|
782
786
|
#defaultBranchCwd: string | undefined = undefined;
|
|
@@ -1457,8 +1461,8 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1457
1461
|
// was designed to avoid. Explicit Git/repository invalidation (watcher
|
|
1458
1462
|
// HEAD-move, cwd/repo switch) goes through {@link invalidateGitCaches}.
|
|
1459
1463
|
// A tool may open, close, or merge a PR without moving HEAD. Expire the
|
|
1460
|
-
// settled PR
|
|
1461
|
-
this.#
|
|
1464
|
+
// settled PR on ordinary activity while leaving HEAD work intact.
|
|
1465
|
+
this.#cachedPrExpired = true;
|
|
1462
1466
|
}
|
|
1463
1467
|
#invalidateSessionCaches(): void {
|
|
1464
1468
|
this.#clearUsageStartTimer();
|
|
@@ -1489,7 +1493,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1489
1493
|
this.#branchResolveActive = undefined;
|
|
1490
1494
|
this.#branchLastFetch = undefined;
|
|
1491
1495
|
this.#branchCacheGeneration++;
|
|
1492
|
-
this.#
|
|
1496
|
+
this.#cachedPrExpired = true;
|
|
1493
1497
|
// A HEAD move (commit, checkout, reset) changes the dirty counts; keep the
|
|
1494
1498
|
// stale counts on screen but refetch on the next render. The generation
|
|
1495
1499
|
// bump stops an in-flight pre-move result from re-stamping freshness.
|
|
@@ -1810,11 +1814,16 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1810
1814
|
const branch = this.#getBranchLabel(activeRepoCache, this.#resolveRepository(activeRepoCache));
|
|
1811
1815
|
const currentContext = branch ? createPrCacheContext(branch, this.#cachedBranchRepoId ?? null) : null;
|
|
1812
1816
|
|
|
1813
|
-
if (canReuseCachedPr(this.#cachedPr, this.#cachedPrContext, currentContext)) {
|
|
1817
|
+
if (!this.#cachedPrExpired && canReuseCachedPr(this.#cachedPr, this.#cachedPrContext, currentContext)) {
|
|
1814
1818
|
return this.#cachedPr ?? null;
|
|
1815
1819
|
}
|
|
1816
1820
|
|
|
1817
|
-
|
|
1821
|
+
// Keep an expired PR visible during the refetch only on the branch it
|
|
1822
|
+
// was resolved for; after a checkout the old PR must not linger.
|
|
1823
|
+
const stalePr =
|
|
1824
|
+
currentContext && isSamePrCacheContext(this.#cachedPrContext, currentContext)
|
|
1825
|
+
? (this.#cachedPr ?? null)
|
|
1826
|
+
: null;
|
|
1818
1827
|
|
|
1819
1828
|
if (!branch) {
|
|
1820
1829
|
this.#cachedPr = null;
|
|
@@ -1822,10 +1831,9 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1822
1831
|
return null;
|
|
1823
1832
|
}
|
|
1824
1833
|
|
|
1825
|
-
//
|
|
1826
|
-
if (branch === "detached" || this.#isDefaultBranch(branch, gitCwd)
|
|
1827
|
-
|
|
1828
|
-
}
|
|
1834
|
+
// Detached HEAD and the default branch never carry a PR; no lookup.
|
|
1835
|
+
if (branch === "detached" || this.#isDefaultBranch(branch, gitCwd)) return null;
|
|
1836
|
+
if (this.#prLookupInFlight) return stalePr;
|
|
1829
1837
|
|
|
1830
1838
|
this.#prLookupInFlight = true;
|
|
1831
1839
|
const lookupContext = currentContext;
|
|
@@ -1847,6 +1855,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1847
1855
|
if (lookupContext && isSamePrCacheContext(latestContext, lookupContext)) {
|
|
1848
1856
|
this.#cachedPr = value;
|
|
1849
1857
|
this.#cachedPrContext = lookupContext;
|
|
1858
|
+
this.#cachedPrExpired = false;
|
|
1850
1859
|
}
|
|
1851
1860
|
};
|
|
1852
1861
|
try {
|
|
@@ -1980,6 +1989,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
1980
1989
|
* cadence while a late successful fetch can still refresh the cached segment.
|
|
1981
1990
|
*/
|
|
1982
1991
|
refreshUsageInBackground(): void {
|
|
1992
|
+
if (this.#disposed) return;
|
|
1983
1993
|
const now = Date.now();
|
|
1984
1994
|
const session = this.session;
|
|
1985
1995
|
const usageContextKey = this.#getUsageContextKey(session);
|
|
@@ -2407,6 +2417,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2407
2417
|
getCachedContextBreakdown(): { usedTokens: number; contextWindow: number } {
|
|
2408
2418
|
const messages = this.session.messages ?? EMPTY_MESSAGES;
|
|
2409
2419
|
const modelContextWindow = this.session.model?.contextWindow ?? 0;
|
|
2420
|
+
const toolSchemasInContext = this.session.model?.toolSchemasInContext !== false;
|
|
2410
2421
|
const length = messages.length;
|
|
2411
2422
|
const lastFingerprint = length > 0 ? messageFingerprint(messages[length - 1]!) : undefined;
|
|
2412
2423
|
// Bumps when the in-flight pending snapshot is set/cleared. Without it a
|
|
@@ -2428,6 +2439,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2428
2439
|
cache.length === length &&
|
|
2429
2440
|
cache.lastFingerprint === lastFingerprint &&
|
|
2430
2441
|
cache.modelContextWindow === modelContextWindow &&
|
|
2442
|
+
cache.toolSchemasInContext === toolSchemasInContext &&
|
|
2431
2443
|
cache.contextUsageRevision === contextUsageRevision &&
|
|
2432
2444
|
cache.systemPromptRef === systemPrompt &&
|
|
2433
2445
|
cache.toolsRef === tools &&
|
|
@@ -2448,6 +2460,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2448
2460
|
length,
|
|
2449
2461
|
lastFingerprint,
|
|
2450
2462
|
modelContextWindow,
|
|
2463
|
+
toolSchemasInContext,
|
|
2451
2464
|
contextUsageRevision,
|
|
2452
2465
|
toolSchemaMetadataRevision,
|
|
2453
2466
|
settingsRevision,
|