@linxiraos/pi-tui 1.2.1 → 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 -125
- 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 +21 -20
- 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 +26 -6
- 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/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/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 +1 -1
- package/src/prompt/composer.ts +194 -74
- 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 +97 -45
- 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 +22 -8
- 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/tools/find.ts
CHANGED
|
@@ -27,11 +27,16 @@ import {
|
|
|
27
27
|
import type { Theme, ThemeColor } from "../theme/theme";
|
|
28
28
|
import type { Component } from "../tui";
|
|
29
29
|
import type { OutputMeta } from "./output-meta";
|
|
30
|
-
import type {
|
|
30
|
+
import type {
|
|
31
|
+
NativeToolHead,
|
|
32
|
+
NativeToolView,
|
|
33
|
+
RenderResultContextOptions,
|
|
34
|
+
RenderResultOptions,
|
|
35
|
+
ToolRenderer,
|
|
36
|
+
} from "./renderer";
|
|
31
37
|
import type { TspTone } from "@linxiraos/pi-wire";
|
|
32
38
|
import { code, col, compact, keyed, node, span, text } from "../native/describe";
|
|
33
39
|
import type { NativeChild, NativeNode } from "../native/node";
|
|
34
|
-
import { getLanguageFromPath } from "../lang-from-path";
|
|
35
40
|
import { fileHref, fileRow, inlineErrorView } from "./native-view";
|
|
36
41
|
import { splitUrlScheme } from "./url-scheme-host";
|
|
37
42
|
|
|
@@ -57,12 +62,22 @@ export interface FindHit {
|
|
|
57
62
|
truncated: boolean;
|
|
58
63
|
}
|
|
59
64
|
|
|
65
|
+
/**
|
|
66
|
+
* How much of a search the judge actually covered. `unavailable`: every judge
|
|
67
|
+
* request failed, so an empty result is not evidence of absence. `partial`:
|
|
68
|
+
* some requests failed. `complete`: none failed (or none were needed).
|
|
69
|
+
*/
|
|
70
|
+
export type FindCoverage = "complete" | "partial" | "unavailable";
|
|
71
|
+
|
|
60
72
|
/** Search accounting reported alongside the hits. */
|
|
61
73
|
export interface FindStats {
|
|
62
74
|
/** Eligible files under the root. */
|
|
63
75
|
listed: number;
|
|
64
76
|
requests: number;
|
|
65
|
-
|
|
77
|
+
/** Judge requests that failed outright; decides {@link FindCoverage}. */
|
|
78
|
+
failed: number;
|
|
79
|
+
/** Questions a successful response left without a usable answer. */
|
|
80
|
+
unanswered: number;
|
|
66
81
|
/** Entries judged by name. */
|
|
67
82
|
judged: number;
|
|
68
83
|
/** Files whose content was read and sent. */
|
|
@@ -87,6 +102,7 @@ export interface FindToolDetails {
|
|
|
87
102
|
/** Verified-passage probability at or above which a file is a hit. */
|
|
88
103
|
threshold: number;
|
|
89
104
|
hits: FindHit[];
|
|
105
|
+
coverage: FindCoverage;
|
|
90
106
|
stats: FindStats;
|
|
91
107
|
elapsedMs: number;
|
|
92
108
|
/** Session cwd; hit paths are relative to it. */
|
|
@@ -129,13 +145,32 @@ function gauge(p: number, theme: Theme): string {
|
|
|
129
145
|
});
|
|
130
146
|
}
|
|
131
147
|
|
|
132
|
-
|
|
148
|
+
/** Raw metadata is used only for file link targets, never for displayed text. */
|
|
149
|
+
function findLinkTargets(details: FindToolDetails, rawDetails: unknown) {
|
|
150
|
+
const raw = rawDetails !== null && typeof rawDetails === "object" ? rawDetails : undefined;
|
|
151
|
+
const cwd = raw && "cwd" in raw && typeof raw.cwd === "string" ? raw.cwd : details.cwd;
|
|
152
|
+
const rawHits: unknown[] = raw && "hits" in raw && Array.isArray(raw.hits) ? raw.hits : [];
|
|
153
|
+
return {
|
|
154
|
+
cwd,
|
|
155
|
+
rel(hit: FindHit, index: number): string {
|
|
156
|
+
const candidate = rawHits[index];
|
|
157
|
+
return candidate !== null &&
|
|
158
|
+
typeof candidate === "object" &&
|
|
159
|
+
"rel" in candidate &&
|
|
160
|
+
typeof candidate.rel === "string"
|
|
161
|
+
? candidate.rel
|
|
162
|
+
: hit.rel;
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function renderHit(hit: FindHit, rangeLimit: number, cwd: string | undefined, rawRel: string, theme: Theme): string[] {
|
|
133
168
|
// `scheme://` hits (e.g. virtual docs) are not files under `cwd`: link the
|
|
134
169
|
// URL itself instead of joining it onto a filesystem base.
|
|
135
170
|
const isUrlHit = splitUrlScheme(hit.rel) !== undefined;
|
|
136
171
|
const link = (text: string, line?: number): string => {
|
|
137
172
|
if (isUrlHit) return uriHyperlink(line === undefined ? hit.rel : `${hit.rel}:${line}`, text);
|
|
138
|
-
const absPath = cwd === undefined ? undefined : path.join(cwd,
|
|
173
|
+
const absPath = cwd === undefined ? undefined : path.join(cwd, rawRel);
|
|
139
174
|
return absPath === undefined ? text : fileHyperlink(absPath, text, { line });
|
|
140
175
|
};
|
|
141
176
|
const coverage = hit.truncated ? `${hit.linesSeen} lines judged, partial` : `${hit.linesSeen} lines judged`;
|
|
@@ -161,7 +196,7 @@ function scoreTone(p: number): TspTone {
|
|
|
161
196
|
* One hit (§7.3 find): a 22px row of the score bar, the path and `12 lines
|
|
162
197
|
* judged`, then its strongest ranges as numbered `code` (no path header).
|
|
163
198
|
*/
|
|
164
|
-
function describeHit(hit: FindHit, rangeLimit: number, cwd: string | undefined): NativeNode {
|
|
199
|
+
function describeHit(hit: FindHit, rangeLimit: number, cwd: string | undefined, rawRel: string): NativeNode {
|
|
165
200
|
const ranges = [...hit.ranges].sort((a, b) => b.p - a.p || a.start - b.start).slice(0, rangeLimit);
|
|
166
201
|
const tone = scoreTone(hit.contentScore);
|
|
167
202
|
const score = hit.contentScore.toFixed(2);
|
|
@@ -175,13 +210,17 @@ function describeHit(hit: FindHit, rangeLimit: number, cwd: string | undefined):
|
|
|
175
210
|
"muted",
|
|
176
211
|
),
|
|
177
212
|
],
|
|
178
|
-
href: isUrlHit || cwd === undefined ? undefined : fileHref(path.join(cwd,
|
|
213
|
+
href: isUrlHit || cwd === undefined ? undefined : fileHref(path.join(cwd, rawRel)),
|
|
179
214
|
key: "hit",
|
|
180
215
|
});
|
|
181
|
-
const lang = getLanguageFromPath(hit.rel);
|
|
182
216
|
const snippets = ranges.map(range =>
|
|
183
217
|
keyed(
|
|
184
|
-
code(range.snippet.trimEnd(), {
|
|
218
|
+
code(range.snippet.trimEnd(), {
|
|
219
|
+
path: hit.rel,
|
|
220
|
+
start: range.start,
|
|
221
|
+
numbers: true,
|
|
222
|
+
title: range.p.toFixed(2),
|
|
223
|
+
}),
|
|
185
224
|
`${range.start}-${range.end}`,
|
|
186
225
|
),
|
|
187
226
|
);
|
|
@@ -222,7 +261,7 @@ export const findToolRenderer = {
|
|
|
222
261
|
|
|
223
262
|
renderResult(
|
|
224
263
|
result: { content: Array<{ type: string; text?: string }>; details?: FindToolDetails; isError?: boolean },
|
|
225
|
-
options:
|
|
264
|
+
options: RenderResultContextOptions,
|
|
226
265
|
uiTheme: Theme,
|
|
227
266
|
args?: FindRenderArgs,
|
|
228
267
|
): Component {
|
|
@@ -250,6 +289,7 @@ export const findToolRenderer = {
|
|
|
250
289
|
}
|
|
251
290
|
|
|
252
291
|
const { hits, stats, threshold } = details;
|
|
292
|
+
const linkTargets = findLinkTargets(details, options.renderContext?.rawDetails);
|
|
253
293
|
const description = quoteQuery(details.query);
|
|
254
294
|
const scope = details.scopePath === undefined ? [] : [`in ${details.scopePath}`];
|
|
255
295
|
const meta = [
|
|
@@ -261,16 +301,21 @@ export const findToolRenderer = {
|
|
|
261
301
|
`$${stats.cost.toFixed(4)}`,
|
|
262
302
|
formatDuration(details.elapsedMs),
|
|
263
303
|
];
|
|
264
|
-
|
|
304
|
+
const failed = stats.failed > 0 ? `${stats.failed}/${stats.requests} requests failed` : undefined;
|
|
305
|
+
if (failed) meta.push(uiTheme.fg("warning", failed));
|
|
265
306
|
|
|
266
307
|
if (hits.length === 0) {
|
|
267
308
|
const emptyMeta = ["0 hits", ...scope, `$${stats.cost.toFixed(4)}`, formatDuration(details.elapsedMs)];
|
|
268
|
-
if (
|
|
309
|
+
if (failed) emptyMeta.push(uiTheme.fg("warning", failed));
|
|
269
310
|
const header = renderStatusLine(
|
|
270
311
|
{ icon: "warning", title: "Find", titleColor: "toolTitle", description, meta: emptyMeta },
|
|
271
312
|
uiTheme,
|
|
272
313
|
);
|
|
273
|
-
const
|
|
314
|
+
const empty =
|
|
315
|
+
details.coverage === "partial"
|
|
316
|
+
? "No relevant passages found in the judged files"
|
|
317
|
+
: "No relevant passages found";
|
|
318
|
+
const lines = [header, formatEmptyMessage(empty, uiTheme)];
|
|
274
319
|
for (const failure of stats.failures) lines.push(uiTheme.fg("warning", failure));
|
|
275
320
|
return new Text(lines.join("\n"), 1, 0);
|
|
276
321
|
}
|
|
@@ -296,8 +341,14 @@ export const findToolRenderer = {
|
|
|
296
341
|
maxCollapsed: COLLAPSED_HITS,
|
|
297
342
|
maxCollapsedLines: PREVIEW_LIMITS.EXPANDED_LINES,
|
|
298
343
|
itemType: "hit",
|
|
299
|
-
renderItem: hit =>
|
|
300
|
-
renderHit(
|
|
344
|
+
renderItem: (hit, context) =>
|
|
345
|
+
renderHit(
|
|
346
|
+
hit,
|
|
347
|
+
expanded ? RANGES_EXPANDED : RANGES_COLLAPSED,
|
|
348
|
+
linkTargets.cwd,
|
|
349
|
+
linkTargets.rel(hit, context.index),
|
|
350
|
+
uiTheme,
|
|
351
|
+
),
|
|
301
352
|
},
|
|
302
353
|
uiTheme,
|
|
303
354
|
);
|
|
@@ -329,7 +380,7 @@ export const findToolRenderer = {
|
|
|
329
380
|
*/
|
|
330
381
|
describeResult(
|
|
331
382
|
result: { content: Array<{ type: string; text?: string }>; details?: FindToolDetails; isError?: boolean },
|
|
332
|
-
options:
|
|
383
|
+
options: RenderResultContextOptions,
|
|
333
384
|
args?: FindRenderArgs,
|
|
334
385
|
): NativeToolView {
|
|
335
386
|
const details = result.details;
|
|
@@ -343,8 +394,10 @@ export const findToolRenderer = {
|
|
|
343
394
|
};
|
|
344
395
|
}
|
|
345
396
|
const { hits, stats, threshold } = details;
|
|
397
|
+
const linkTargets = findLinkTargets(details, options.renderContext?.rawDetails);
|
|
346
398
|
const scope = details.scopePath === undefined ? undefined : `in ${details.scopePath}`;
|
|
347
|
-
const failed =
|
|
399
|
+
const failed =
|
|
400
|
+
stats.failed > 0 ? [span(`${stats.failed}/${stats.requests} requests failed`, "warning")] : undefined;
|
|
348
401
|
const accounting = [
|
|
349
402
|
`${stats.filesRead} files read`,
|
|
350
403
|
`τ ${threshold.toFixed(2)}`,
|
|
@@ -382,7 +435,7 @@ export const findToolRenderer = {
|
|
|
382
435
|
),
|
|
383
436
|
inline: true,
|
|
384
437
|
body: compact<NativeChild>([
|
|
385
|
-
...shown.map(hit => describeHit(hit, rangeLimit,
|
|
438
|
+
...shown.map((hit, index) => describeHit(hit, rangeLimit, linkTargets.cwd, linkTargets.rel(hit, index))),
|
|
386
439
|
...failures,
|
|
387
440
|
foot,
|
|
388
441
|
]),
|
package/src/tools/glob.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Component } from "../tui";
|
|
2
|
-
import type { NativeToolView, RenderResultOptions } from "./renderer";
|
|
2
|
+
import type { NativeToolView, RenderResultContextOptions, RenderResultOptions } from "./renderer";
|
|
3
3
|
import { type Theme } from "../theme/theme";
|
|
4
4
|
import type { OutputMeta } from "./output-meta";
|
|
5
5
|
import type { TruncationResult } from "./streaming-output";
|
|
@@ -42,7 +42,7 @@ export declare const globToolRenderer: {
|
|
|
42
42
|
}>;
|
|
43
43
|
details?: GlobToolDetails;
|
|
44
44
|
isError?: boolean;
|
|
45
|
-
}, options:
|
|
45
|
+
}, options: RenderResultContextOptions, uiTheme: Theme, args?: GlobRenderArgs): Component;
|
|
46
46
|
describeCall(args: GlobRenderArgs): NativeToolView;
|
|
47
47
|
/**
|
|
48
48
|
* Inline (§7.3 glob): `Glob “pattern” 5 files`, then the files as rows
|
|
@@ -55,7 +55,7 @@ export declare const globToolRenderer: {
|
|
|
55
55
|
}>;
|
|
56
56
|
details?: GlobToolDetails;
|
|
57
57
|
isError?: boolean;
|
|
58
|
-
},
|
|
58
|
+
}, options: RenderResultContextOptions, args?: GlobRenderArgs): NativeToolView;
|
|
59
59
|
mergeCallAndResult: true;
|
|
60
60
|
};
|
|
61
61
|
export {};
|
package/src/tools/glob.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { Component } from "../tui";
|
|
2
2
|
import { Text } from "../components/text";
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
NativeToolHead,
|
|
5
|
+
NativeToolView,
|
|
6
|
+
RenderResultContextOptions,
|
|
7
|
+
RenderResultOptions,
|
|
8
|
+
ToolRenderer,
|
|
9
|
+
} from "./renderer";
|
|
4
10
|
import { col, compact, keyed, node, row, span, text } from "../native/describe";
|
|
5
11
|
import type { NativeChild, NativeNode } from "../native/node";
|
|
6
12
|
import { fileHref, fileRow, footnoteText, inlineErrorView, resultText } from "./native-view";
|
|
@@ -68,13 +74,33 @@ const NATIVE_LIST_MAX = 6;
|
|
|
68
74
|
/** Collapsed clamp: the whole ≤ 6-row list, or about six rows of chips. */
|
|
69
75
|
const NATIVE_PREVIEW_LINES = 8;
|
|
70
76
|
|
|
71
|
-
/**
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
77
|
+
/** Use original filesystem paths for existing links, never for displayed labels or extra links. */
|
|
78
|
+
function globLinkPaths(
|
|
79
|
+
details: GlobToolDetails | undefined,
|
|
80
|
+
rawDetails: unknown,
|
|
81
|
+
): { cwd: string | undefined; files: readonly unknown[] } {
|
|
82
|
+
const raw = rawDetails && typeof rawDetails === "object" ? rawDetails : undefined;
|
|
83
|
+
const rawCwd = raw && "cwd" in raw && typeof raw.cwd === "string" ? raw.cwd : undefined;
|
|
84
|
+
const rawFiles = raw && "files" in raw && Array.isArray(raw.files) ? raw.files : undefined;
|
|
85
|
+
const files = details?.files ?? [];
|
|
86
|
+
return {
|
|
87
|
+
cwd: details?.cwd ? (rawCwd ?? details.cwd) : undefined,
|
|
88
|
+
files: rawFiles?.length === files.length ? rawFiles : files,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Native glob files: path rows when few, otherwise wrapped chips; links require a known cwd. */
|
|
93
|
+
function describeGlobFiles(
|
|
94
|
+
files: readonly string[],
|
|
95
|
+
cwd: string | undefined,
|
|
96
|
+
rawFiles: readonly unknown[] = files,
|
|
97
|
+
): NativeNode {
|
|
98
|
+
const links = files.map((file, i) => {
|
|
99
|
+
const rawFile = rawFiles[i];
|
|
100
|
+
return cwd && !file.endsWith("/")
|
|
101
|
+
? fileHref(path.resolve(cwd, typeof rawFile === "string" ? rawFile : file))
|
|
102
|
+
: undefined;
|
|
103
|
+
});
|
|
78
104
|
if (files.length <= NATIVE_LIST_MAX) {
|
|
79
105
|
return col(
|
|
80
106
|
files.map((file, i) => fileRow(file, { href: links[i] })),
|
|
@@ -130,11 +156,12 @@ export const globToolRenderer = {
|
|
|
130
156
|
|
|
131
157
|
renderResult(
|
|
132
158
|
result: { content: Array<{ type: string; text?: string }>; details?: GlobToolDetails; isError?: boolean },
|
|
133
|
-
options:
|
|
159
|
+
options: RenderResultContextOptions,
|
|
134
160
|
uiTheme: Theme,
|
|
135
161
|
args?: GlobRenderArgs,
|
|
136
162
|
): Component {
|
|
137
163
|
const details = result.details;
|
|
164
|
+
const linkPaths = globLinkPaths(details, options.renderContext?.rawDetails);
|
|
138
165
|
|
|
139
166
|
if (result.isError || details?.error) {
|
|
140
167
|
const errorText = details?.error || result.content?.find(c => c.type === "text")?.text || "Unknown error";
|
|
@@ -249,14 +276,20 @@ export const globToolRenderer = {
|
|
|
249
276
|
return createCachedComponent(
|
|
250
277
|
() => options.expanded,
|
|
251
278
|
width => {
|
|
252
|
-
const cwd =
|
|
279
|
+
const cwd = linkPaths.cwd;
|
|
253
280
|
const fileLines = renderFileList(
|
|
254
281
|
{
|
|
255
|
-
files: files.map(entry =>
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
282
|
+
files: files.map((entry, index) => {
|
|
283
|
+
const rawFile = linkPaths.files[index];
|
|
284
|
+
return {
|
|
285
|
+
path: entry,
|
|
286
|
+
isDirectory: entry.endsWith("/"),
|
|
287
|
+
absPath:
|
|
288
|
+
cwd && !entry.endsWith("/")
|
|
289
|
+
? path.resolve(cwd, typeof rawFile === "string" ? rawFile : entry)
|
|
290
|
+
: undefined,
|
|
291
|
+
};
|
|
292
|
+
}),
|
|
260
293
|
expanded: options.expanded,
|
|
261
294
|
maxCollapsed: PREVIEW_LIMITS.COLLAPSED_ITEMS,
|
|
262
295
|
hyperlinkFn: fileHyperlink,
|
|
@@ -278,10 +311,11 @@ export const globToolRenderer = {
|
|
|
278
311
|
*/
|
|
279
312
|
describeResult(
|
|
280
313
|
result: { content: Array<{ type: string; text?: string }>; details?: GlobToolDetails; isError?: boolean },
|
|
281
|
-
|
|
314
|
+
options: RenderResultContextOptions,
|
|
282
315
|
args?: GlobRenderArgs,
|
|
283
316
|
): NativeToolView {
|
|
284
317
|
const details = result.details;
|
|
318
|
+
const linkPaths = globLinkPaths(details, options.renderContext?.rawDetails);
|
|
285
319
|
if (result.isError || details?.error) {
|
|
286
320
|
return inlineErrorView(globNativeHead(args), details?.error || resultText(result) || "Unknown error");
|
|
287
321
|
}
|
|
@@ -335,7 +369,7 @@ export const globToolRenderer = {
|
|
|
335
369
|
: head,
|
|
336
370
|
inline: true,
|
|
337
371
|
body: compact<NativeChild>([
|
|
338
|
-
describeGlobFiles(details.files ?? [],
|
|
372
|
+
describeGlobFiles(details.files ?? [], linkPaths.cwd, linkPaths.files),
|
|
339
373
|
footnoteText(compact([reasons.length > 0 && `truncated: ${reasons.join(", ")}`, missingNote])),
|
|
340
374
|
]),
|
|
341
375
|
preview: { lines: NATIVE_PREVIEW_LINES },
|
package/src/tools/grep.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Component } from "../tui";
|
|
2
|
-
import type { NativeToolView, RenderResultOptions } from "./renderer";
|
|
2
|
+
import type { NativeToolView, RenderResultContextOptions, RenderResultOptions } from "./renderer";
|
|
3
3
|
import { type Theme } from "../theme/theme";
|
|
4
4
|
import type { OutputMeta } from "./output-meta";
|
|
5
5
|
import type { TruncationResult } from "./streaming-output";
|
|
@@ -58,7 +58,7 @@ export declare const grepToolRenderer: {
|
|
|
58
58
|
}>;
|
|
59
59
|
details?: GrepToolDetails;
|
|
60
60
|
isError?: boolean;
|
|
61
|
-
}, options:
|
|
61
|
+
}, options: RenderResultContextOptions, uiTheme: Theme, args?: GrepRenderArgs): Component;
|
|
62
62
|
describeCall(args: GrepRenderArgs): NativeToolView;
|
|
63
63
|
/**
|
|
64
64
|
* Inline (§7.3 grep): `Grep “pattern” 5 matches · 2 files in src`, then
|
package/src/tools/grep.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { isRecord, sanitizeText } from "@linxiraos/pi-utils";
|
|
1
3
|
import type { Component } from "../tui";
|
|
2
4
|
import { Text } from "../components/text";
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
+
import type {
|
|
6
|
+
NativeToolHead,
|
|
7
|
+
NativeToolView,
|
|
8
|
+
RenderResultContextOptions,
|
|
9
|
+
RenderResultOptions,
|
|
10
|
+
ToolRenderer,
|
|
11
|
+
} from "./renderer";
|
|
5
12
|
import { compact } from "../native/describe";
|
|
6
13
|
import type { NativeChild } from "../native/node";
|
|
7
14
|
import { footnoteText, inlineErrorView, resultText } from "./native-view";
|
|
@@ -89,10 +96,82 @@ const NATIVE_COLLAPSED_FILES = 2;
|
|
|
89
96
|
|
|
90
97
|
const SEARCH_CODE_FRAME_LINE_RE = /^\s*\*?(\d+)│/;
|
|
91
98
|
|
|
92
|
-
function searchScopeMeta(details: GrepToolDetails | undefined): string | undefined {
|
|
99
|
+
function searchScopeMeta(details: GrepToolDetails | undefined, searchPath?: string): string | undefined {
|
|
93
100
|
if (!details?.scopePath) return undefined;
|
|
94
|
-
const label =
|
|
95
|
-
return
|
|
101
|
+
const label = searchPath ? fileHyperlink(searchPath, details.scopePath) : details.scopePath;
|
|
102
|
+
return `in ${label}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function isStringArray(value: unknown): value is string[] {
|
|
106
|
+
return Array.isArray(value) && value.every((item: unknown) => typeof item === "string");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function isStringRecord(value: unknown): value is Record<string, string> {
|
|
110
|
+
if (!isRecord(value)) return false;
|
|
111
|
+
for (const key in value) {
|
|
112
|
+
if (typeof value[key] !== "string") return false;
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function samePath(filePath: string): string {
|
|
118
|
+
return filePath;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Keep only path metadata from the original details; display text always comes from sanitized details. */
|
|
122
|
+
function grepLinkTargets(details: GrepToolDetails | undefined, rawDetails: unknown) {
|
|
123
|
+
if (details === rawDetails) {
|
|
124
|
+
return {
|
|
125
|
+
target: samePath,
|
|
126
|
+
searchPath: details?.searchPath,
|
|
127
|
+
fileScope: details?.searchPath,
|
|
128
|
+
displayTargets: details?.displayTargets,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const raw = typeof rawDetails === "object" && rawDetails !== null ? rawDetails : {};
|
|
132
|
+
const rawCwd = "cwd" in raw && typeof raw.cwd === "string" ? raw.cwd : undefined;
|
|
133
|
+
const rawSearchPath = "searchPath" in raw && typeof raw.searchPath === "string" ? raw.searchPath : undefined;
|
|
134
|
+
const rawFiles = "files" in raw && isStringArray(raw.files) ? raw.files : undefined;
|
|
135
|
+
const rawDisplayTargets =
|
|
136
|
+
"displayTargets" in raw && isStringRecord(raw.displayTargets) ? raw.displayTargets : undefined;
|
|
137
|
+
let displayTargets = details?.displayTargets;
|
|
138
|
+
if (rawDisplayTargets) {
|
|
139
|
+
displayTargets = {};
|
|
140
|
+
for (const label in rawDisplayTargets) {
|
|
141
|
+
displayTargets[sanitizeText(label)] = rawDisplayTargets[label]!;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const pathTargets = new Map<string, string>();
|
|
146
|
+
const displayBase = details?.cwd ?? details?.searchPath;
|
|
147
|
+
const rawBase = rawCwd ?? rawSearchPath ?? displayBase;
|
|
148
|
+
if (displayBase && rawBase && rawFiles && details?.files) {
|
|
149
|
+
for (let index = 0; index < Math.min(details.files.length, rawFiles.length); index++) {
|
|
150
|
+
const displayFile = path.resolve(displayBase, details.files[index]!);
|
|
151
|
+
const rawFile = path.resolve(rawBase, rawFiles[index]!);
|
|
152
|
+
pathTargets.set(displayFile, rawFile);
|
|
153
|
+
// Folded directory headers must retain the original path as well.
|
|
154
|
+
let displayDir = path.dirname(displayFile);
|
|
155
|
+
let rawDir = path.dirname(rawFile);
|
|
156
|
+
while (displayDir !== path.dirname(displayDir) && rawDir !== path.dirname(rawDir)) {
|
|
157
|
+
pathTargets.set(displayDir, rawDir);
|
|
158
|
+
displayDir = path.dirname(displayDir);
|
|
159
|
+
rawDir = path.dirname(rawDir);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const target = (displayPath: string): string => {
|
|
164
|
+
const known = pathTargets.get(displayPath);
|
|
165
|
+
if (known) return known;
|
|
166
|
+
if (rawCwd && details?.cwd) return path.resolve(rawCwd, path.relative(details.cwd, displayPath));
|
|
167
|
+
return displayPath;
|
|
168
|
+
};
|
|
169
|
+
return {
|
|
170
|
+
target,
|
|
171
|
+
searchPath: rawSearchPath ?? details?.searchPath,
|
|
172
|
+
fileScope: rawSearchPath ?? details?.searchPath,
|
|
173
|
+
displayTargets,
|
|
174
|
+
};
|
|
96
175
|
}
|
|
97
176
|
|
|
98
177
|
function linkUrlLikeSearchHeader(
|
|
@@ -134,6 +213,7 @@ function createSearchLineStyler(
|
|
|
134
213
|
fileScope: string | undefined,
|
|
135
214
|
uiTheme: Theme,
|
|
136
215
|
displayTargets: Record<string, string> | undefined,
|
|
216
|
+
target: (displayPath: string) => string,
|
|
137
217
|
): (index: number) => string {
|
|
138
218
|
const contexts = classifyGroupedLines(lines, headerBase, fileScope);
|
|
139
219
|
// `classifyGroupedLines` can't resolve internal URLs (TUI-only), so track the
|
|
@@ -168,7 +248,7 @@ function createSearchLineStyler(
|
|
|
168
248
|
let styled: string;
|
|
169
249
|
if (ctx.kind === "dir") {
|
|
170
250
|
const accent = uiTheme.fg("accent", line);
|
|
171
|
-
styled = ctx.headerPath ? fileHyperlink(ctx.headerPath, accent) : accent;
|
|
251
|
+
styled = ctx.headerPath ? fileHyperlink(target(ctx.headerPath), accent) : accent;
|
|
172
252
|
} else if (ctx.kind === "file") {
|
|
173
253
|
const raw = urlRaw[index];
|
|
174
254
|
if (raw !== undefined) {
|
|
@@ -176,12 +256,12 @@ function createSearchLineStyler(
|
|
|
176
256
|
} else {
|
|
177
257
|
// Root-level files keep the bright accent; nested file headers are dimmed.
|
|
178
258
|
const tinted = uiTheme.fg(ctx.depth === 1 ? "accent" : "dim", line);
|
|
179
|
-
styled = ctx.headerPath ? fileHyperlink(ctx.headerPath, tinted) : tinted;
|
|
259
|
+
styled = ctx.headerPath ? fileHyperlink(target(ctx.headerPath), tinted) : tinted;
|
|
180
260
|
}
|
|
181
261
|
} else {
|
|
182
262
|
const tinted = uiTheme.fg("toolOutput", line);
|
|
183
263
|
const lineNumber = parseSearchDisplayLineNumber(line);
|
|
184
|
-
const filePath = ctx.filePath
|
|
264
|
+
const filePath = ctx.filePath ? target(ctx.filePath) : urlFiles[index];
|
|
185
265
|
styled = filePath && lineNumber !== undefined ? fileHyperlink(filePath, tinted, { line: lineNumber }) : tinted;
|
|
186
266
|
}
|
|
187
267
|
styledLines[index] = styled;
|
|
@@ -290,7 +370,7 @@ export const grepToolRenderer = {
|
|
|
290
370
|
|
|
291
371
|
renderResult(
|
|
292
372
|
result: { content: Array<{ type: string; text?: string }>; details?: GrepToolDetails; isError?: boolean },
|
|
293
|
-
options:
|
|
373
|
+
options: RenderResultContextOptions,
|
|
294
374
|
uiTheme: Theme,
|
|
295
375
|
args?: GrepRenderArgs,
|
|
296
376
|
): Component {
|
|
@@ -345,6 +425,7 @@ export const grepToolRenderer = {
|
|
|
345
425
|
{ paddingX: 1 },
|
|
346
426
|
);
|
|
347
427
|
}
|
|
428
|
+
const linkTargets = grepLinkTargets(details, options.renderContext?.rawDetails);
|
|
348
429
|
|
|
349
430
|
const matchCount = details?.matchCount ?? 0;
|
|
350
431
|
const fileCount = details?.fileCount ?? 0;
|
|
@@ -366,8 +447,8 @@ export const grepToolRenderer = {
|
|
|
366
447
|
: undefined;
|
|
367
448
|
|
|
368
449
|
if (matchCount === 0) {
|
|
369
|
-
const meta = [
|
|
370
|
-
const scopeMeta = searchScopeMeta(details);
|
|
450
|
+
const meta = ["0 matches"];
|
|
451
|
+
const scopeMeta = searchScopeMeta(details, linkTargets.searchPath);
|
|
371
452
|
if (scopeMeta) meta.push(scopeMeta);
|
|
372
453
|
const header = renderStatusLine(
|
|
373
454
|
{ icon: "warning", title: "Grep", titleColor: "toolTitle", description: args?.pattern, meta },
|
|
@@ -378,14 +459,9 @@ export const grepToolRenderer = {
|
|
|
378
459
|
return new Text(lines.join("\n"), 1, 0);
|
|
379
460
|
}
|
|
380
461
|
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
: tuiTextFmt("grepMatchManyFmt", "%d matches", matchCount);
|
|
385
|
-
const fileMeta =
|
|
386
|
-
fileCount === 1 ? tuiText("grepFileOne", "1 file") : tuiTextFmt("grepFileManyFmt", "%d files", fileCount);
|
|
387
|
-
const meta = [matchMeta, fileMeta];
|
|
388
|
-
const scopeMeta = searchScopeMeta(details);
|
|
462
|
+
const summaryParts = [formatCount("match", matchCount), formatCount("file", fileCount)];
|
|
463
|
+
const meta = [...summaryParts];
|
|
464
|
+
const scopeMeta = searchScopeMeta(details, linkTargets.searchPath);
|
|
389
465
|
if (scopeMeta) meta.push(scopeMeta);
|
|
390
466
|
if (truncated) meta.push(uiTheme.fg("warning", tuiText("grepTruncated", "truncated")));
|
|
391
467
|
const description = args?.pattern ?? undefined;
|
|
@@ -410,9 +486,10 @@ export const grepToolRenderer = {
|
|
|
410
486
|
const styleLine = createSearchLineStyler(
|
|
411
487
|
allLines,
|
|
412
488
|
details?.cwd ?? details?.searchPath,
|
|
413
|
-
|
|
489
|
+
linkTargets.fileScope,
|
|
414
490
|
uiTheme,
|
|
415
|
-
|
|
491
|
+
linkTargets.displayTargets,
|
|
492
|
+
linkTargets.target,
|
|
416
493
|
);
|
|
417
494
|
const matchGroups = groupLineIndicesByBlank(allLines);
|
|
418
495
|
|
|
@@ -493,8 +570,8 @@ export const grepToolRenderer = {
|
|
|
493
570
|
inline: true,
|
|
494
571
|
body: compact<NativeChild>([
|
|
495
572
|
...describeGroupedOutput(textContent.split("\n"), {
|
|
496
|
-
lang: getLanguageFromPath,
|
|
497
573
|
maxFiles: options.expanded ? undefined : NATIVE_COLLAPSED_FILES,
|
|
574
|
+
file: details?.files?.length === 1 ? details.files[0] : undefined,
|
|
498
575
|
}),
|
|
499
576
|
foot,
|
|
500
577
|
]),
|
|
@@ -110,10 +110,13 @@ export interface NumberedLine {
|
|
|
110
110
|
* Numbered `code` for possibly non-contiguous source lines: one block per run
|
|
111
111
|
* of consecutive line numbers (`start` = the run's first line, match lines
|
|
112
112
|
* `mark`ed), with a `…` row wherever the numbers jump or an elision marker
|
|
113
|
-
* (`n: null`) sits.
|
|
113
|
+
* (`n: null`) sits. `path` is the file the lines come from: Tern picks their
|
|
114
|
+
* grammar from it and draws no header (the row or head above names the file).
|
|
115
|
+
* `wrap` soft-wraps long lines instead of scrolling the block sideways.
|
|
114
116
|
*/
|
|
115
117
|
export declare function numberedCode(lines: readonly NumberedLine[], opts?: {
|
|
116
|
-
|
|
118
|
+
path?: string;
|
|
119
|
+
wrap?: boolean;
|
|
117
120
|
}): NativeNode[];
|
|
118
121
|
/**
|
|
119
122
|
* Describe grouped search output (grep / ast-grep / ast-edit display content)
|
|
@@ -121,9 +124,11 @@ export declare function numberedCode(lines: readonly NumberedLine[], opts?: {
|
|
|
121
124
|
* header's suffix or the match count as a chip), then its lines as
|
|
122
125
|
* {@link numberedCode} with match lines marked and `…` between non-adjacent
|
|
123
126
|
* runs. Unnumbered body lines stay plain `code`. `maxFiles` keeps only the
|
|
124
|
-
* first files (the caller says how many were left out).
|
|
127
|
+
* first files (the caller says how many were left out). `file` owns the lines
|
|
128
|
+
* before any header: a single-file search prints none, yet its lines get the
|
|
129
|
+
* same file row as a directory search's.
|
|
125
130
|
*/
|
|
126
131
|
export declare function describeGroupedOutput(lines: readonly string[], options?: {
|
|
127
|
-
lang?: (path: string) => string | undefined;
|
|
128
132
|
maxFiles?: number;
|
|
133
|
+
file?: string;
|
|
129
134
|
}): NativeNode[];
|
|
@@ -314,9 +314,14 @@ export interface NumberedLine {
|
|
|
314
314
|
* Numbered `code` for possibly non-contiguous source lines: one block per run
|
|
315
315
|
* of consecutive line numbers (`start` = the run's first line, match lines
|
|
316
316
|
* `mark`ed), with a `…` row wherever the numbers jump or an elision marker
|
|
317
|
-
* (`n: null`) sits.
|
|
317
|
+
* (`n: null`) sits. `path` is the file the lines come from: Tern picks their
|
|
318
|
+
* grammar from it and draws no header (the row or head above names the file).
|
|
319
|
+
* `wrap` soft-wraps long lines instead of scrolling the block sideways.
|
|
318
320
|
*/
|
|
319
|
-
export function numberedCode(
|
|
321
|
+
export function numberedCode(
|
|
322
|
+
lines: readonly NumberedLine[],
|
|
323
|
+
opts: { path?: string; wrap?: boolean } = {},
|
|
324
|
+
): NativeNode[] {
|
|
320
325
|
const out: NativeNode[] = [];
|
|
321
326
|
let run: { start: number; text: string[]; marks: { line: number; tone: TspTone }[] } | undefined;
|
|
322
327
|
const flush = () => {
|
|
@@ -328,7 +333,8 @@ export function numberedCode(lines: readonly NumberedLine[], opts: { lang?: stri
|
|
|
328
333
|
code(run.text.join("\n"), {
|
|
329
334
|
start: run.start,
|
|
330
335
|
numbers: true,
|
|
331
|
-
|
|
336
|
+
path: opts.path,
|
|
337
|
+
wrap: opts.wrap,
|
|
332
338
|
marks: run.marks.length > 0 ? run.marks : undefined,
|
|
333
339
|
}),
|
|
334
340
|
`c${run.start}`,
|
|
@@ -356,22 +362,26 @@ export function numberedCode(lines: readonly NumberedLine[], opts: { lang?: stri
|
|
|
356
362
|
* header's suffix or the match count as a chip), then its lines as
|
|
357
363
|
* {@link numberedCode} with match lines marked and `…` between non-adjacent
|
|
358
364
|
* runs. Unnumbered body lines stay plain `code`. `maxFiles` keeps only the
|
|
359
|
-
* first files (the caller says how many were left out).
|
|
365
|
+
* first files (the caller says how many were left out). `file` owns the lines
|
|
366
|
+
* before any header: a single-file search prints none, yet its lines get the
|
|
367
|
+
* same file row as a directory search's.
|
|
360
368
|
*/
|
|
361
369
|
export function describeGroupedOutput(
|
|
362
370
|
lines: readonly string[],
|
|
363
|
-
options: {
|
|
371
|
+
options: { maxFiles?: number; file?: string } = {},
|
|
364
372
|
): NativeNode[] {
|
|
365
373
|
const files: NativeNode[] = [];
|
|
366
374
|
const loose: NativeNode[] = [];
|
|
367
375
|
let current: { path: string; suffix?: string; children: NativeNode[]; marks: number } | undefined;
|
|
368
376
|
let numbered: NumberedLine[] = [];
|
|
369
377
|
let plain: string[] = [];
|
|
378
|
+
// `options.file` until the first line or header claims it.
|
|
379
|
+
let headless = options.file;
|
|
370
380
|
|
|
371
381
|
const flushLines = () => {
|
|
372
382
|
const target = current?.children ?? loose;
|
|
373
383
|
if (numbered.length > 0) {
|
|
374
|
-
target.push(...numberedCode(numbered, {
|
|
384
|
+
target.push(...numberedCode(numbered, { path: current?.path }));
|
|
375
385
|
numbered = [];
|
|
376
386
|
}
|
|
377
387
|
if (plain.length > 0) {
|
|
@@ -397,11 +407,16 @@ export function describeGroupedOutput(
|
|
|
397
407
|
|
|
398
408
|
for (const event of walkGroupedOutput(lines)) {
|
|
399
409
|
if (event.kind !== "line") {
|
|
410
|
+
headless = undefined;
|
|
400
411
|
flushFile();
|
|
401
412
|
if (options.maxFiles !== undefined && files.length >= options.maxFiles) break;
|
|
402
413
|
if (event.kind === "file") current = { path: event.path, suffix: event.suffix, children: [], marks: 0 };
|
|
403
414
|
continue;
|
|
404
415
|
}
|
|
416
|
+
if (headless !== undefined) {
|
|
417
|
+
current = { path: headless, children: [], marks: 0 };
|
|
418
|
+
headless = undefined;
|
|
419
|
+
}
|
|
405
420
|
if (GROUPED_GAP_LINE_RE.test(event.text)) {
|
|
406
421
|
numbered.push({ n: null, text: "" });
|
|
407
422
|
continue;
|