@oh-my-pi/pi-tui 18.14.2 → 18.16.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 +64 -50
- package/README.md +1 -1
- package/dist/types/app-keybindings.d.ts +5 -0
- package/dist/types/apps/session-picker.d.ts +5 -3
- package/dist/types/chat/bash-execution.d.ts +4 -1
- package/dist/types/chat/eval-execution.d.ts +2 -0
- package/dist/types/chat/execution-shared.d.ts +14 -8
- package/dist/types/chat/transcript-render-helpers.d.ts +8 -0
- package/dist/types/chrome/dynamic-border.d.ts +6 -0
- package/dist/types/chrome/transcript-status.d.ts +2 -2
- package/dist/types/components/editor.d.ts +15 -6
- package/dist/types/components/loader.d.ts +2 -0
- package/dist/types/keybinding-matchers.d.ts +7 -0
- package/dist/types/overlays/btw-history-panel.d.ts +13 -0
- package/dist/types/overlays/btw-history.d.ts +24 -0
- package/dist/types/overlays/btw-inject.d.ts +22 -0
- package/dist/types/overlays/btw-panel.d.ts +7 -0
- package/dist/types/overlays/hook-editor.d.ts +2 -0
- package/dist/types/overlays/jobs-panel.d.ts +4 -0
- package/dist/types/overlays/session-selector.d.ts +24 -3
- package/dist/types/render/render-utils.d.ts +21 -1
- package/dist/types/status-line/startup.d.ts +0 -15
- package/dist/types/terminal-capabilities.d.ts +1 -1
- package/dist/types/theme/symbols.d.ts +3 -1
- package/dist/types/theme/theme-class.d.ts +1 -0
- package/dist/types/tools/grouped-file-output.d.ts +76 -8
- package/dist/types/tools/hashline-format.d.ts +5 -0
- package/dist/types/tools/native-view.d.ts +2 -0
- package/dist/types/tools/output-meta.d.ts +27 -0
- package/dist/types/tools/read.d.ts +10 -0
- package/dist/types/tools/wait.d.ts +2 -0
- package/dist/types/utils.d.ts +4 -0
- package/package.json +9 -9
- package/src/app-keybindings.ts +5 -0
- package/src/apps/session-picker.ts +7 -3
- package/src/chat/bash-execution.ts +20 -9
- package/src/chat/chat-transcript-builder.ts +5 -2
- package/src/chat/eval-execution.ts +14 -7
- package/src/chat/execution-shared.ts +39 -18
- package/src/chat/read-tool-group.ts +56 -36
- package/src/chat/transcript-render-helpers.ts +52 -6
- package/src/chrome/dynamic-border.ts +28 -2
- package/src/chrome/transcript-status.ts +5 -3
- package/src/components/editor.ts +123 -24
- package/src/components/key-value-list.ts +9 -9
- package/src/components/loader.ts +7 -0
- package/src/components/section.ts +1 -9
- package/src/components/table.ts +1 -9
- package/src/keybinding-matchers.ts +13 -0
- package/src/overlays/btw-history-panel.ts +98 -14
- package/src/overlays/btw-history.ts +65 -0
- package/src/overlays/btw-inject.ts +91 -0
- package/src/overlays/btw-panel.ts +53 -12
- package/src/overlays/hook-editor.ts +6 -0
- package/src/overlays/jobs-panel.ts +45 -19
- package/src/overlays/model-browser.ts +65 -8
- package/src/overlays/session-selector.ts +131 -26
- package/src/overlays/usage-dashboard.ts +2 -2
- package/src/render/code-cell.ts +1 -1
- package/src/render/file-list.ts +4 -7
- package/src/render/render-utils.ts +36 -6
- package/src/setup/scenes/glyph.ts +1 -1
- package/src/status-line/startup.ts +36 -70
- package/src/terminal-capabilities.ts +5 -0
- package/src/theme/symbols.ts +16 -9
- package/src/theme/theme-class.ts +3 -0
- package/src/tmux.ts +1 -0
- package/src/tools/agent-tree.ts +13 -11
- package/src/tools/ask.ts +13 -6
- package/src/tools/ast-edit.ts +48 -93
- package/src/tools/ast-grep.ts +22 -66
- package/src/tools/bash.ts +7 -0
- package/src/tools/default-renderer.ts +1 -1
- package/src/tools/edit.ts +50 -43
- package/src/tools/fetch.ts +2 -1
- package/src/tools/find.ts +6 -3
- package/src/tools/github.ts +14 -46
- package/src/tools/glob.ts +2 -1
- package/src/tools/grep.ts +43 -287
- package/src/tools/grouped-file-output.ts +250 -64
- package/src/tools/hashline-format.ts +5 -0
- package/src/tools/json-tree.ts +1 -5
- package/src/tools/lsp.ts +53 -25
- package/src/tools/native-view.ts +11 -5
- package/src/tools/output-meta.ts +68 -4
- package/src/tools/proc-render.ts +3 -3
- package/src/tools/read.ts +14 -0
- package/src/tools/task.ts +34 -77
- package/src/tools/vibe.ts +4 -4
- package/src/tools/wait.ts +8 -5
- package/src/tools/write.ts +17 -27
- package/src/tools/yield.ts +1 -1
- package/src/utils.ts +10 -0
package/src/tools/ast-grep.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { isRecord, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
2
1
|
import type { Component } from "../tui";
|
|
3
2
|
import { Text } from "../components/text";
|
|
4
3
|
import type { Theme } from "../theme/theme";
|
|
5
|
-
import {
|
|
4
|
+
import { renderStatusLine, renderTreeList } from "../render";
|
|
6
5
|
import {
|
|
7
6
|
appendParseErrorsBulletList,
|
|
8
7
|
createCachedComponent,
|
|
@@ -17,8 +16,13 @@ import {
|
|
|
17
16
|
import {
|
|
18
17
|
classifyGroupedLines,
|
|
19
18
|
createCodeFrameStyler,
|
|
19
|
+
createGroupedRowStyler,
|
|
20
20
|
describeGroupedOutput,
|
|
21
|
+
fitGroupsToBudget,
|
|
22
|
+
groupedLinkTargets,
|
|
21
23
|
groupLineIndicesByBlank,
|
|
24
|
+
groupsByFile,
|
|
25
|
+
isMatchRow,
|
|
22
26
|
} from "./grouped-file-output";
|
|
23
27
|
import { code, compact, node, span } from "../native/describe";
|
|
24
28
|
import type { NativeChild } from "../native/node";
|
|
@@ -128,64 +132,21 @@ export const astGrepToolRenderer = {
|
|
|
128
132
|
|
|
129
133
|
const textContent = result.details?.displayContent ?? result.content?.find(c => c.type === "text")?.text ?? "";
|
|
130
134
|
const allLines = textContent.split("\n");
|
|
131
|
-
//
|
|
132
|
-
// reconstruct across the blank-line groups the tree list collapses by.
|
|
135
|
+
// Classify the whole output so nested directory headers resolve across blank-line groups.
|
|
133
136
|
const contexts = classifyGroupedLines(allLines, details?.cwd ?? details?.searchPath, details?.searchPath);
|
|
134
137
|
const styleCodeFrame = createCodeFrameStyler(allLines, contexts, "toolOutput", uiTheme, args?.lang);
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
isRecord(rawDetails) && typeof rawDetails.searchPath === "string" ? rawDetails.searchPath : undefined;
|
|
145
|
-
const rawContexts =
|
|
146
|
-
rawContent !== undefined && rawLines?.length === allLines.length && sanitizeText(rawContent) === textContent
|
|
147
|
-
? classifyGroupedLines(rawLines, rawCwd ?? rawSearchPath, rawSearchPath)
|
|
148
|
-
: undefined;
|
|
149
|
-
// Style lazily: the collapsed tree list only renders the first few groups,
|
|
150
|
-
// so a large result never styles/hyperlinks rows nobody sees.
|
|
151
|
-
const styledLines: (string | undefined)[] = Array.from({ length: allLines.length }, () => undefined);
|
|
152
|
-
const styleLine = (index: number): string => {
|
|
153
|
-
const cached = styledLines[index];
|
|
154
|
-
if (cached !== undefined) return cached;
|
|
155
|
-
const line = allLines[index]!;
|
|
156
|
-
const ctx = contexts[index]!;
|
|
157
|
-
const rawContext = rawContexts?.[index];
|
|
158
|
-
const target =
|
|
159
|
-
ctx.headerPath && rawContext?.kind === ctx.kind && rawContext.depth === ctx.depth
|
|
160
|
-
? (rawContext.headerPath ?? ctx.headerPath)
|
|
161
|
-
: ctx.headerPath;
|
|
162
|
-
let styled: string;
|
|
163
|
-
if (ctx.kind === "dir") {
|
|
164
|
-
const accent = uiTheme.fg("accent", line);
|
|
165
|
-
styled = target ? fileHyperlink(target, accent) : accent;
|
|
166
|
-
} else if (ctx.kind === "file") {
|
|
167
|
-
const tinted = uiTheme.fg(ctx.depth === 1 ? "accent" : "dim", line);
|
|
168
|
-
styled = target ? fileHyperlink(target, tinted) : tinted;
|
|
169
|
-
} else {
|
|
170
|
-
styled = styleCodeFrame(index) ?? uiTheme.fg(line.startsWith(" meta:") ? "dim" : "toolOutput", line);
|
|
171
|
-
}
|
|
172
|
-
styledLines[index] = styled;
|
|
173
|
-
return styled;
|
|
174
|
-
};
|
|
175
|
-
const matchGroups = groupLineIndicesByBlank(allLines).filter(indices => {
|
|
138
|
+
const styleLine = createGroupedRowStyler(allLines, contexts, uiTheme, {
|
|
139
|
+
linkTarget: groupedLinkTargets(contexts, textContent, details, options.renderContext?.rawDetails),
|
|
140
|
+
styleBody: index => {
|
|
141
|
+
const line = allLines[index]!;
|
|
142
|
+
return styleCodeFrame(index) ?? uiTheme.fg(line.startsWith(" meta:") ? "dim" : "toolOutput", line);
|
|
143
|
+
},
|
|
144
|
+
linkLines: true,
|
|
145
|
+
});
|
|
146
|
+
const matchGroups = groupsByFile(groupLineIndicesByBlank(allLines), contexts).filter(indices => {
|
|
176
147
|
const first = allLines[indices[0]!]!;
|
|
177
148
|
return !first.startsWith("Result limit reached") && !first.startsWith("Parse issues:");
|
|
178
149
|
});
|
|
179
|
-
// Line offset at which each group starts. A collapsed tree list stops at the
|
|
180
|
-
// first group that overflows its line budget, so groups starting at or past
|
|
181
|
-
// the budget are never shown — hand it same-length placeholders instead of
|
|
182
|
-
// styling them (renderTreeList only needs their line counts).
|
|
183
|
-
const groupStarts: number[] = [];
|
|
184
|
-
let groupStart = 0;
|
|
185
|
-
for (const group of matchGroups) {
|
|
186
|
-
groupStarts.push(groupStart);
|
|
187
|
-
groupStart += group.length;
|
|
188
|
-
}
|
|
189
150
|
|
|
190
151
|
const extraLines: string[] = [];
|
|
191
152
|
if (limitReached) {
|
|
@@ -200,18 +161,13 @@ export const astGrepToolRenderer = {
|
|
|
200
161
|
return createCachedComponent(
|
|
201
162
|
() => options.expanded,
|
|
202
163
|
width => {
|
|
164
|
+
const { shown, summary } = fitGroupsToBudget(
|
|
165
|
+
matchGroups,
|
|
166
|
+
options.expanded ? Number.POSITIVE_INFINITY : COLLAPSED_MATCH_LIMIT,
|
|
167
|
+
{ isUnit: index => isMatchRow(allLines[index]!), unit: "match", total: matchCount },
|
|
168
|
+
);
|
|
203
169
|
const matchLines = renderTreeList(
|
|
204
|
-
{
|
|
205
|
-
items: matchGroups,
|
|
206
|
-
expanded: options.expanded,
|
|
207
|
-
maxCollapsed: matchGroups.length,
|
|
208
|
-
maxCollapsedLines: COLLAPSED_MATCH_LIMIT,
|
|
209
|
-
itemType: "match",
|
|
210
|
-
renderItem: (group, context) =>
|
|
211
|
-
!options.expanded && groupStarts[context.index]! >= COLLAPSED_MATCH_LIMIT
|
|
212
|
-
? Array.from({ length: group.length }, () => "")
|
|
213
|
-
: group.map(styleLine),
|
|
214
|
-
},
|
|
170
|
+
{ items: shown, trailingSummary: summary, renderItem: group => group.map(styleLine) },
|
|
215
171
|
uiTheme,
|
|
216
172
|
);
|
|
217
173
|
return fitHeaderedRows(header, [...matchLines, ...extraLines], width);
|
package/src/tools/bash.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
replaceTabs,
|
|
14
14
|
} from "../render/render-utils";
|
|
15
15
|
import {
|
|
16
|
+
formatMinimizedMeta,
|
|
16
17
|
formatStyledTruncationWarning,
|
|
17
18
|
type OutputMeta,
|
|
18
19
|
stripOutputNotice,
|
|
@@ -351,6 +352,10 @@ function bashStatsParts(
|
|
|
351
352
|
if (artifactId) {
|
|
352
353
|
statsParts.push(`Artifact: ${artifactId}`);
|
|
353
354
|
}
|
|
355
|
+
const minimized = details?.meta?.minimized;
|
|
356
|
+
if (minimized) {
|
|
357
|
+
statsParts.push(`Minimized: ${formatMinimizedMeta(minimized)}`);
|
|
358
|
+
}
|
|
354
359
|
if (isError && typeof details?.exitCode === "number") {
|
|
355
360
|
statsParts.push(`Exit: ${details.exitCode}`);
|
|
356
361
|
}
|
|
@@ -400,6 +405,8 @@ function shellFootParts(details: BashToolDetails | undefined, artifactId: string
|
|
|
400
405
|
if (service.pid !== undefined) parts.push(`PID ${service.pid}`);
|
|
401
406
|
}
|
|
402
407
|
if (artifactId) parts.push(`Artifact ${artifactId}`);
|
|
408
|
+
const minimized = details?.meta?.minimized;
|
|
409
|
+
if (minimized) parts.push(`Minimized ${formatMinimizedMeta(minimized)}`);
|
|
403
410
|
return parts;
|
|
404
411
|
}
|
|
405
412
|
|
package/src/tools/edit.ts
CHANGED
|
@@ -45,7 +45,13 @@ import {
|
|
|
45
45
|
import type { ToolActivityContext, ToolActivitySummary } from "./renderer";
|
|
46
46
|
import { fileHyperlink, Hasher, type RenderCache, renderStatusLine, truncateToWidth, WidthAwareText } from "../render";
|
|
47
47
|
import { framedToolCard } from "../render/tool-card";
|
|
48
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
HASHLINE_TAG_SUFFIX_RE,
|
|
50
|
+
HL_FILE_PREFIX,
|
|
51
|
+
HL_FILE_SUFFIX,
|
|
52
|
+
HL_MOVE_KEYWORD,
|
|
53
|
+
HL_REM_KEYWORD,
|
|
54
|
+
} from "./hashline-format";
|
|
49
55
|
|
|
50
56
|
/** Edit payload syntax selected by the caller. */
|
|
51
57
|
export type EditMode = "replace" | "patch" | "hashline" | "apply_patch" | "sloppy";
|
|
@@ -354,7 +360,8 @@ function renderEditHeader(
|
|
|
354
360
|
width: number,
|
|
355
361
|
uiTheme: Theme,
|
|
356
362
|
options: {
|
|
357
|
-
icon?: "pending" | "success" | "error";
|
|
363
|
+
icon?: "pending" | "running" | "success" | "error";
|
|
364
|
+
spinnerFrame?: number;
|
|
358
365
|
iconOverride?: string;
|
|
359
366
|
op?: Operation;
|
|
360
367
|
rawPath: string;
|
|
@@ -380,6 +387,7 @@ function renderEditHeader(
|
|
|
380
387
|
renderStatusLine(
|
|
381
388
|
{
|
|
382
389
|
icon: options.icon,
|
|
390
|
+
spinnerFrame: options.spinnerFrame,
|
|
383
391
|
iconOverride: options.iconOverride,
|
|
384
392
|
title,
|
|
385
393
|
description,
|
|
@@ -546,8 +554,6 @@ function formatStreamingDiff(
|
|
|
546
554
|
width: number,
|
|
547
555
|
uiTheme: Theme,
|
|
548
556
|
expanded: boolean,
|
|
549
|
-
label = "streaming",
|
|
550
|
-
spinnerFrame?: number,
|
|
551
557
|
cache?: RenderedStringCache,
|
|
552
558
|
): string {
|
|
553
559
|
if (!diff) return "";
|
|
@@ -564,7 +570,7 @@ function formatStreamingDiff(
|
|
|
564
570
|
// innerWidth/budget are in the cache salt so a resize re-slices.
|
|
565
571
|
const innerWidth = Math.max(1, width - 2);
|
|
566
572
|
const budget = expanded ? previewWindowRows() : Math.min(PREVIEW_LIMITS.EXPANDED_LINES, previewWindowRows());
|
|
567
|
-
|
|
573
|
+
return cachedRenderedString(cache, uiTheme, expanded, `${rawPath}:${innerWidth}:${budget}`, diff, () => {
|
|
568
574
|
// "Cursor" tail window: pin the last rows to the bottom so freshly streamed
|
|
569
575
|
// changes stay on screen. The whole-file diff is recomputed every chunk and
|
|
570
576
|
// its Myers alignment is not monotonic in payload length, so a hunk-aware
|
|
@@ -580,18 +586,6 @@ function formatStreamingDiff(
|
|
|
580
586
|
rendered += renderDiffColored(tail.content, { filePath: rawPath, theme: uiTheme });
|
|
581
587
|
return rendered;
|
|
582
588
|
});
|
|
583
|
-
// The animated glyph rides this trailing line — inside the transcript's
|
|
584
|
-
// volatile-tail holdback — never the block header: an animating head row
|
|
585
|
-
// pins the native-scrollback commit boundary at the top of the block, so a
|
|
586
|
-
// tall expanded preview could never scroll-append mid-stream.
|
|
587
|
-
const spinner = spinnerFrame !== undefined ? `${formatStatusIcon("running", uiTheme, spinnerFrame)} ` : "";
|
|
588
|
-
// Expanded approval previews hide the "(preview)" label (#1992) but keep
|
|
589
|
-
// the animated glyph when one is active so the volatile tail stays live.
|
|
590
|
-
const hideLabel = expanded && label === "preview";
|
|
591
|
-
if (spinner || !hideLabel) {
|
|
592
|
-
text += `\n${hideLabel ? spinner.trimEnd() : `${spinner}${uiTheme.fg("dim", `(${label})`)}`}`;
|
|
593
|
-
}
|
|
594
|
-
return text;
|
|
595
589
|
}
|
|
596
590
|
|
|
597
591
|
function formatMultiFileStreamingDiff(
|
|
@@ -599,7 +593,6 @@ function formatMultiFileStreamingDiff(
|
|
|
599
593
|
width: number,
|
|
600
594
|
uiTheme: Theme,
|
|
601
595
|
expanded: boolean,
|
|
602
|
-
spinnerFrame?: number,
|
|
603
596
|
caches?: RenderedStringCache[],
|
|
604
597
|
): string {
|
|
605
598
|
const parts: string[] = [];
|
|
@@ -612,19 +605,19 @@ function formatMultiFileStreamingDiff(
|
|
|
612
605
|
continue;
|
|
613
606
|
}
|
|
614
607
|
if (preview.diff) {
|
|
615
|
-
// Only the last file's preview carries the animated streaming glyph;
|
|
616
|
-
// earlier files have settled and must stay byte-stable so their rows
|
|
617
|
-
// can commit to native scrollback mid-stream.
|
|
618
|
-
const isLast = index === previews.length - 1;
|
|
619
608
|
const cache = previewCacheAt(caches, index);
|
|
620
|
-
parts.push(
|
|
621
|
-
`${header}${formatStreamingDiff(preview.diff, preview.path, width, uiTheme, expanded, "preview", isLast ? spinnerFrame : undefined, cache)}`,
|
|
622
|
-
);
|
|
609
|
+
parts.push(`${header}${formatStreamingDiff(preview.diff, preview.path, width, uiTheme, expanded, cache)}`);
|
|
623
610
|
}
|
|
624
611
|
}
|
|
625
612
|
return parts.join("");
|
|
626
613
|
}
|
|
627
614
|
|
|
615
|
+
/** A call's streamed preview body, and the label its card shows in the bottom border. */
|
|
616
|
+
interface CallPreview {
|
|
617
|
+
text: string;
|
|
618
|
+
footer?: "preview" | "streaming";
|
|
619
|
+
}
|
|
620
|
+
|
|
628
621
|
function getCallPreview(
|
|
629
622
|
args: EditRenderArgs,
|
|
630
623
|
rawPath: string,
|
|
@@ -632,41 +625,49 @@ function getCallPreview(
|
|
|
632
625
|
uiTheme: Theme,
|
|
633
626
|
renderContext: EditRenderContext | undefined,
|
|
634
627
|
expanded: boolean,
|
|
635
|
-
spinnerFrame?: number,
|
|
636
628
|
caches?: RenderedStringCache[],
|
|
637
|
-
):
|
|
629
|
+
): CallPreview {
|
|
638
630
|
const multi = renderContext?.perFileDiffPreview;
|
|
639
631
|
if (multi && multi.length > 1 && multi.some(p => p.diff || p.error)) {
|
|
640
|
-
return
|
|
632
|
+
return {
|
|
633
|
+
text: formatMultiFileStreamingDiff(multi, width, uiTheme, expanded, caches),
|
|
634
|
+
footer: multi.some(p => p.diff) ? "preview" : undefined,
|
|
635
|
+
};
|
|
641
636
|
}
|
|
642
637
|
const cache = previewCacheAt(caches, 0);
|
|
643
638
|
if (args.previewDiff) {
|
|
644
|
-
return
|
|
639
|
+
return {
|
|
640
|
+
text: formatStreamingDiff(args.previewDiff, rawPath, width, uiTheme, expanded, cache),
|
|
641
|
+
footer: "preview",
|
|
642
|
+
};
|
|
645
643
|
}
|
|
646
644
|
if (args.diff && args.op) {
|
|
647
|
-
return formatStreamingDiff(args.diff, rawPath, width, uiTheme, expanded, "streaming"
|
|
645
|
+
return { text: formatStreamingDiff(args.diff, rawPath, width, uiTheme, expanded, cache), footer: "streaming" };
|
|
648
646
|
}
|
|
649
647
|
if (args.diff) {
|
|
650
|
-
return renderPlainTextPreview(args.diff, uiTheme, rawPath);
|
|
648
|
+
return { text: renderPlainTextPreview(args.diff, uiTheme, rawPath) };
|
|
651
649
|
}
|
|
652
650
|
if (args.newText || args.patch) {
|
|
653
|
-
return renderPlainTextPreview(args.newText ?? args.patch ?? "", uiTheme, rawPath);
|
|
651
|
+
return { text: renderPlainTextPreview(args.newText ?? args.patch ?? "", uiTheme, rawPath) };
|
|
654
652
|
}
|
|
655
653
|
if (renderContext?.editStreamingFallback) {
|
|
656
|
-
return renderContext.editStreamingFallback;
|
|
654
|
+
return { text: renderContext.editStreamingFallback };
|
|
657
655
|
}
|
|
658
656
|
const contextPreview = renderContext?.editDiffPreview;
|
|
659
657
|
if (contextPreview && "error" in contextPreview && contextPreview.error) {
|
|
660
|
-
return uiTheme.fg("error", replaceTabs(contextPreview.error));
|
|
658
|
+
return { text: uiTheme.fg("error", replaceTabs(contextPreview.error)) };
|
|
661
659
|
}
|
|
662
|
-
return "";
|
|
660
|
+
return { text: "" };
|
|
663
661
|
}
|
|
664
662
|
|
|
665
663
|
const MISSING_APPLY_PATCH_END_ERROR = "The last line of the patch must be '*** End Patch'";
|
|
666
664
|
|
|
665
|
+
/** A model-typed `[path#tag]` header may spell the tag in lowercase. */
|
|
666
|
+
const TYPED_TAG_SUFFIX_RE = new RegExp(HASHLINE_TAG_SUFFIX_RE.source, "i");
|
|
667
|
+
|
|
667
668
|
function normalizeHashlineInputPreviewPath(rawPath: string): string {
|
|
668
669
|
const trimmed = rawPath.trim();
|
|
669
|
-
const hashStart =
|
|
670
|
+
const hashStart = TYPED_TAG_SUFFIX_RE.exec(trimmed)?.index;
|
|
670
671
|
const withoutHash = hashStart === undefined ? trimmed : trimmed.slice(0, hashStart);
|
|
671
672
|
if (withoutHash.length < 2) return withoutHash;
|
|
672
673
|
const first = withoutHash[0];
|
|
@@ -1143,34 +1144,40 @@ export const editToolRenderer = {
|
|
|
1143
1144
|
return framedToolCard(
|
|
1144
1145
|
uiTheme,
|
|
1145
1146
|
({ width }) => {
|
|
1146
|
-
//
|
|
1147
|
-
//
|
|
1148
|
-
// would pin the commit boundary at the top, and the pending hourglass
|
|
1149
|
-
// just adds noise. The liveness cue rides the trailing "(preview)" /
|
|
1150
|
-
// "(streaming)" line instead.
|
|
1147
|
+
// Only a live spinner fills the icon slot: a pending hourglass after it
|
|
1148
|
+
// stops would read as a step backwards while the edit executes.
|
|
1151
1149
|
const header = renderEditHeader(width, uiTheme, {
|
|
1150
|
+
icon: options.spinnerFrame === undefined ? undefined : "running",
|
|
1151
|
+
spinnerFrame: options.spinnerFrame,
|
|
1152
1152
|
op,
|
|
1153
1153
|
rawPath,
|
|
1154
1154
|
rename,
|
|
1155
1155
|
extraSuffix: fileCount > 1 ? uiTheme.fg("dim", ` (+${fileCount - 1} more)`) : undefined,
|
|
1156
1156
|
});
|
|
1157
|
-
|
|
1157
|
+
const preview = getCallPreview(
|
|
1158
1158
|
editArgs,
|
|
1159
1159
|
rawPath,
|
|
1160
1160
|
width,
|
|
1161
1161
|
uiTheme,
|
|
1162
1162
|
renderContext,
|
|
1163
1163
|
options.expanded,
|
|
1164
|
-
options?.spinnerFrame,
|
|
1165
1164
|
callPreviewCaches,
|
|
1166
1165
|
);
|
|
1166
|
+
let body = preview.text;
|
|
1167
1167
|
if (applyPatchError) {
|
|
1168
1168
|
body += `\n${uiTheme.fg("error", truncateToWidth(replaceTabs(applyPatchError), Math.max(1, width - 2)))}`;
|
|
1169
1169
|
}
|
|
1170
1170
|
const bodyLines = body ? body.split("\n") : [];
|
|
1171
1171
|
while (bodyLines.length > 0 && bodyLines[0].trim() === "") bodyLines.shift();
|
|
1172
|
+
// Expanded approval previews hide the "preview" label (#1992); "streaming" ends with the args.
|
|
1173
|
+
const footer =
|
|
1174
|
+
(preview.footer === "preview" && options.expanded) ||
|
|
1175
|
+
(preview.footer === "streaming" && options.argsComplete)
|
|
1176
|
+
? undefined
|
|
1177
|
+
: preview.footer;
|
|
1172
1178
|
return {
|
|
1173
1179
|
header,
|
|
1180
|
+
footerMeta: footer ? uiTheme.fg("dim", footer) : undefined,
|
|
1174
1181
|
sections:
|
|
1175
1182
|
bodyLines.length > 0
|
|
1176
1183
|
? [{ content: bodyLines.flatMap(line => wrapEditRendererLine(line, Math.max(1, width - 2))) }]
|
package/src/tools/fetch.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { framedToolCard } from "../render/tool-card";
|
|
|
14
14
|
import {
|
|
15
15
|
formatExpandHint,
|
|
16
16
|
formatMoreItems,
|
|
17
|
+
formatStatusIcon,
|
|
17
18
|
getDomain,
|
|
18
19
|
PREVIEW_LIMITS,
|
|
19
20
|
sanitizeDisplayLines,
|
|
@@ -274,7 +275,7 @@ export function renderReadUrlResult(
|
|
|
274
275
|
metadataLines.push(`${uiTheme.fg("muted", "Lines:")} ${lineLabel}`);
|
|
275
276
|
metadataLines.push(`${uiTheme.fg("muted", "Chars:")} ${charCount}`);
|
|
276
277
|
if (truncated) {
|
|
277
|
-
metadataLines.push(
|
|
278
|
+
metadataLines.push(`${formatStatusIcon("warning", uiTheme)} ${uiTheme.fg("warning", "Output truncated")}`);
|
|
278
279
|
if (truncation?.artifactId) metadataLines.push(formatStyledArtifactReference(truncation.artifactId, uiTheme));
|
|
279
280
|
}
|
|
280
281
|
if (hasNotes) {
|
package/src/tools/find.ts
CHANGED
|
@@ -10,10 +10,12 @@ import { Text } from "../components/text";
|
|
|
10
10
|
import { fileHyperlink, getTreeBranch, renderStatusLine, renderTreeList, uriHyperlink } from "../render";
|
|
11
11
|
import {
|
|
12
12
|
createCachedComponent,
|
|
13
|
+
FILE_NAME_COLOR,
|
|
13
14
|
fitHeaderedRows,
|
|
14
15
|
formatCount,
|
|
15
16
|
formatEmptyMessage,
|
|
16
17
|
formatErrorMessage,
|
|
18
|
+
formatStyledPath,
|
|
17
19
|
PREVIEW_LIMITS,
|
|
18
20
|
replaceTabs,
|
|
19
21
|
} from "../render/render-utils";
|
|
@@ -168,9 +170,10 @@ function renderHit(hit: FindHit, rangeLimit: number, cwd: string | undefined, ra
|
|
|
168
170
|
return absPath === undefined ? text : fileHyperlink(absPath, text, { line });
|
|
169
171
|
};
|
|
170
172
|
const coverage = hit.truncated ? `${hit.linesSeen} lines judged, partial` : `${hit.linesSeen} lines judged`;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
173
|
+
// URL hits show whole in the file-name color; file hits get the dim directory.
|
|
174
|
+
const label = isUrlHit ? theme.fg(FILE_NAME_COLOR, hit.rel) : formatStyledPath(hit.rel, theme);
|
|
175
|
+
const score = theme.fg(scoreColor(hit.contentScore), hit.contentScore.toFixed(2));
|
|
176
|
+
const lines = [`${gauge(hit.contentScore, theme)} ${score} ${link(label)} ${theme.fg("dim", coverage)}`];
|
|
174
177
|
const ranges = [...hit.ranges].sort((a, b) => b.p - a.p || a.start - b.start).slice(0, rangeLimit);
|
|
175
178
|
const lang = getLanguageFromPath(hit.rel);
|
|
176
179
|
ranges.forEach((range, index) => {
|
package/src/tools/github.ts
CHANGED
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
formatStatusIcon,
|
|
19
19
|
PREVIEW_LIMITS,
|
|
20
20
|
replaceTabs,
|
|
21
|
-
type ToolUIColor,
|
|
22
21
|
type ToolUIStatus,
|
|
23
22
|
TRUNCATE_LENGTHS,
|
|
24
23
|
sanitizeDisplayWarning,
|
|
@@ -165,52 +164,22 @@ function formatRunLine(run: GhRunWatchRunDetails, theme: Theme): string {
|
|
|
165
164
|
return [title, ...meta].join(" ");
|
|
166
165
|
}
|
|
167
166
|
|
|
168
|
-
function getJobStateVisual(
|
|
169
|
-
job
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (job.conclusion && FAILURE_CONCLUSIONS.has(job.conclusion)) {
|
|
181
|
-
return {
|
|
182
|
-
iconRaw: theme.status.error,
|
|
183
|
-
iconColor: "error",
|
|
184
|
-
textColor: "error",
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (job.status && RUNNING_STATUSES.has(job.status)) {
|
|
189
|
-
return {
|
|
190
|
-
iconRaw: theme.status.enabled,
|
|
191
|
-
iconColor: "warning",
|
|
192
|
-
textColor: "warning",
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (job.status && PENDING_STATUSES.has(job.status)) {
|
|
197
|
-
return {
|
|
198
|
-
iconRaw: theme.status.shadowed,
|
|
199
|
-
iconColor: "muted",
|
|
200
|
-
textColor: "muted",
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return {
|
|
205
|
-
iconRaw: theme.status.shadowed,
|
|
206
|
-
iconColor: "muted",
|
|
207
|
-
textColor: "muted",
|
|
208
|
-
};
|
|
167
|
+
function getJobStateVisual(job: GhRunWatchJobDetails, theme: Theme): { icon: string; textColor: ThemeColor } {
|
|
168
|
+
if (job.conclusion && SUCCESS_CONCLUSIONS.has(job.conclusion))
|
|
169
|
+
return { icon: formatStatusIcon("success", theme), textColor: "success" };
|
|
170
|
+
if (job.conclusion && FAILURE_CONCLUSIONS.has(job.conclusion))
|
|
171
|
+
return { icon: formatStatusIcon("error", theme), textColor: "error" };
|
|
172
|
+
if (job.status && RUNNING_STATUSES.has(job.status))
|
|
173
|
+
return { icon: formatStatusIcon("running", theme), textColor: "warning" };
|
|
174
|
+
if (job.status && PENDING_STATUSES.has(job.status))
|
|
175
|
+
return { icon: formatStatusIcon("pending", theme), textColor: "muted" };
|
|
176
|
+
// An unrecognized state (e.g. a finished job with a new conclusion) claims neither queued nor settled.
|
|
177
|
+
return { icon: theme.styledSymbol("status.shadowed", "muted"), textColor: "muted" };
|
|
209
178
|
}
|
|
210
179
|
|
|
211
180
|
function renderJobLine(job: GhRunWatchJobDetails, width: number, theme: Theme): string {
|
|
212
181
|
const visual = getJobStateVisual(job, theme);
|
|
213
|
-
const prefix =
|
|
182
|
+
const prefix = `${visual.icon} `;
|
|
214
183
|
const durationLabel = job.durationSeconds !== undefined ? `${job.durationSeconds}s` : undefined;
|
|
215
184
|
const styledDuration = durationLabel ? theme.fg(visual.textColor, durationLabel) : undefined;
|
|
216
185
|
const reservedWidth = visibleWidth(prefix) + (styledDuration ? 1 + visibleWidth(styledDuration) : 0);
|
|
@@ -255,9 +224,8 @@ function renderFailedLogs(
|
|
|
255
224
|
const lines: string[] = [];
|
|
256
225
|
for (const entry of failedLogs) {
|
|
257
226
|
const context = entry.workflowName ? `${entry.workflowName} #${entry.runId}` : `run #${entry.runId}`;
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
);
|
|
227
|
+
const jobName = theme.fg("error", replaceTabs(entry.jobName));
|
|
228
|
+
lines.push(`${formatStatusIcon("error", theme)} ${jobName} ${theme.fg("muted", context)}`);
|
|
261
229
|
|
|
262
230
|
if (!entry.available || !entry.tail) {
|
|
263
231
|
lines.push(theme.fg("dim", truncateVisualWidth(` ${formatUnavailableLog(entry)}`, Math.max(8, width))));
|
package/src/tools/glob.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
formatCount,
|
|
23
23
|
formatEmptyMessage,
|
|
24
24
|
formatErrorMessage,
|
|
25
|
+
formatStyledPath,
|
|
25
26
|
PREVIEW_LIMITS,
|
|
26
27
|
} from "../render/render-utils";
|
|
27
28
|
import { formatFullOutputReference } from "./output-meta";
|
|
@@ -202,7 +203,7 @@ export const globToolRenderer = {
|
|
|
202
203
|
expanded: options.expanded,
|
|
203
204
|
maxCollapsed: PREVIEW_LIMITS.COLLAPSED_ITEMS,
|
|
204
205
|
itemType: "file",
|
|
205
|
-
renderItem: line =>
|
|
206
|
+
renderItem: line => formatStyledPath(line, uiTheme),
|
|
206
207
|
},
|
|
207
208
|
uiTheme,
|
|
208
209
|
);
|