@pify/pretty 0.2.0 → 0.3.1
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/README.md +16 -0
- package/extensions/pretty.ts +50 -12
- package/package.json +3 -3
- package/src/config.ts +4 -1
- package/src/preview.ts +5 -0
- package/src/settings.ts +93 -0
- package/src/summary.ts +49 -12
package/README.md
CHANGED
|
@@ -34,6 +34,22 @@ Aliases are accepted where they're obvious: `list`/`dir` → `ls`, `search`/`rg`
|
|
|
34
34
|
|
|
35
35
|
Expanded bodies are capped at 200 lines (v0.2) — a 5,000-line diff or grep result used to render in full and scroll the conversation away.
|
|
36
36
|
|
|
37
|
+
## Settings (v0.3)
|
|
38
|
+
|
|
39
|
+
Every cap in a renderer is somebody's taste, and the right number depends on your terminal. Put them in `.pi/pretty.json` (project) or `<agentDir>/pretty.json` (global — the project file wins):
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"collapsedLines": 12,
|
|
44
|
+
"expandedLines": 200,
|
|
45
|
+
"diffLines": 200,
|
|
46
|
+
"syntaxHighlight": true,
|
|
47
|
+
"summaryClip": 100
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Unknown keys, wrong types, and absurd numbers are reported at session start and fall back to the shipped defaults rather than taking the renderers down — a typo tells you it was a typo instead of quietly doing nothing. `/pretty` shows the settings in force and where they came from. (The case for making these configurable is [`pi-cc-extensions`](https://github.com/minuque/pi-cc-extensions)' `/ccstyle`, which does it with a whole settings panel.)
|
|
52
|
+
|
|
37
53
|
## How it works
|
|
38
54
|
|
|
39
55
|
The official `built-in-tool-renderer` pattern: each tool is re-registered with `createReadTool()`/`createBashTool()`/… delegating `execute` untouched, overriding only `renderCall`/`renderResult`. Highlighting uses pi's exported `highlightCode` + `getLanguageFromPath` — zero extra dependencies, colors always match your theme.
|
package/extensions/pretty.ts
CHANGED
|
@@ -21,12 +21,15 @@ import {
|
|
|
21
21
|
createLsTool,
|
|
22
22
|
createReadTool,
|
|
23
23
|
createWriteTool,
|
|
24
|
+
getAgentDir,
|
|
24
25
|
getLanguageFromPath,
|
|
25
26
|
highlightCode,
|
|
26
27
|
type ExtensionAPI,
|
|
27
28
|
type ExtensionContext,
|
|
28
29
|
} from "@earendil-works/pi-coding-agent";
|
|
29
30
|
import { Text } from "@earendil-works/pi-tui";
|
|
31
|
+
import { readFileSync } from "node:fs";
|
|
32
|
+
import { join } from "node:path";
|
|
30
33
|
|
|
31
34
|
import {
|
|
32
35
|
PRETTY_CONFIG,
|
|
@@ -37,7 +40,8 @@ import {
|
|
|
37
40
|
statusLines,
|
|
38
41
|
} from "../src/config.ts";
|
|
39
42
|
import { colorizeDiff, diffStats, statsLabel } from "../src/diff.ts";
|
|
40
|
-
import { preview } from "../src/preview.ts";
|
|
43
|
+
import { limitsFrom, preview } from "../src/preview.ts";
|
|
44
|
+
import { DEFAULT_SETTINGS, formatSettings, resolveSettings, type PrettySettings } from "../src/settings.ts";
|
|
41
45
|
import {
|
|
42
46
|
bashCall,
|
|
43
47
|
bashSummary,
|
|
@@ -68,6 +72,36 @@ type AnyTool = {
|
|
|
68
72
|
|
|
69
73
|
export default function pretty(pi: ExtensionAPI) {
|
|
70
74
|
let config: PrettyConfig = DEFAULT_CONFIG;
|
|
75
|
+
let settings: PrettySettings = DEFAULT_SETTINGS;
|
|
76
|
+
let settingsSource: string | null = null;
|
|
77
|
+
let settingsWarnings: string[] = [];
|
|
78
|
+
|
|
79
|
+
/** Project settings win over global ones; neither is required. */
|
|
80
|
+
function loadSettings(cwd: string): void {
|
|
81
|
+
const candidates = [join(cwd, ".pi", "pretty.json"), join(getAgentDir(), "pretty.json")];
|
|
82
|
+
for (const file of candidates) {
|
|
83
|
+
let raw: string;
|
|
84
|
+
try {
|
|
85
|
+
raw = readFileSync(file, "utf8");
|
|
86
|
+
} catch {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const parsed = resolveSettings(JSON.parse(raw));
|
|
91
|
+
settings = parsed.settings;
|
|
92
|
+
settingsWarnings = parsed.warnings;
|
|
93
|
+
settingsSource = file;
|
|
94
|
+
} catch (err) {
|
|
95
|
+
settings = DEFAULT_SETTINGS;
|
|
96
|
+
settingsSource = null;
|
|
97
|
+
settingsWarnings = [`${file}: ${err instanceof Error ? err.message : String(err)}`];
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
settings = DEFAULT_SETTINGS;
|
|
102
|
+
settingsSource = null;
|
|
103
|
+
settingsWarnings = [];
|
|
104
|
+
}
|
|
71
105
|
let originals: Record<PrettyTool, AnyTool> | null = null;
|
|
72
106
|
|
|
73
107
|
function isFailed(result: unknown): boolean {
|
|
@@ -92,7 +126,7 @@ export default function pretty(pi: ExtensionAPI) {
|
|
|
92
126
|
case "read":
|
|
93
127
|
return {
|
|
94
128
|
renderCall: (args: { path?: string; offset?: number; limit?: number }, theme: ThemeLike) =>
|
|
95
|
-
new Text(readCall(theme, args ?? {}), 0, 0),
|
|
129
|
+
new Text(readCall(theme, args ?? {}, settings.summaryClip), 0, 0),
|
|
96
130
|
renderResult: (
|
|
97
131
|
result: unknown,
|
|
98
132
|
options: { expanded?: boolean; isPartial?: boolean },
|
|
@@ -124,7 +158,7 @@ export default function pretty(pi: ExtensionAPI) {
|
|
|
124
158
|
case "bash":
|
|
125
159
|
return {
|
|
126
160
|
renderCall: (args: { command?: string }, theme: ThemeLike, context: { expanded?: boolean }) =>
|
|
127
|
-
new Text(bashCall(theme, args?.command ?? "", context?.expanded === true), 0, 0),
|
|
161
|
+
new Text(bashCall(theme, args?.command ?? "", context?.expanded === true, settings.summaryClip), 0, 0),
|
|
128
162
|
renderResult: (
|
|
129
163
|
result: unknown,
|
|
130
164
|
options: { expanded?: boolean; isPartial?: boolean },
|
|
@@ -132,17 +166,17 @@ export default function pretty(pi: ExtensionAPI) {
|
|
|
132
166
|
) => {
|
|
133
167
|
const output = textContent(result);
|
|
134
168
|
if (options.isPartial) {
|
|
135
|
-
return new Text(`${theme.fg("warning", "Running…")}\n${preview(output, false)}`, 0, 0);
|
|
169
|
+
return new Text(`${theme.fg("warning", "Running…")}\n${preview(output, false, limitsFrom(settings))}`, 0, 0);
|
|
136
170
|
}
|
|
137
171
|
const failed = isFailed(result);
|
|
138
172
|
const summary = bashSummary(theme, output, failed);
|
|
139
|
-
const body = output && (options.expanded || failed) ? `\n${preview(output, options.expanded === true)}` : "";
|
|
173
|
+
const body = output && (options.expanded || failed) ? `\n${preview(output, options.expanded === true, limitsFrom(settings))}` : "";
|
|
140
174
|
return new Text(summary + body, 0, 0);
|
|
141
175
|
},
|
|
142
176
|
};
|
|
143
177
|
case "edit":
|
|
144
178
|
return {
|
|
145
|
-
renderCall: (args: { path?: string }, theme: ThemeLike) => new Text(editCall(theme, args ?? {}), 0, 0),
|
|
179
|
+
renderCall: (args: { path?: string }, theme: ThemeLike) => new Text(editCall(theme, args ?? {}, settings.summaryClip), 0, 0),
|
|
146
180
|
renderResult: (
|
|
147
181
|
result: unknown,
|
|
148
182
|
options: { expanded?: boolean; isPartial?: boolean },
|
|
@@ -158,13 +192,13 @@ export default function pretty(pi: ExtensionAPI) {
|
|
|
158
192
|
: "";
|
|
159
193
|
const stats = statsLabel(theme, diffStats(diff));
|
|
160
194
|
if (!options.expanded) return new Text(stats, 0, 0);
|
|
161
|
-
return new Text(`${stats}\n${colorizeDiff(theme, preview(diff, true))}`, 0, 0);
|
|
195
|
+
return new Text(`${stats}\n${colorizeDiff(theme, preview(diff, true, { collapsed: settings.collapsedLines, expanded: settings.diffLines }))}`, 0, 0);
|
|
162
196
|
},
|
|
163
197
|
};
|
|
164
198
|
case "write":
|
|
165
199
|
return {
|
|
166
200
|
renderCall: (args: { path?: string; content?: string }, theme: ThemeLike) =>
|
|
167
|
-
new Text(writeCall(theme, args ?? {}), 0, 0),
|
|
201
|
+
new Text(writeCall(theme, args ?? {}, settings.summaryClip), 0, 0),
|
|
168
202
|
renderResult: (
|
|
169
203
|
result: unknown,
|
|
170
204
|
options: { expanded?: boolean; isPartial?: boolean },
|
|
@@ -183,7 +217,7 @@ export default function pretty(pi: ExtensionAPI) {
|
|
|
183
217
|
renderCall: (
|
|
184
218
|
args: { pattern?: string; path?: string; glob?: string },
|
|
185
219
|
theme: ThemeLike,
|
|
186
|
-
) => new Text(searchCall(theme, tool === "grep" ? "Grep" : "Find", args ?? {}), 0, 0),
|
|
220
|
+
) => new Text(searchCall(theme, tool === "grep" ? "Grep" : "Find", args ?? {}, settings.summaryClip), 0, 0),
|
|
187
221
|
renderResult: (
|
|
188
222
|
result: unknown,
|
|
189
223
|
options: { expanded?: boolean; isPartial?: boolean },
|
|
@@ -199,12 +233,12 @@ export default function pretty(pi: ExtensionAPI) {
|
|
|
199
233
|
tool === "grep" ? { one: "match", many: "matches" } : { one: "result", many: "results" },
|
|
200
234
|
);
|
|
201
235
|
if (!options.expanded || failed || !output) return new Text(summary, 0, 0);
|
|
202
|
-
return new Text(`${summary}\n${preview(output, true)}`, 0, 0);
|
|
236
|
+
return new Text(`${summary}\n${preview(output, true, limitsFrom(settings))}`, 0, 0);
|
|
203
237
|
},
|
|
204
238
|
};
|
|
205
239
|
case "ls":
|
|
206
240
|
return {
|
|
207
|
-
renderCall: (args: { path?: string }, theme: ThemeLike) => new Text(listCall(theme, args ?? {}), 0, 0),
|
|
241
|
+
renderCall: (args: { path?: string }, theme: ThemeLike) => new Text(listCall(theme, args ?? {}, settings.summaryClip), 0, 0),
|
|
208
242
|
renderResult: (
|
|
209
243
|
result: unknown,
|
|
210
244
|
options: { expanded?: boolean; isPartial?: boolean },
|
|
@@ -215,7 +249,7 @@ export default function pretty(pi: ExtensionAPI) {
|
|
|
215
249
|
const failed = isFailed(result);
|
|
216
250
|
const summary = matchSummary(theme, output, failed, { one: "entry", many: "entries" });
|
|
217
251
|
if (!options.expanded || failed) return new Text(summary, 0, 0);
|
|
218
|
-
return new Text(`${summary}\n${preview(output, true)}`, 0, 0);
|
|
252
|
+
return new Text(`${summary}\n${preview(output, true, limitsFrom(settings))}`, 0, 0);
|
|
219
253
|
},
|
|
220
254
|
};
|
|
221
255
|
}
|
|
@@ -241,8 +275,12 @@ export default function pretty(pi: ExtensionAPI) {
|
|
|
241
275
|
|
|
242
276
|
pi.on("session_start", async (_event, ctx) => {
|
|
243
277
|
originals = buildOriginals(ctx.cwd);
|
|
278
|
+
loadSettings(ctx.cwd);
|
|
244
279
|
config = replayBranch(ctx.sessionManager.getBranch() as never);
|
|
245
280
|
applyAll();
|
|
281
|
+
if (settingsWarnings.length > 0 && ctx.hasUI) {
|
|
282
|
+
ctx.ui.notify(`pretty settings: ${settingsWarnings.join("; ")}`, "warning");
|
|
283
|
+
}
|
|
246
284
|
});
|
|
247
285
|
|
|
248
286
|
pi.on("session_tree", async (_event, ctx) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pify/pretty",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Compact, theme-aware rendering for pi's built-in tools: one-line summaries, syntax-highlighted reads, colorized diffs — behavior untouched",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
}
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
57
|
-
"@earendil-works/pi-tui": "^0.
|
|
56
|
+
"@earendil-works/pi-coding-agent": "^0.85.1",
|
|
57
|
+
"@earendil-works/pi-tui": "^0.85.1",
|
|
58
58
|
"@types/node": "^22.10.2",
|
|
59
59
|
"typescript": "^5.7.2"
|
|
60
60
|
},
|
package/src/config.ts
CHANGED
|
@@ -33,7 +33,10 @@ export function toggleTool(config: PrettyConfig, tool: PrettyTool): PrettyConfig
|
|
|
33
33
|
: { disabled: [...config.disabled, tool] };
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
export const PRETTY_USAGE =
|
|
36
|
+
export const PRETTY_USAGE = [
|
|
37
|
+
"Usage: /pretty [status | on|off [tool…] | reset | <tool…>] tools: read, bash, edit, write, grep, find, ls",
|
|
38
|
+
"Settings: .pi/pretty.json (project) or <agentDir>/pretty.json",
|
|
39
|
+
].join("\n");
|
|
37
40
|
|
|
38
41
|
/** Aliases the model or a hurried user is likely to type. */
|
|
39
42
|
const ALIASES: Record<string, PrettyTool> = {
|
package/src/preview.ts
CHANGED
|
@@ -12,6 +12,11 @@ export interface PreviewLimits {
|
|
|
12
12
|
|
|
13
13
|
export const DEFAULT_LIMITS: PreviewLimits = { collapsed: 12, expanded: 200 };
|
|
14
14
|
|
|
15
|
+
/** Limits from user settings (v0.3); the caps themselves are unchanged. */
|
|
16
|
+
export function limitsFrom(settings: { collapsedLines: number; expandedLines: number }): PreviewLimits {
|
|
17
|
+
return { collapsed: settings.collapsedLines, expanded: settings.expandedLines };
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
export function preview(text: string, expanded: boolean, limits: PreviewLimits = DEFAULT_LIMITS): string {
|
|
16
21
|
const max = expanded ? limits.expanded : limits.collapsed;
|
|
17
22
|
const lines = text.split("\n");
|
package/src/settings.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User settings for the renderers.
|
|
3
|
+
*
|
|
4
|
+
* pi-cc-extensions makes the case by example: every number in a renderer is
|
|
5
|
+
* someone's taste, and the ones that matter are the caps. A 200-line expanded
|
|
6
|
+
* body is right on a 4K monitor and absurd in a 24-row terminal, and nobody
|
|
7
|
+
* wants to fork a package to change an integer.
|
|
8
|
+
*
|
|
9
|
+
* Read from `.pi/pretty.json` in the project, else `<agentDir>/pretty.json`.
|
|
10
|
+
* Unknown keys are ignored and bad values fall back to the default, so an
|
|
11
|
+
* old or hand-edited file can never take the renderers down — the worst case
|
|
12
|
+
* is the shipped defaults, which is where everyone starts anyway.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface PrettySettings {
|
|
16
|
+
/** Lines shown in a collapsed body (bash streaming, tool output). */
|
|
17
|
+
collapsedLines: number;
|
|
18
|
+
/** Lines shown after expanding, before "+N more lines". */
|
|
19
|
+
expandedLines: number;
|
|
20
|
+
/** Lines of an edit diff shown after expanding. */
|
|
21
|
+
diffLines: number;
|
|
22
|
+
/** Highlight expanded read results with pi's own highlighter. */
|
|
23
|
+
syntaxHighlight: boolean;
|
|
24
|
+
/** Longest path/command shown in a one-line summary. */
|
|
25
|
+
summaryClip: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const DEFAULT_SETTINGS: PrettySettings = {
|
|
29
|
+
collapsedLines: 12,
|
|
30
|
+
expandedLines: 200,
|
|
31
|
+
diffLines: 200,
|
|
32
|
+
syntaxHighlight: true,
|
|
33
|
+
summaryClip: 100,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const LIMITS: Record<keyof PrettySettings, { min: number; max: number } | null> = {
|
|
37
|
+
collapsedLines: { min: 1, max: 200 },
|
|
38
|
+
expandedLines: { min: 5, max: 10_000 },
|
|
39
|
+
diffLines: { min: 5, max: 10_000 },
|
|
40
|
+
syntaxHighlight: null,
|
|
41
|
+
summaryClip: { min: 20, max: 500 },
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Merge a parsed settings file over the defaults. Returns the settings that
|
|
46
|
+
* will actually be used plus a note for every value that was rejected —
|
|
47
|
+
* silently ignoring a typo is how someone spends an afternoon wondering why
|
|
48
|
+
* their config does nothing.
|
|
49
|
+
*/
|
|
50
|
+
export function resolveSettings(raw: unknown): { settings: PrettySettings; warnings: string[] } {
|
|
51
|
+
const settings: PrettySettings = { ...DEFAULT_SETTINGS };
|
|
52
|
+
const warnings: string[] = [];
|
|
53
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
54
|
+
if (raw !== undefined && raw !== null) warnings.push("settings file is not an object — ignored");
|
|
55
|
+
return { settings, warnings };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {
|
|
59
|
+
if (!(key in DEFAULT_SETTINGS)) {
|
|
60
|
+
warnings.push(`unknown setting "${key}"`);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const name = key as keyof PrettySettings;
|
|
64
|
+
const limit = LIMITS[name];
|
|
65
|
+
if (limit === null) {
|
|
66
|
+
if (typeof value !== "boolean") {
|
|
67
|
+
warnings.push(`"${key}" must be true or false — using ${String(DEFAULT_SETTINGS[name])}`);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
(settings[name] as boolean) = value;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
74
|
+
warnings.push(`"${key}" must be a number — using ${String(DEFAULT_SETTINGS[name])}`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const clamped = Math.round(Math.min(limit.max, Math.max(limit.min, value)));
|
|
78
|
+
if (clamped !== value) {
|
|
79
|
+
warnings.push(`"${key}" clamped to ${clamped} (allowed ${limit.min}–${limit.max})`);
|
|
80
|
+
}
|
|
81
|
+
(settings[name] as number) = clamped;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return { settings, warnings };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function formatSettings(settings: PrettySettings, source: string | null): string {
|
|
88
|
+
const width = Object.keys(settings).reduce((m, k) => Math.max(m, k.length), 0);
|
|
89
|
+
const lines = Object.entries(settings).map(
|
|
90
|
+
([key, value]) => ` ${key.padEnd(width)} ${String(value)}`,
|
|
91
|
+
);
|
|
92
|
+
return [source ? `Settings from ${source}` : "Settings (defaults — no pretty.json found)", ...lines].join("\n");
|
|
93
|
+
}
|
package/src/summary.ts
CHANGED
|
@@ -10,12 +10,32 @@ function title(theme: ThemeLike, name: string): string {
|
|
|
10
10
|
return theme.fg("toolTitle", theme.bold(name)) + " ";
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
/** Default summary clip; overridable through settings (v0.3). */
|
|
14
|
+
export const DEFAULT_CLIP = 100;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Keep a one-line summary one line. A deeply nested path or a long command
|
|
18
|
+
* is clipped in the middle: the start says what it is, the end says which
|
|
19
|
+
* file, and the part nobody reads is what goes.
|
|
20
|
+
*/
|
|
21
|
+
export function clip(text: string, max: number = DEFAULT_CLIP): string {
|
|
22
|
+
if (max <= 0 || text.length <= max) return text;
|
|
23
|
+
if (max <= 4) return text.slice(0, max);
|
|
24
|
+
const head = Math.ceil((max - 1) / 2);
|
|
25
|
+
const tail = max - 1 - head;
|
|
26
|
+
return `${text.slice(0, head)}…${text.slice(text.length - tail)}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function readCall(
|
|
30
|
+
theme: ThemeLike,
|
|
31
|
+
args: { path?: string; offset?: number; limit?: number },
|
|
32
|
+
max: number = DEFAULT_CLIP,
|
|
33
|
+
): string {
|
|
14
34
|
const range =
|
|
15
35
|
args.offset || args.limit
|
|
16
36
|
? theme.fg("dim", ` · lines ${args.offset ?? 1}${args.limit ? `–${(args.offset ?? 1) + args.limit - 1}` : "+"}`)
|
|
17
37
|
: "";
|
|
18
|
-
return title(theme, "Read") + theme.fg("accent", args.path ?? "") + range;
|
|
38
|
+
return title(theme, "Read") + theme.fg("accent", clip(args.path ?? "", max)) + range;
|
|
19
39
|
}
|
|
20
40
|
|
|
21
41
|
export function readSummary(theme: ThemeLike, output: string, truncated: boolean, failed: boolean): string {
|
|
@@ -24,11 +44,19 @@ export function readSummary(theme: ThemeLike, output: string, truncated: boolean
|
|
|
24
44
|
return theme.fg("dim", `${lines} ${lines === 1 ? "line" : "lines"}${truncated ? " · truncated" : ""}`);
|
|
25
45
|
}
|
|
26
46
|
|
|
27
|
-
export function bashCall(
|
|
47
|
+
export function bashCall(
|
|
48
|
+
theme: ThemeLike,
|
|
49
|
+
command: string,
|
|
50
|
+
expanded: boolean,
|
|
51
|
+
max: number = DEFAULT_CLIP,
|
|
52
|
+
): string {
|
|
28
53
|
const lines = command.split(/\r\n|\r|\n/);
|
|
29
54
|
const head = lines[0] ?? "";
|
|
30
55
|
const omitted = lines.length - 1;
|
|
31
|
-
const shown =
|
|
56
|
+
const shown =
|
|
57
|
+
expanded || omitted === 0
|
|
58
|
+
? clip(command, expanded ? Number.MAX_SAFE_INTEGER : max)
|
|
59
|
+
: `${clip(head, max)} ${theme.fg("dim", `… (+${omitted} ${omitted === 1 ? "line" : "lines"})`)}`;
|
|
32
60
|
return title(theme, "Bash") + theme.fg("accent", shown);
|
|
33
61
|
}
|
|
34
62
|
|
|
@@ -41,27 +69,36 @@ export function bashSummary(theme: ThemeLike, output: string, failed: boolean):
|
|
|
41
69
|
return theme.fg("success", "✓") + theme.fg("dim", lines > 0 ? ` ${lines} output ${lines === 1 ? "line" : "lines"}` : " done");
|
|
42
70
|
}
|
|
43
71
|
|
|
44
|
-
export function editCall(theme: ThemeLike, args: { path?: string }): string {
|
|
45
|
-
return title(theme, "Edit") + theme.fg("accent", args.path ?? "");
|
|
72
|
+
export function editCall(theme: ThemeLike, args: { path?: string }, max: number = DEFAULT_CLIP): string {
|
|
73
|
+
return title(theme, "Edit") + theme.fg("accent", clip(args.path ?? "", max));
|
|
46
74
|
}
|
|
47
75
|
|
|
48
|
-
export function writeCall(
|
|
76
|
+
export function writeCall(
|
|
77
|
+
theme: ThemeLike,
|
|
78
|
+
args: { path?: string; content?: string },
|
|
79
|
+
max: number = DEFAULT_CLIP,
|
|
80
|
+
): string {
|
|
49
81
|
const lines = typeof args.content === "string" ? args.content.split("\n").length : 0;
|
|
50
|
-
return
|
|
82
|
+
return (
|
|
83
|
+
title(theme, "Write") +
|
|
84
|
+
theme.fg("accent", clip(args.path ?? "", max)) +
|
|
85
|
+
theme.fg("dim", ` · ${lines} ${lines === 1 ? "line" : "lines"}`)
|
|
86
|
+
);
|
|
51
87
|
}
|
|
52
88
|
|
|
53
89
|
export function searchCall(
|
|
54
90
|
theme: ThemeLike,
|
|
55
91
|
name: string,
|
|
56
92
|
args: { pattern?: string; path?: string; glob?: string },
|
|
93
|
+
max: number = DEFAULT_CLIP,
|
|
57
94
|
): string {
|
|
58
|
-
const pattern = args.pattern ?? args.glob ?? "";
|
|
59
|
-
const where = args.path ? theme.fg("dim", ` in ${args.path}`) : "";
|
|
95
|
+
const pattern = clip(args.pattern ?? args.glob ?? "", max);
|
|
96
|
+
const where = args.path ? theme.fg("dim", ` in ${clip(args.path, max)}`) : "";
|
|
60
97
|
return title(theme, name) + theme.fg("accent", pattern) + where;
|
|
61
98
|
}
|
|
62
99
|
|
|
63
|
-
export function listCall(theme: ThemeLike, args: { path?: string }): string {
|
|
64
|
-
return title(theme, "List") + theme.fg("accent", args.path ?? ".");
|
|
100
|
+
export function listCall(theme: ThemeLike, args: { path?: string }, max: number = DEFAULT_CLIP): string {
|
|
101
|
+
return title(theme, "List") + theme.fg("accent", clip(args.path ?? ".", max));
|
|
65
102
|
}
|
|
66
103
|
|
|
67
104
|
export function matchSummary(
|