@xynogen/pix-pretty 1.7.18 → 1.7.20
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/package.json +1 -1
- package/src/types.ts +2 -0
- package/src/utils.test.ts +53 -3
- package/src/utils.ts +73 -17
package/package.json
CHANGED
package/src/types.ts
CHANGED
|
@@ -185,6 +185,7 @@ export type FindResultDetails = {
|
|
|
185
185
|
_type: "findResult";
|
|
186
186
|
text: string;
|
|
187
187
|
pattern: string;
|
|
188
|
+
path?: string;
|
|
188
189
|
matchCount: number;
|
|
189
190
|
};
|
|
190
191
|
|
|
@@ -192,6 +193,7 @@ export type GrepResultDetails = {
|
|
|
192
193
|
_type: "grepResult";
|
|
193
194
|
text: string;
|
|
194
195
|
pattern: string;
|
|
196
|
+
path?: string;
|
|
195
197
|
matchCount: number;
|
|
196
198
|
};
|
|
197
199
|
|
package/src/utils.test.ts
CHANGED
|
@@ -2,7 +2,14 @@ import { describe, expect, it } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
import { MAX_PREVIEW_LINES } from "./config.js";
|
|
4
4
|
import type { FgTheme } from "./types.js";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
formatCollapsedToolRow,
|
|
7
|
+
hideCollapsedToolCall,
|
|
8
|
+
pluralize,
|
|
9
|
+
renderCollapsedToolRow,
|
|
10
|
+
renderDimPreview,
|
|
11
|
+
setResultDetails,
|
|
12
|
+
} from "./utils.js";
|
|
6
13
|
|
|
7
14
|
// Strip ANSI escapes so assertions test content, not color codes.
|
|
8
15
|
const ANSI = /\x1b\[[0-9;]*m/g;
|
|
@@ -29,6 +36,47 @@ describe("pluralize", () => {
|
|
|
29
36
|
});
|
|
30
37
|
});
|
|
31
38
|
|
|
39
|
+
describe("collapsed tool rows", () => {
|
|
40
|
+
const rowTheme = { fg: (_key: string, text: string) => text, bold: (text: string) => text };
|
|
41
|
+
|
|
42
|
+
it("renders a consistent status, tool, target, and metadata row", () => {
|
|
43
|
+
expect(formatCollapsedToolRow(rowTheme, "read", "src/a.ts", "12 lines")).toBe(
|
|
44
|
+
"✓ read src/a.ts · 12 lines",
|
|
45
|
+
);
|
|
46
|
+
expect(plain(renderCollapsedToolRow(rowTheme, "read", "src/a.ts", "12 lines"))).toContain(
|
|
47
|
+
"✓ read src/a.ts · 12 lines",
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("hides only collapsed, non-expanded call rows", () => {
|
|
52
|
+
let value = "unchanged";
|
|
53
|
+
expect(hideCollapsedToolCall({ collapsed: true }, false, (text) => (value = text))).toBe(true);
|
|
54
|
+
expect(value).toBe("");
|
|
55
|
+
expect(hideCollapsedToolCall({ collapsed: true }, true, () => {})).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("setResultDetails", () => {
|
|
60
|
+
it("preserves upstream metadata while adding renderer details", () => {
|
|
61
|
+
const result = {
|
|
62
|
+
content: [{ type: "text" as const, text: "output" }],
|
|
63
|
+
details: {
|
|
64
|
+
truncation: { truncated: true, totalLines: 500 },
|
|
65
|
+
fullOutputPath: "/tmp/full.log",
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
setResultDetails(result, { _type: "bashResult", exitCode: 0 });
|
|
70
|
+
|
|
71
|
+
expect(result.details as Record<string, unknown>).toEqual({
|
|
72
|
+
truncation: { truncated: true, totalLines: 500 },
|
|
73
|
+
fullOutputPath: "/tmp/full.log",
|
|
74
|
+
_type: "bashResult",
|
|
75
|
+
exitCode: 0,
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
32
80
|
describe("renderDimPreview", () => {
|
|
33
81
|
it("renders 'done' for empty input", () => {
|
|
34
82
|
expect(plain(renderDimPreview("", theme))).toContain("done");
|
|
@@ -79,7 +127,9 @@ describe("renderDimPreview", () => {
|
|
|
79
127
|
expect(plain(raw)).toContain("foo bar foo");
|
|
80
128
|
});
|
|
81
129
|
|
|
82
|
-
it("
|
|
83
|
-
|
|
130
|
+
it("treats regex metacharacters as literal highlight text", () => {
|
|
131
|
+
const raw = renderDimPreview("call(foo)", theme, { highlight: "(" });
|
|
132
|
+
expect(plain(raw)).toContain("call(foo)");
|
|
133
|
+
expect(raw).toContain("\x1b[");
|
|
84
134
|
});
|
|
85
135
|
});
|
package/src/utils.ts
CHANGED
|
@@ -52,6 +52,55 @@ export function pluralize(count: number, noun: string, plural?: string): string
|
|
|
52
52
|
return `${count} ${count === 1 ? noun : (plural ?? `${noun}s`)}`;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
export type CollapsedToolStatus = "success" | "error" | "warning";
|
|
56
|
+
|
|
57
|
+
type CollapsedToolTheme = {
|
|
58
|
+
fg: (
|
|
59
|
+
key: "success" | "error" | "warning" | "toolTitle" | "muted" | "dim",
|
|
60
|
+
text: string,
|
|
61
|
+
) => string;
|
|
62
|
+
bold: (text: string) => string;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** Format the shared one-row content without assuming a render shell. */
|
|
66
|
+
export function formatCollapsedToolRow(
|
|
67
|
+
theme: CollapsedToolTheme,
|
|
68
|
+
tool: string,
|
|
69
|
+
target: string,
|
|
70
|
+
meta = "",
|
|
71
|
+
status: CollapsedToolStatus = "success",
|
|
72
|
+
): string {
|
|
73
|
+
const icon = status === "success" ? "✓" : status === "warning" ? "⚡" : "✗";
|
|
74
|
+
const parts = [
|
|
75
|
+
`${theme.fg(status, icon)} ${theme.fg("toolTitle", theme.bold(tool))}`,
|
|
76
|
+
target ? theme.fg("muted", target) : "",
|
|
77
|
+
meta ? `${theme.fg("dim", "·")} ${theme.fg("dim", meta)}` : "",
|
|
78
|
+
].filter(Boolean);
|
|
79
|
+
return parts.join(" ");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Render shared one-row content for tools using the self-rendered shell. */
|
|
83
|
+
export function renderCollapsedToolRow(
|
|
84
|
+
theme: CollapsedToolTheme,
|
|
85
|
+
tool: string,
|
|
86
|
+
target: string,
|
|
87
|
+
meta = "",
|
|
88
|
+
status: CollapsedToolStatus = "success",
|
|
89
|
+
): string {
|
|
90
|
+
return fillToolBackground(` ${formatCollapsedToolRow(theme, tool, target, meta, status)}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Hide renderCall after its paired result has auto-collapsed. */
|
|
94
|
+
export function hideCollapsedToolCall(
|
|
95
|
+
state: { collapsed?: boolean },
|
|
96
|
+
expanded: boolean,
|
|
97
|
+
setText: (text: string) => void,
|
|
98
|
+
): boolean {
|
|
99
|
+
if (!state.collapsed || expanded) return false;
|
|
100
|
+
setText("");
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
55
104
|
export type DimPreviewOptions = {
|
|
56
105
|
maxLines?: number;
|
|
57
106
|
header?: string;
|
|
@@ -59,21 +108,23 @@ export type DimPreviewOptions = {
|
|
|
59
108
|
highlight?: string;
|
|
60
109
|
};
|
|
61
110
|
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
}
|
|
111
|
+
function dimLineWithHighlight(line: string, theme: FgTheme, pattern?: string): string {
|
|
112
|
+
if (!pattern) return theme.fg("dim", line);
|
|
113
|
+
const foldedLine = line.toLocaleLowerCase();
|
|
114
|
+
const foldedPattern = pattern.toLocaleLowerCase();
|
|
115
|
+
if (!foldedPattern) return theme.fg("dim", line);
|
|
69
116
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
.
|
|
117
|
+
const parts: string[] = [];
|
|
118
|
+
let start = 0;
|
|
119
|
+
for (;;) {
|
|
120
|
+
const match = foldedLine.indexOf(foldedPattern, start);
|
|
121
|
+
if (match < 0) break;
|
|
122
|
+
if (match > start) parts.push(theme.fg("dim", line.slice(start, match)));
|
|
123
|
+
parts.push(`${FG_GREEN}${BOLD}${line.slice(match, match + pattern.length)}${RST}`);
|
|
124
|
+
start = match + pattern.length;
|
|
125
|
+
}
|
|
126
|
+
if (start < line.length) parts.push(theme.fg("dim", line.slice(start)));
|
|
127
|
+
return parts.length > 0 ? parts.join("") : theme.fg("dim", line);
|
|
77
128
|
}
|
|
78
129
|
|
|
79
130
|
export function renderDimPreview(
|
|
@@ -82,12 +133,12 @@ export function renderDimPreview(
|
|
|
82
133
|
opts: DimPreviewOptions = {},
|
|
83
134
|
): string {
|
|
84
135
|
const maxLines = opts.maxLines ?? MAX_PREVIEW_LINES;
|
|
85
|
-
const
|
|
136
|
+
const highlight = opts.highlight;
|
|
86
137
|
const output = normalizeLineEndings(text).trim() || "done";
|
|
87
138
|
const lines = output.split("\n");
|
|
88
139
|
const preview = lines
|
|
89
140
|
.slice(0, maxLines)
|
|
90
|
-
.map((line) => ` ${dimLineWithHighlight(line, theme,
|
|
141
|
+
.map((line) => ` ${dimLineWithHighlight(line, theme, highlight)}`);
|
|
91
142
|
if (opts.header) preview.unshift(` ${theme.fg("dim", opts.header)}`);
|
|
92
143
|
if (lines.length > maxLines) {
|
|
93
144
|
const more = pluralize(lines.length - maxLines, "more line");
|
|
@@ -208,8 +259,13 @@ export function getTextContent(result: ToolResultLike): string {
|
|
|
208
259
|
);
|
|
209
260
|
}
|
|
210
261
|
|
|
262
|
+
/** Add renderer metadata without discarding execution metadata from the upstream tool. */
|
|
211
263
|
export function setResultDetails<T>(result: ToolResultLike, details: T): void {
|
|
212
|
-
|
|
264
|
+
const upstream =
|
|
265
|
+
result.details && typeof result.details === "object"
|
|
266
|
+
? (result.details as Record<string, unknown>)
|
|
267
|
+
: undefined;
|
|
268
|
+
result.details = upstream ? { ...upstream, ...details } : details;
|
|
213
269
|
}
|
|
214
270
|
|
|
215
271
|
export function makeTextResult<TDetails>(
|