@xynogen/pix-pretty 1.7.14 → 1.7.16
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/ansi.ts +4 -12
- package/src/commands/pretty.ts +2 -5
- package/src/config.ts +7 -31
- package/src/confirm.ts +6 -24
- package/src/diff-render.ts +42 -174
- package/src/diff.ts +1 -2
- package/src/fff.ts +3 -9
- package/src/gate-overlay.test.ts +2 -6
- package/src/gate-overlay.ts +5 -26
- package/src/highlight.ts +1 -4
- package/src/icon-catalog.ts +3 -0
- package/src/icon-persist.ts +1 -4
- package/src/modal-frame.ts +2 -6
- package/src/progress.ts +1 -5
- package/src/renderers.ts +2 -13
- package/src/types-diff.d.ts +1 -5
- package/src/types.ts +3 -12
- package/src/utils.test.ts +3 -11
- package/src/utils.ts +6 -23
package/package.json
CHANGED
package/src/ansi.ts
CHANGED
|
@@ -17,12 +17,8 @@ export let BG_BASE = BG_DEFAULT; // tool box success/base bg — updated from th
|
|
|
17
17
|
export let BG_ERROR = BG_DEFAULT; // tool box error bg — updated from theme's toolErrorBg
|
|
18
18
|
|
|
19
19
|
/** Parse an ANSI 24-bit color escape into { r, g, b }. Handles both fg (38;2) and bg (48;2). */
|
|
20
|
-
function parseAnsiRgb(
|
|
21
|
-
ansi
|
|
22
|
-
): { r: number; g: number; b: number } | null {
|
|
23
|
-
const m = ansi.match(
|
|
24
|
-
new RegExp(`${ESC_RE}\\[(?:38|48);2;(\\d+);(\\d+);(\\d+)m`),
|
|
25
|
-
);
|
|
20
|
+
function parseAnsiRgb(ansi: string): { r: number; g: number; b: number } | null {
|
|
21
|
+
const m = ansi.match(new RegExp(`${ESC_RE}\\[(?:38|48);2;(\\d+);(\\d+);(\\d+)m`));
|
|
26
22
|
return m ? { r: +(m[1] ?? 0), g: +(m[2] ?? 0), b: +(m[3] ?? 0) } : null;
|
|
27
23
|
}
|
|
28
24
|
|
|
@@ -40,10 +36,7 @@ function getThemeBgAnsi(theme: BgTheme, key: string): string | null {
|
|
|
40
36
|
export function resolveBaseBackground(theme: BgTheme | null | undefined): void {
|
|
41
37
|
if (!theme?.getBgAnsi) return;
|
|
42
38
|
|
|
43
|
-
BG_BASE =
|
|
44
|
-
getThemeBgAnsi(theme, "toolBg") ??
|
|
45
|
-
getThemeBgAnsi(theme, "background") ??
|
|
46
|
-
BG_DEFAULT;
|
|
39
|
+
BG_BASE = getThemeBgAnsi(theme, "toolBg") ?? getThemeBgAnsi(theme, "background") ?? BG_DEFAULT;
|
|
47
40
|
BG_ERROR = getThemeBgAnsi(theme, "toolErrorBg") ?? BG_BASE;
|
|
48
41
|
RST = `\x1b[0m${BG_BASE}`;
|
|
49
42
|
}
|
|
@@ -61,8 +54,7 @@ function isLowContrastShikiFg(params: string): boolean {
|
|
|
61
54
|
if (params === "38;5;0" || params === "38;5;8") return true;
|
|
62
55
|
if (!params.startsWith("38;2;")) return false;
|
|
63
56
|
const parts = params.split(";").map(Number);
|
|
64
|
-
if (parts.length !== 5 || parts.some((n) => !Number.isFinite(n)))
|
|
65
|
-
return false;
|
|
57
|
+
if (parts.length !== 5 || parts.some((n) => !Number.isFinite(n))) return false;
|
|
66
58
|
const [, , r = 0, g = 0, b = 0] = parts;
|
|
67
59
|
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
68
60
|
return luminance < 72;
|
package/src/commands/pretty.ts
CHANGED
|
@@ -93,9 +93,7 @@ export function registerPrettyCommand(pi: PiPrettyApi): void {
|
|
|
93
93
|
const name = theme.fg(sel ? "accent" : "text", mode.padEnd(8));
|
|
94
94
|
// Live preview: render the sample glyphs in THIS mode.
|
|
95
95
|
const prev = ICON_MODES[i] === getIconMode();
|
|
96
|
-
const samples = prev
|
|
97
|
-
? PREVIEW.map((p) => icon(p.key)).join(" ")
|
|
98
|
-
: "";
|
|
96
|
+
const samples = prev ? PREVIEW.map((p) => icon(p.key)).join(" ") : "";
|
|
99
97
|
return `${cursor} ${name} ${theme.fg("dim", samples)}`;
|
|
100
98
|
});
|
|
101
99
|
const lines = [
|
|
@@ -115,8 +113,7 @@ export function registerPrettyCommand(pi: PiPrettyApi): void {
|
|
|
115
113
|
invalidate: () => {},
|
|
116
114
|
handleInput: (data: string) => {
|
|
117
115
|
if (data === "k" || data === "\u001b[A") choose(selected - 1);
|
|
118
|
-
else if (data === "j" || data === "\u001b[B")
|
|
119
|
-
choose(selected + 1);
|
|
116
|
+
else if (data === "j" || data === "\u001b[B") choose(selected + 1);
|
|
120
117
|
else if (data === "\u001b" || data === "q" || data === "\r") {
|
|
121
118
|
done(null);
|
|
122
119
|
return;
|
package/src/config.ts
CHANGED
|
@@ -16,14 +16,10 @@ function readThemeFromSettings(agentDir?: string): BundledTheme | undefined {
|
|
|
16
16
|
if (!resolvedAgentDir) return undefined;
|
|
17
17
|
|
|
18
18
|
try {
|
|
19
|
-
const settings = JSON.parse(
|
|
20
|
-
readFileSync(join(resolvedAgentDir, "settings.json"), "utf8"),
|
|
21
|
-
) as {
|
|
19
|
+
const settings = JSON.parse(readFileSync(join(resolvedAgentDir, "settings.json"), "utf8")) as {
|
|
22
20
|
theme?: unknown;
|
|
23
21
|
};
|
|
24
|
-
return typeof settings.theme === "string"
|
|
25
|
-
? (settings.theme as BundledTheme)
|
|
26
|
-
: undefined;
|
|
22
|
+
return typeof settings.theme === "string" ? (settings.theme as BundledTheme) : undefined;
|
|
27
23
|
} catch {
|
|
28
24
|
return undefined;
|
|
29
25
|
}
|
|
@@ -53,11 +49,7 @@ export function envInt(name: string, fallback: number): number {
|
|
|
53
49
|
}
|
|
54
50
|
|
|
55
51
|
// Precedence for numeric config: env var → pix.json → hardcoded default
|
|
56
|
-
function pixOrEnvInt(
|
|
57
|
-
envName: string,
|
|
58
|
-
pixValue: number,
|
|
59
|
-
fallback: number,
|
|
60
|
-
): number {
|
|
52
|
+
function pixOrEnvInt(envName: string, pixValue: number, fallback: number): number {
|
|
61
53
|
const env = process.env[envName];
|
|
62
54
|
if (env) {
|
|
63
55
|
const v = Number.parseInt(env, 10);
|
|
@@ -68,30 +60,14 @@ function pixOrEnvInt(
|
|
|
68
60
|
|
|
69
61
|
const pc = pixConfig().pretty;
|
|
70
62
|
|
|
71
|
-
export const MAX_HL_CHARS = pixOrEnvInt(
|
|
72
|
-
"PRETTY_MAX_HL_CHARS",
|
|
73
|
-
pc.maxHighlightChars,
|
|
74
|
-
80_000,
|
|
75
|
-
);
|
|
63
|
+
export const MAX_HL_CHARS = pixOrEnvInt("PRETTY_MAX_HL_CHARS", pc.maxHighlightChars, 80_000);
|
|
76
64
|
|
|
77
|
-
export const MAX_PREVIEW_LINES = pixOrEnvInt(
|
|
78
|
-
"PRETTY_MAX_PREVIEW_LINES",
|
|
79
|
-
pc.maxPreviewLines,
|
|
80
|
-
80,
|
|
81
|
-
);
|
|
65
|
+
export const MAX_PREVIEW_LINES = pixOrEnvInt("PRETTY_MAX_PREVIEW_LINES", pc.maxPreviewLines, 80);
|
|
82
66
|
|
|
83
|
-
export const CACHE_LIMIT = pixOrEnvInt(
|
|
84
|
-
"PRETTY_CACHE_LIMIT",
|
|
85
|
-
pc.cacheLimit,
|
|
86
|
-
128,
|
|
87
|
-
);
|
|
67
|
+
export const CACHE_LIMIT = pixOrEnvInt("PRETTY_CACHE_LIMIT", pc.cacheLimit, 128);
|
|
88
68
|
|
|
89
69
|
// --- Diff rendering limits (edit/write tools) ---
|
|
90
|
-
export const MAX_RENDER_LINES = pixOrEnvInt(
|
|
91
|
-
"PRETTY_MAX_RENDER_LINES",
|
|
92
|
-
pc.maxRenderLines,
|
|
93
|
-
150,
|
|
94
|
-
);
|
|
70
|
+
export const MAX_RENDER_LINES = pixOrEnvInt("PRETTY_MAX_RENDER_LINES", pc.maxRenderLines, 150);
|
|
95
71
|
|
|
96
72
|
// Word-level emphasis only when paired del/add lines are at least this similar.
|
|
97
73
|
export const WORD_DIFF_MIN_SIM = 0.15;
|
package/src/confirm.ts
CHANGED
|
@@ -55,19 +55,13 @@ const COUNTDOWN_WARN_S = 5;
|
|
|
55
55
|
/**
|
|
56
56
|
* Show a Yes/No overlay. Resolves true on confirm, false otherwise.
|
|
57
57
|
*/
|
|
58
|
-
export function confirmOverlay(
|
|
59
|
-
ui: ConfirmUI,
|
|
60
|
-
opts: ConfirmOptions,
|
|
61
|
-
): Promise<boolean> {
|
|
58
|
+
export function confirmOverlay(ui: ConfirmUI, opts: ConfirmOptions): Promise<boolean> {
|
|
62
59
|
const accent = opts.accent ?? "accent";
|
|
63
60
|
const timeoutMs = opts.timeoutMs ?? 0;
|
|
64
61
|
|
|
65
62
|
return new Promise((resolve) => {
|
|
66
63
|
const controller = new AbortController();
|
|
67
|
-
const timer =
|
|
68
|
-
timeoutMs > 0
|
|
69
|
-
? setTimeout(() => controller.abort(), timeoutMs)
|
|
70
|
-
: undefined;
|
|
64
|
+
const timer = timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : undefined;
|
|
71
65
|
|
|
72
66
|
ui.custom<boolean>(
|
|
73
67
|
(tui, theme, _kb, done) => {
|
|
@@ -87,25 +81,15 @@ export function confirmOverlay(
|
|
|
87
81
|
},
|
|
88
82
|
];
|
|
89
83
|
|
|
90
|
-
const selectList = new SelectList(
|
|
91
|
-
choices,
|
|
92
|
-
choices.length,
|
|
93
|
-
selectListTheme(theme, accent),
|
|
94
|
-
);
|
|
84
|
+
const selectList = new SelectList(choices, choices.length, selectListTheme(theme, accent));
|
|
95
85
|
|
|
96
86
|
if (timeoutMs > 0) {
|
|
97
87
|
const deadlineMs = Date.now() + timeoutMs;
|
|
98
88
|
const updateCountdown = () => {
|
|
99
|
-
const remaining = Math.max(
|
|
100
|
-
0,
|
|
101
|
-
Math.ceil((deadlineMs - Date.now()) / SECOND_MS),
|
|
102
|
-
);
|
|
89
|
+
const remaining = Math.max(0, Math.ceil((deadlineMs - Date.now()) / SECOND_MS));
|
|
103
90
|
countdownLine =
|
|
104
91
|
theme.fg("dim", "Auto-cancel in ") +
|
|
105
|
-
theme.fg(
|
|
106
|
-
remaining <= COUNTDOWN_WARN_S ? accent : "muted",
|
|
107
|
-
`${remaining}s`,
|
|
108
|
-
);
|
|
92
|
+
theme.fg(remaining <= COUNTDOWN_WARN_S ? accent : "muted", `${remaining}s`);
|
|
109
93
|
};
|
|
110
94
|
updateCountdown();
|
|
111
95
|
ticker = setInterval(() => {
|
|
@@ -148,9 +132,7 @@ export function confirmOverlay(
|
|
|
148
132
|
for (const l of selectList.render(inner)) lines.push(l);
|
|
149
133
|
|
|
150
134
|
lines.push("");
|
|
151
|
-
lines.push(
|
|
152
|
-
theme.fg("dim", "↑↓ navigate • enter select • esc cancel"),
|
|
153
|
-
);
|
|
135
|
+
lines.push(theme.fg("dim", "↑↓ navigate • enter select • esc cancel"));
|
|
154
136
|
|
|
155
137
|
return frameLines({
|
|
156
138
|
width: mw,
|
package/src/diff-render.ts
CHANGED
|
@@ -72,31 +72,13 @@ const dc = pixConfig().pretty.diff;
|
|
|
72
72
|
// Precedence: env → pix.json → hardcoded default
|
|
73
73
|
const BG_ADD = envBg("DIFF_BG_ADD", hexToBg(dc.bgAdd) || "\x1b[48;2;22;38;32m");
|
|
74
74
|
const BG_DEL = envBg("DIFF_BG_DEL", hexToBg(dc.bgDel) || "\x1b[48;2;45;25;25m");
|
|
75
|
-
const BG_ADD_W = envBg(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
);
|
|
83
|
-
const BG_GUTTER_ADD = envBg(
|
|
84
|
-
"DIFF_BG_GUTTER_ADD",
|
|
85
|
-
hexToBg(dc.bgGutterAdd) || "\x1b[48;2;18;32;26m",
|
|
86
|
-
);
|
|
87
|
-
const BG_GUTTER_DEL = envBg(
|
|
88
|
-
"DIFF_BG_GUTTER_DEL",
|
|
89
|
-
hexToBg(dc.bgGutterDel) || "\x1b[48;2;38;22;22m",
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
const FG_ADD = envFg(
|
|
93
|
-
"DIFF_FG_ADD",
|
|
94
|
-
hexToFg(dc.fgAdd) || "\x1b[38;2;100;180;120m",
|
|
95
|
-
);
|
|
96
|
-
const FG_DEL = envFg(
|
|
97
|
-
"DIFF_FG_DEL",
|
|
98
|
-
hexToFg(dc.fgDel) || "\x1b[38;2;200;100;100m",
|
|
99
|
-
);
|
|
75
|
+
const BG_ADD_W = envBg("DIFF_BG_ADD_HL", hexToBg(dc.bgAddHighlight) || "\x1b[48;2;35;75;50m");
|
|
76
|
+
const BG_DEL_W = envBg("DIFF_BG_DEL_HL", hexToBg(dc.bgDelHighlight) || "\x1b[48;2;80;35;35m");
|
|
77
|
+
const BG_GUTTER_ADD = envBg("DIFF_BG_GUTTER_ADD", hexToBg(dc.bgGutterAdd) || "\x1b[48;2;18;32;26m");
|
|
78
|
+
const BG_GUTTER_DEL = envBg("DIFF_BG_GUTTER_DEL", hexToBg(dc.bgGutterDel) || "\x1b[48;2;38;22;22m");
|
|
79
|
+
|
|
80
|
+
const FG_ADD = envFg("DIFF_FG_ADD", hexToFg(dc.fgAdd) || "\x1b[38;2;100;180;120m");
|
|
81
|
+
const FG_DEL = envFg("DIFF_FG_DEL", hexToFg(dc.fgDel) || "\x1b[38;2;200;100;100m");
|
|
100
82
|
const FG_STRIPE = "\x1b[38;2;40;40;40m"; // diagonal stripes on filler cells
|
|
101
83
|
|
|
102
84
|
const BORDER_BAR = "▌";
|
|
@@ -115,10 +97,7 @@ const MAX_TERM_WIDTH = 210;
|
|
|
115
97
|
const MAX_PREVIEW_LINES = envInt("PRETTY_MAX_PREVIEW_LINES", 80);
|
|
116
98
|
|
|
117
99
|
const SPLIT_MIN_WIDTH = envInt("DIFF_SPLIT_MIN_WIDTH", dc.splitMinWidth || 150);
|
|
118
|
-
const SPLIT_MIN_CODE_WIDTH = envInt(
|
|
119
|
-
"DIFF_SPLIT_MIN_CODE_WIDTH",
|
|
120
|
-
dc.splitMinCodeWidth || 60,
|
|
121
|
-
);
|
|
100
|
+
const SPLIT_MIN_CODE_WIDTH = envInt("DIFF_SPLIT_MIN_CODE_WIDTH", dc.splitMinCodeWidth || 60);
|
|
122
101
|
const SPLIT_MAX_WRAP_RATIO = 0.2;
|
|
123
102
|
const SPLIT_MAX_WRAP_LINES = 8;
|
|
124
103
|
|
|
@@ -192,20 +171,12 @@ function ensureContrast(fg: string, bgSeq: string, min = 3): string {
|
|
|
192
171
|
*
|
|
193
172
|
* Theme hue is preserved, but each add/del fg is contrast-checked against the
|
|
194
173
|
* gutter bg it is painted on and lifted if it would render too dark to read. */
|
|
195
|
-
export function resolveDiffColors(theme?: {
|
|
196
|
-
getFgAnsi?: (key: string) => string;
|
|
197
|
-
}): DiffColors {
|
|
174
|
+
export function resolveDiffColors(theme?: { getFgAnsi?: (key: string) => string }): DiffColors {
|
|
198
175
|
if (!theme?.getFgAnsi) return DEFAULT_DIFF_COLORS;
|
|
199
176
|
try {
|
|
200
177
|
return {
|
|
201
|
-
fgAdd: ensureContrast(
|
|
202
|
-
|
|
203
|
-
BG_GUTTER_ADD,
|
|
204
|
-
),
|
|
205
|
-
fgDel: ensureContrast(
|
|
206
|
-
theme.getFgAnsi("toolDiffRemoved") || FG_DEL,
|
|
207
|
-
BG_GUTTER_DEL,
|
|
208
|
-
),
|
|
178
|
+
fgAdd: ensureContrast(theme.getFgAnsi("toolDiffAdded") || FG_ADD, BG_GUTTER_ADD),
|
|
179
|
+
fgDel: ensureContrast(theme.getFgAnsi("toolDiffRemoved") || FG_DEL, BG_GUTTER_DEL),
|
|
209
180
|
fgCtx: theme.getFgAnsi("toolDiffContext") || FG_DIM,
|
|
210
181
|
};
|
|
211
182
|
} catch {
|
|
@@ -214,9 +185,7 @@ export function resolveDiffColors(theme?: {
|
|
|
214
185
|
}
|
|
215
186
|
|
|
216
187
|
/** Stable cache key for the resolved diff theme colors. */
|
|
217
|
-
export function diffThemeCacheKey(theme?: {
|
|
218
|
-
getFgAnsi?: (key: string) => string;
|
|
219
|
-
}): string {
|
|
188
|
+
export function diffThemeCacheKey(theme?: { getFgAnsi?: (key: string) => string }): string {
|
|
220
189
|
const c = resolveDiffColors(theme);
|
|
221
190
|
return `${c.fgAdd}|${c.fgDel}|${c.fgCtx}|${BG_BASE}`;
|
|
222
191
|
}
|
|
@@ -266,9 +235,7 @@ function fit(s: string, w: number): string {
|
|
|
266
235
|
vis++;
|
|
267
236
|
i++;
|
|
268
237
|
}
|
|
269
|
-
return w > 2
|
|
270
|
-
? `${s.slice(0, i)}${RST}${FG_DIM}›${RST}`
|
|
271
|
-
: `${s.slice(0, i)}${RST}`;
|
|
238
|
+
return w > 2 ? `${s.slice(0, i)}${RST}${FG_DIM}›${RST}` : `${s.slice(0, i)}${RST}`;
|
|
272
239
|
}
|
|
273
240
|
|
|
274
241
|
/** Extract last active fg + bg ANSI codes from a string (for wrap continuations). */
|
|
@@ -293,12 +260,7 @@ function ansiState(s: string): string {
|
|
|
293
260
|
}
|
|
294
261
|
|
|
295
262
|
/** Wrap ANSI-encoded string into rows of `w` visible chars. */
|
|
296
|
-
function wrapAnsi(
|
|
297
|
-
s: string,
|
|
298
|
-
w: number,
|
|
299
|
-
maxRows = adaptiveWrapRows(),
|
|
300
|
-
fillBg = "",
|
|
301
|
-
): string[] {
|
|
263
|
+
function wrapAnsi(s: string, w: number, maxRows = adaptiveWrapRows(), fillBg = ""): string[] {
|
|
302
264
|
if (w <= 0) return [""];
|
|
303
265
|
const plain = strip(s);
|
|
304
266
|
if (plain.length <= w) {
|
|
@@ -375,12 +337,7 @@ function stripes(w: number, _rowOffset: number): string {
|
|
|
375
337
|
|
|
376
338
|
/** Right-aligned line number. `noReset` keeps the active bg alive (no
|
|
377
339
|
* trailing RST) so the caller can build one bg-continuous gutter segment. */
|
|
378
|
-
function lnum(
|
|
379
|
-
n: number | null,
|
|
380
|
-
w: number,
|
|
381
|
-
fg = FG_LNUM,
|
|
382
|
-
noReset = false,
|
|
383
|
-
): string {
|
|
340
|
+
function lnum(n: number | null, w: number, fg = FG_LNUM, noReset = false): string {
|
|
384
341
|
if (n === null) return " ".repeat(w);
|
|
385
342
|
const v = String(n);
|
|
386
343
|
const pad = " ".repeat(Math.max(0, w - v.length));
|
|
@@ -402,20 +359,8 @@ function buildGutter(opts: {
|
|
|
402
359
|
nw: number;
|
|
403
360
|
continuation: boolean;
|
|
404
361
|
}): string {
|
|
405
|
-
const {
|
|
406
|
-
|
|
407
|
-
gutterBg,
|
|
408
|
-
bodyBg,
|
|
409
|
-
num,
|
|
410
|
-
numFg,
|
|
411
|
-
signFg,
|
|
412
|
-
sign,
|
|
413
|
-
nw,
|
|
414
|
-
continuation,
|
|
415
|
-
} = opts;
|
|
416
|
-
const border = borderFg
|
|
417
|
-
? `${gutterBg}${borderFg}${BORDER_BAR}`
|
|
418
|
-
: `${BG_BASE} `;
|
|
362
|
+
const { borderFg, gutterBg, bodyBg, num, numFg, signFg, sign, nw, continuation } = opts;
|
|
363
|
+
const border = borderFg ? `${gutterBg}${borderFg}${BORDER_BAR}` : `${BG_BASE} `;
|
|
419
364
|
const numCell = continuation
|
|
420
365
|
? " ".repeat(nw + 2)
|
|
421
366
|
: `${lnum(num, nw, numFg, true)}${signFg}${sign} `;
|
|
@@ -505,8 +450,7 @@ function injectBg(
|
|
|
505
450
|
continue;
|
|
506
451
|
}
|
|
507
452
|
}
|
|
508
|
-
while (ri < ranges.length && vis >= (ranges[ri] as [number, number])[1])
|
|
509
|
-
ri++;
|
|
453
|
+
while (ri < ranges.length && vis >= (ranges[ri] as [number, number])[1]) ri++;
|
|
510
454
|
const want =
|
|
511
455
|
ri < ranges.length &&
|
|
512
456
|
vis >= (ranges[ri] as [number, number])[0] &&
|
|
@@ -523,10 +467,7 @@ function injectBg(
|
|
|
523
467
|
}
|
|
524
468
|
|
|
525
469
|
/** Simple word diff (no syntax hl) — fallback when highlighting is unavailable. */
|
|
526
|
-
function plainWordDiff(
|
|
527
|
-
oldText: string,
|
|
528
|
-
newText: string,
|
|
529
|
-
): { old: string; new: string } {
|
|
470
|
+
function plainWordDiff(oldText: string, newText: string): { old: string; new: string } {
|
|
530
471
|
const parts = Diff.diffWords(oldText, newText);
|
|
531
472
|
let o = "";
|
|
532
473
|
let n = "";
|
|
@@ -551,18 +492,13 @@ function at<T>(arr: T[], i: number): T {
|
|
|
551
492
|
// Split-vs-unified decision
|
|
552
493
|
// ---------------------------------------------------------------------------
|
|
553
494
|
|
|
554
|
-
function shouldUseSplit(
|
|
555
|
-
diff: ParsedDiff,
|
|
556
|
-
tw: number,
|
|
557
|
-
maxRows = MAX_PREVIEW_LINES,
|
|
558
|
-
): boolean {
|
|
495
|
+
function shouldUseSplit(diff: ParsedDiff, tw: number, maxRows = MAX_PREVIEW_LINES): boolean {
|
|
559
496
|
if (!diff.lines.length) return false;
|
|
560
497
|
if (tw < SPLIT_MIN_WIDTH) return false;
|
|
561
498
|
|
|
562
499
|
const nw = Math.max(
|
|
563
500
|
2,
|
|
564
|
-
String(Math.max(...diff.lines.map((l) => l.oldNum ?? l.newNum ?? 0), 0))
|
|
565
|
-
.length,
|
|
501
|
+
String(Math.max(...diff.lines.map((l) => l.oldNum ?? l.newNum ?? 0), 0)).length,
|
|
566
502
|
);
|
|
567
503
|
const half = Math.floor((tw - 1) / 2);
|
|
568
504
|
const gw = nw + 5;
|
|
@@ -599,10 +535,7 @@ export async function renderUnified(
|
|
|
599
535
|
|
|
600
536
|
const vis = diff.lines.slice(0, max);
|
|
601
537
|
const tw = termW();
|
|
602
|
-
const nw = Math.max(
|
|
603
|
-
2,
|
|
604
|
-
String(Math.max(...vis.map((l) => l.oldNum ?? l.newNum ?? 0), 0)).length,
|
|
605
|
-
);
|
|
538
|
+
const nw = Math.max(2, String(Math.max(...vis.map((l) => l.oldNum ?? l.newNum ?? 0), 0)).length);
|
|
606
539
|
const gw = nw + 5;
|
|
607
540
|
const cw = Math.max(20, tw - gw);
|
|
608
541
|
const canHL = diff.chars <= MAX_HL_CHARS && vis.length <= MAX_RENDER_LINES;
|
|
@@ -650,8 +583,7 @@ export async function renderUnified(
|
|
|
650
583
|
const contGutter = buildGutter({ ...gutterArgs, continuation: true });
|
|
651
584
|
const rows = wrapAnsi(tabs(body), cw, adaptiveWrapRows(), bodyBg);
|
|
652
585
|
out.push(`${gutter}${rows[0]}${RST}`);
|
|
653
|
-
for (let r = 1; r < rows.length; r++)
|
|
654
|
-
out.push(`${contGutter}${rows[r]}${RST}`);
|
|
586
|
+
for (let r = 1; r < rows.length; r++) out.push(`${contGutter}${rows[r]}${RST}`);
|
|
655
587
|
}
|
|
656
588
|
|
|
657
589
|
while (idx < vis.length) {
|
|
@@ -664,23 +596,14 @@ export async function renderUnified(
|
|
|
664
596
|
const pad = Math.max(0, totalW - label.length - 2);
|
|
665
597
|
const half1 = Math.floor(pad / 2);
|
|
666
598
|
const half2 = pad - half1;
|
|
667
|
-
out.push(
|
|
668
|
-
`${BG_BASE}${FG_DIM}${"─".repeat(half1)}${label}${"─".repeat(half2)}${RST}`,
|
|
669
|
-
);
|
|
599
|
+
out.push(`${BG_BASE}${FG_DIM}${"─".repeat(half1)}${label}${"─".repeat(half2)}${RST}`);
|
|
670
600
|
idx++;
|
|
671
601
|
continue;
|
|
672
602
|
}
|
|
673
603
|
|
|
674
604
|
if (l.type === "ctx") {
|
|
675
605
|
const hl = oldHL[oI] ?? l.content;
|
|
676
|
-
emitRow(
|
|
677
|
-
l.newNum,
|
|
678
|
-
" ",
|
|
679
|
-
BG_BASE,
|
|
680
|
-
dc.fgCtx,
|
|
681
|
-
`${BG_BASE}${DIM}${hl}`,
|
|
682
|
-
BG_BASE,
|
|
683
|
-
);
|
|
606
|
+
emitRow(l.newNum, " ", BG_BASE, dc.fgCtx, `${BG_BASE}${DIM}${hl}`, BG_BASE);
|
|
684
607
|
oI++;
|
|
685
608
|
nI++;
|
|
686
609
|
idx++;
|
|
@@ -705,9 +628,7 @@ export async function renderUnified(
|
|
|
705
628
|
}
|
|
706
629
|
|
|
707
630
|
const isPaired = dels.length === 1 && adds.length === 1;
|
|
708
|
-
const wd = isPaired
|
|
709
|
-
? wordDiffAnalysis(at(dels, 0).l.content, at(adds, 0).l.content)
|
|
710
|
-
: null;
|
|
631
|
+
const wd = isPaired ? wordDiffAnalysis(at(dels, 0).l.content, at(adds, 0).l.content) : null;
|
|
711
632
|
const wdBalanced = wd && wd.oldRanges.length > 0 && wd.newRanges.length > 0;
|
|
712
633
|
|
|
713
634
|
if (isPaired && wdBalanced && wd.similarity >= WORD_DIFF_MIN_SIM && canHL) {
|
|
@@ -715,30 +636,11 @@ export async function renderUnified(
|
|
|
715
636
|
const add0 = at(adds, 0);
|
|
716
637
|
const delBody = injectBg(del0.hl, wd.oldRanges, BG_DEL, BG_DEL_W);
|
|
717
638
|
const addBody = injectBg(add0.hl, wd.newRanges, BG_ADD, BG_ADD_W);
|
|
718
|
-
emitRow(
|
|
719
|
-
|
|
720
|
-
"-",
|
|
721
|
-
BG_GUTTER_DEL,
|
|
722
|
-
`${dc.fgDel}${BOLD}`,
|
|
723
|
-
delBody,
|
|
724
|
-
BG_DEL,
|
|
725
|
-
);
|
|
726
|
-
emitRow(
|
|
727
|
-
add0.l.newNum,
|
|
728
|
-
"+",
|
|
729
|
-
BG_GUTTER_ADD,
|
|
730
|
-
`${dc.fgAdd}${BOLD}`,
|
|
731
|
-
addBody,
|
|
732
|
-
BG_ADD,
|
|
733
|
-
);
|
|
639
|
+
emitRow(del0.l.oldNum, "-", BG_GUTTER_DEL, `${dc.fgDel}${BOLD}`, delBody, BG_DEL);
|
|
640
|
+
emitRow(add0.l.newNum, "+", BG_GUTTER_ADD, `${dc.fgAdd}${BOLD}`, addBody, BG_ADD);
|
|
734
641
|
continue;
|
|
735
642
|
}
|
|
736
|
-
if (
|
|
737
|
-
isPaired &&
|
|
738
|
-
wdBalanced &&
|
|
739
|
-
wd.similarity >= WORD_DIFF_MIN_SIM &&
|
|
740
|
-
!canHL
|
|
741
|
-
) {
|
|
643
|
+
if (isPaired && wdBalanced && wd.similarity >= WORD_DIFF_MIN_SIM && !canHL) {
|
|
742
644
|
const del0 = at(dels, 0);
|
|
743
645
|
const add0 = at(adds, 0);
|
|
744
646
|
const pwd = plainWordDiff(del0.l.content, add0.l.content);
|
|
@@ -763,33 +665,17 @@ export async function renderUnified(
|
|
|
763
665
|
|
|
764
666
|
for (const d of dels) {
|
|
765
667
|
const body = canHL ? `${BG_DEL}${d.hl}` : `${BG_DEL}${d.l.content}`;
|
|
766
|
-
emitRow(
|
|
767
|
-
d.l.oldNum,
|
|
768
|
-
"-",
|
|
769
|
-
BG_GUTTER_DEL,
|
|
770
|
-
`${dc.fgDel}${BOLD}`,
|
|
771
|
-
body,
|
|
772
|
-
BG_DEL,
|
|
773
|
-
);
|
|
668
|
+
emitRow(d.l.oldNum, "-", BG_GUTTER_DEL, `${dc.fgDel}${BOLD}`, body, BG_DEL);
|
|
774
669
|
}
|
|
775
670
|
for (const a of adds) {
|
|
776
671
|
const body = canHL ? `${BG_ADD}${a.hl}` : `${BG_ADD}${a.l.content}`;
|
|
777
|
-
emitRow(
|
|
778
|
-
a.l.newNum,
|
|
779
|
-
"+",
|
|
780
|
-
BG_GUTTER_ADD,
|
|
781
|
-
`${dc.fgAdd}${BOLD}`,
|
|
782
|
-
body,
|
|
783
|
-
BG_ADD,
|
|
784
|
-
);
|
|
672
|
+
emitRow(a.l.newNum, "+", BG_GUTTER_ADD, `${dc.fgAdd}${BOLD}`, body, BG_ADD);
|
|
785
673
|
}
|
|
786
674
|
}
|
|
787
675
|
|
|
788
676
|
out.push(rule(tw));
|
|
789
677
|
if (diff.lines.length > vis.length) {
|
|
790
|
-
out.push(
|
|
791
|
-
`${BG_BASE}${FG_DIM} … ${diff.lines.length - vis.length} more lines${RST}`,
|
|
792
|
-
);
|
|
678
|
+
out.push(`${BG_BASE}${FG_DIM} … ${diff.lines.length - vis.length} more lines${RST}`);
|
|
793
679
|
}
|
|
794
680
|
return out.join("\n");
|
|
795
681
|
}
|
|
@@ -805,8 +691,7 @@ export async function renderSplit(
|
|
|
805
691
|
dc: DiffColors = DEFAULT_DIFF_COLORS,
|
|
806
692
|
): Promise<string> {
|
|
807
693
|
const tw = termW();
|
|
808
|
-
if (!shouldUseSplit(diff, tw, max))
|
|
809
|
-
return renderUnified(diff, language, max, dc);
|
|
694
|
+
if (!shouldUseSplit(diff, tw, max)) return renderUnified(diff, language, max, dc);
|
|
810
695
|
if (!diff.lines.length) return "";
|
|
811
696
|
|
|
812
697
|
type Row = { left: DiffLine | null; right: DiffLine | null };
|
|
@@ -834,21 +719,18 @@ export async function renderSplit(
|
|
|
834
719
|
i++;
|
|
835
720
|
}
|
|
836
721
|
const n = Math.max(dels.length, adds.length);
|
|
837
|
-
for (let j = 0; j < n; j++)
|
|
838
|
-
rows.push({ left: dels[j] ?? null, right: adds[j] ?? null });
|
|
722
|
+
for (let j = 0; j < n; j++) rows.push({ left: dels[j] ?? null, right: adds[j] ?? null });
|
|
839
723
|
}
|
|
840
724
|
|
|
841
725
|
const vis = rows.slice(0, max);
|
|
842
726
|
const half = Math.floor((tw - 1) / 2);
|
|
843
727
|
const nw = Math.max(
|
|
844
728
|
2,
|
|
845
|
-
String(Math.max(...diff.lines.map((l) => l.oldNum ?? l.newNum ?? 0), 0))
|
|
846
|
-
.length,
|
|
729
|
+
String(Math.max(...diff.lines.map((l) => l.oldNum ?? l.newNum ?? 0), 0)).length,
|
|
847
730
|
);
|
|
848
731
|
const gw = nw + 5;
|
|
849
732
|
const cw = Math.max(12, half - gw);
|
|
850
|
-
const canHL =
|
|
851
|
-
diff.chars <= MAX_HL_CHARS && vis.length * 2 <= MAX_RENDER_LINES * 2;
|
|
733
|
+
const canHL = diff.chars <= MAX_HL_CHARS && vis.length * 2 <= MAX_RENDER_LINES * 2;
|
|
852
734
|
|
|
853
735
|
const leftSrc: string[] = [];
|
|
854
736
|
const rightSrc: string[] = [];
|
|
@@ -920,9 +802,7 @@ export async function renderSplit(
|
|
|
920
802
|
|
|
921
803
|
// Split view's non-bordered context rows lead with a space before bg;
|
|
922
804
|
// buildGutter handles bordered rows, so feed the same border convention.
|
|
923
|
-
const splitBorder = borderFg
|
|
924
|
-
? `${gBg}${borderFg}${BORDER_BAR}`
|
|
925
|
-
: ` ${BG_BASE}`;
|
|
805
|
+
const splitBorder = borderFg ? `${gBg}${borderFg}${BORDER_BAR}` : ` ${BG_BASE}`;
|
|
926
806
|
const numCell = `${lnum(num, nw, numFg, true)}${sFg}${BOLD}${sign} `;
|
|
927
807
|
const gutter = `${splitBorder}${gBg}${numCell}${FG_RULE}│${cBg} ${RST}`;
|
|
928
808
|
const contGutter = `${splitBorder}${gBg}${" ".repeat(nw + 2)}${FG_RULE}│${cBg} ${RST}`;
|
|
@@ -936,14 +816,8 @@ export async function renderSplit(
|
|
|
936
816
|
for (const r of vis) {
|
|
937
817
|
const leftLine = r.left;
|
|
938
818
|
const rightLine = r.right;
|
|
939
|
-
const paired =
|
|
940
|
-
|
|
941
|
-
rightLine &&
|
|
942
|
-
leftLine.type === "del" &&
|
|
943
|
-
rightLine.type === "add";
|
|
944
|
-
const wd = paired
|
|
945
|
-
? wordDiffAnalysis(leftLine.content, rightLine.content)
|
|
946
|
-
: null;
|
|
819
|
+
const paired = leftLine && rightLine && leftLine.type === "del" && rightLine.type === "add";
|
|
820
|
+
const wd = paired ? wordDiffAnalysis(leftLine.content, rightLine.content) : null;
|
|
947
821
|
|
|
948
822
|
let lResult: HalfResult;
|
|
949
823
|
let rResult: HalfResult;
|
|
@@ -961,13 +835,9 @@ export async function renderSplit(
|
|
|
961
835
|
rResult = halfBuild(rightLine, pwd.new, null, "right");
|
|
962
836
|
} else {
|
|
963
837
|
const lhl =
|
|
964
|
-
leftLine && leftLine.type !== "sep"
|
|
965
|
-
? (leftHL[lI++] ?? leftLine?.content ?? "")
|
|
966
|
-
: "";
|
|
838
|
+
leftLine && leftLine.type !== "sep" ? (leftHL[lI++] ?? leftLine?.content ?? "") : "";
|
|
967
839
|
const rhl =
|
|
968
|
-
rightLine && rightLine.type !== "sep"
|
|
969
|
-
? (rightHL[rI++] ?? rightLine?.content ?? "")
|
|
970
|
-
: "";
|
|
840
|
+
rightLine && rightLine.type !== "sep" ? (rightHL[rI++] ?? rightLine?.content ?? "") : "";
|
|
971
841
|
lResult = halfBuild(leftLine, lhl, null, "left");
|
|
972
842
|
rResult = halfBuild(rightLine, rhl, null, "right");
|
|
973
843
|
}
|
|
@@ -987,9 +857,7 @@ export async function renderSplit(
|
|
|
987
857
|
|
|
988
858
|
out.push(`${rule(half)}${FG_RULE}┊${RST}${rule(half)}`);
|
|
989
859
|
if (rows.length > vis.length) {
|
|
990
|
-
out.push(
|
|
991
|
-
`${BG_BASE}${FG_DIM} … ${rows.length - vis.length} more lines${RST}`,
|
|
992
|
-
);
|
|
860
|
+
out.push(`${BG_BASE}${FG_DIM} … ${rows.length - vis.length} more lines${RST}`);
|
|
993
861
|
}
|
|
994
862
|
return out.join("\n");
|
|
995
863
|
}
|
package/src/diff.ts
CHANGED
|
@@ -42,8 +42,7 @@ export function parseDiff(
|
|
|
42
42
|
for (let hi = 0; hi < patch.hunks.length; hi++) {
|
|
43
43
|
if (hi > 0) {
|
|
44
44
|
const prev = at(patch.hunks, hi - 1);
|
|
45
|
-
const gap =
|
|
46
|
-
at(patch.hunks, hi).oldStart - (prev.oldStart + prev.oldLines);
|
|
45
|
+
const gap = at(patch.hunks, hi).oldStart - (prev.oldStart + prev.oldLines);
|
|
47
46
|
lines.push({
|
|
48
47
|
type: "sep",
|
|
49
48
|
oldNum: null,
|
package/src/fff.ts
CHANGED
|
@@ -31,9 +31,7 @@ export function getPiPrettyFffDir(_agentDir: string): string {
|
|
|
31
31
|
return join(cacheHome, "pi", "fff");
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export async function fffEnsureFinder(
|
|
35
|
-
cwd: string,
|
|
36
|
-
): Promise<FffBackedFinder | null> {
|
|
34
|
+
export async function fffEnsureFinder(cwd: string): Promise<FffBackedFinder | null> {
|
|
37
35
|
if (fffState.finder && !fffState.finder.isDestroyed) return fffState.finder;
|
|
38
36
|
if (!fffState.module || !fffState.dbDir) return null;
|
|
39
37
|
|
|
@@ -68,13 +66,9 @@ export function fffDestroy(): void {
|
|
|
68
66
|
function sanitizeGrepRecordContent(text: string): string {
|
|
69
67
|
let content = text;
|
|
70
68
|
if (content.endsWith("\r\n")) content = content.slice(0, -2);
|
|
71
|
-
else if (content.endsWith("\r") || content.endsWith("\n"))
|
|
72
|
-
content = content.slice(0, -1);
|
|
69
|
+
else if (content.endsWith("\r") || content.endsWith("\n")) content = content.slice(0, -1);
|
|
73
70
|
|
|
74
|
-
return content
|
|
75
|
-
.replace(/\r\n/g, "\\n")
|
|
76
|
-
.replace(/\r/g, "\\r")
|
|
77
|
-
.replace(/\n/g, "\\n");
|
|
71
|
+
return content.replace(/\r\n/g, "\\n").replace(/\r/g, "\\r").replace(/\n/g, "\\n");
|
|
78
72
|
}
|
|
79
73
|
|
|
80
74
|
function truncateGrepRecordContent(text: string): string {
|
package/src/gate-overlay.test.ts
CHANGED
|
@@ -27,9 +27,7 @@ interface Wired {
|
|
|
27
27
|
* the SelectList keys; simpler: we expose the live component so the test can
|
|
28
28
|
* call its handlers. We do the latter via the captured component ref.
|
|
29
29
|
*/
|
|
30
|
-
function makeUI(
|
|
31
|
-
onReady: (comp: Wired, finish: (v: unknown) => void) => void,
|
|
32
|
-
): OverlayUI {
|
|
30
|
+
function makeUI(onReady: (comp: Wired, finish: (v: unknown) => void) => void): OverlayUI {
|
|
33
31
|
return {
|
|
34
32
|
custom: async <T>(
|
|
35
33
|
cb: (
|
|
@@ -195,9 +193,7 @@ function makeTimerUI(onReady?: (comp: Wired) => void): OverlayUI {
|
|
|
195
193
|
) => Wired,
|
|
196
194
|
): Promise<T | undefined> =>
|
|
197
195
|
new Promise((resolve) => {
|
|
198
|
-
const comp = cb({ requestRender: () => {} }, theme, undefined, (v) =>
|
|
199
|
-
resolve(v),
|
|
200
|
-
);
|
|
196
|
+
const comp = cb({ requestRender: () => {} }, theme, undefined, (v) => resolve(v));
|
|
201
197
|
comp.render(80);
|
|
202
198
|
onReady?.(comp);
|
|
203
199
|
}),
|
package/src/gate-overlay.ts
CHANGED
|
@@ -14,12 +14,7 @@
|
|
|
14
14
|
* - Single source of truth for the overlay look across pix-gate and pix-sudo.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import {
|
|
18
|
-
Input,
|
|
19
|
-
type SelectItem,
|
|
20
|
-
SelectList,
|
|
21
|
-
wrapTextWithAnsi,
|
|
22
|
-
} from "@earendil-works/pi-tui";
|
|
17
|
+
import { Input, type SelectItem, SelectList, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
23
18
|
import { frameLines, modalWidth, selectListTheme } from "./modal-frame.js";
|
|
24
19
|
|
|
25
20
|
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
@@ -135,16 +130,7 @@ function buildLines(opts: {
|
|
|
135
130
|
countdownLine: string | undefined;
|
|
136
131
|
width: number;
|
|
137
132
|
}): string[] {
|
|
138
|
-
const {
|
|
139
|
-
theme,
|
|
140
|
-
accent,
|
|
141
|
-
config,
|
|
142
|
-
stage,
|
|
143
|
-
selectList,
|
|
144
|
-
maskedInput,
|
|
145
|
-
countdownLine,
|
|
146
|
-
width,
|
|
147
|
-
} = opts;
|
|
133
|
+
const { theme, accent, config, stage, selectList, maskedInput, countdownLine, width } = opts;
|
|
148
134
|
const inner = width - 4; // CHROME = 2 border + 2 padding
|
|
149
135
|
const lines: string[] = [];
|
|
150
136
|
|
|
@@ -172,10 +158,7 @@ function buildLines(opts: {
|
|
|
172
158
|
lines.push("");
|
|
173
159
|
lines.push(theme.fg("dim", "↑↓ navigate • enter select • esc deny"));
|
|
174
160
|
} else {
|
|
175
|
-
const label =
|
|
176
|
-
config.mode === "sudo"
|
|
177
|
-
? (config.passwordLabel ?? "Sudo password:")
|
|
178
|
-
: "Password:";
|
|
161
|
+
const label = config.mode === "sudo" ? (config.passwordLabel ?? "Sudo password:") : "Password:";
|
|
179
162
|
lines.push(theme.fg("muted", label));
|
|
180
163
|
const inputLines = maskedInput.render(inner);
|
|
181
164
|
for (const l of inputLines) lines.push(l);
|
|
@@ -217,10 +200,7 @@ function buildLines(opts: {
|
|
|
217
200
|
* if (result.action === "approved") runWithSudo(cmd, result.password!);
|
|
218
201
|
* ```
|
|
219
202
|
*/
|
|
220
|
-
export function showOverlay(
|
|
221
|
-
ui: OverlayUI,
|
|
222
|
-
config: OverlayConfig,
|
|
223
|
-
): Promise<OverlayResult> {
|
|
203
|
+
export function showOverlay(ui: OverlayUI, config: OverlayConfig): Promise<OverlayResult> {
|
|
224
204
|
const accent = config.accent ?? "accent";
|
|
225
205
|
const choices = config.choices ?? DEFAULT_CHOICES;
|
|
226
206
|
const approveVal = config.approveValue ?? "yes";
|
|
@@ -291,8 +271,7 @@ export function showOverlay(
|
|
|
291
271
|
};
|
|
292
272
|
selectList.onCancel = () => finish({ action: "denied" });
|
|
293
273
|
|
|
294
|
-
maskedInput.onSubmit = (pw) =>
|
|
295
|
-
finish({ action: "approved", password: pw });
|
|
274
|
+
maskedInput.onSubmit = (pw) => finish({ action: "approved", password: pw });
|
|
296
275
|
maskedInput.onEscape = () => finish({ action: "denied" });
|
|
297
276
|
|
|
298
277
|
// ── component interface ──────────────────────────────────────────
|
package/src/highlight.ts
CHANGED
|
@@ -10,10 +10,7 @@ import type { BundledLanguage } from "./types.js";
|
|
|
10
10
|
// is not the process stdout chalk inspects) we default FORCE_COLOR before chalk
|
|
11
11
|
// initializes, and lazy-load cli-highlight so this runs first. Respect an
|
|
12
12
|
// explicit FORCE_COLOR/NO_COLOR if the user set one.
|
|
13
|
-
if (
|
|
14
|
-
process.env.FORCE_COLOR === undefined &&
|
|
15
|
-
process.env.NO_COLOR === undefined
|
|
16
|
-
) {
|
|
13
|
+
if (process.env.FORCE_COLOR === undefined && process.env.NO_COLOR === undefined) {
|
|
17
14
|
process.env.FORCE_COLOR = "3";
|
|
18
15
|
}
|
|
19
16
|
|
package/src/icon-catalog.ts
CHANGED
|
@@ -77,6 +77,9 @@ const CATALOG = {
|
|
|
77
77
|
|
|
78
78
|
// ── subagent widget (pix-subagent) ────────────────────────────────────
|
|
79
79
|
agent: { nerd: "\u{F0BA0}", unicode: `\u2699${VS}`, ascii: "@" },
|
|
80
|
+
turns: { nerd: "\u{F006A}", unicode: `\u21BB${VS}`, ascii: "~" },
|
|
81
|
+
tools: { nerd: "\u{F1064}", unicode: `\u2692${VS}`, ascii: "T" },
|
|
82
|
+
tokens: { nerd: "\u{F027F}", unicode: `\u25A4${VS}`, ascii: "tk" },
|
|
80
83
|
} as const;
|
|
81
84
|
|
|
82
85
|
/** Every valid semantic icon key. */
|
package/src/icon-persist.ts
CHANGED
|
@@ -47,10 +47,7 @@ export function saveIconMode(mode: IconMode): void {
|
|
|
47
47
|
let existing: Record<string, unknown> = {};
|
|
48
48
|
if (existsSync(p)) {
|
|
49
49
|
try {
|
|
50
|
-
existing = JSON.parse(readFileSync(p, "utf-8")) as Record<
|
|
51
|
-
string,
|
|
52
|
-
unknown
|
|
53
|
-
>;
|
|
50
|
+
existing = JSON.parse(readFileSync(p, "utf-8")) as Record<string, unknown>;
|
|
54
51
|
} catch {
|
|
55
52
|
existing = {};
|
|
56
53
|
}
|
package/src/modal-frame.ts
CHANGED
|
@@ -71,8 +71,7 @@ export function frameLines(opts: FrameOptions): string[] {
|
|
|
71
71
|
|
|
72
72
|
const row = (content: string): string => {
|
|
73
73
|
const pad = inner - visibleWidth(content);
|
|
74
|
-
const padded =
|
|
75
|
-
pad > 0 ? content + " ".repeat(pad) : truncateToWidth(content, inner);
|
|
74
|
+
const padded = pad > 0 ? content + " ".repeat(pad) : truncateToWidth(content, inner);
|
|
76
75
|
return bg(`${color("│")} ${reassert(padded)} ${color("│")}`);
|
|
77
76
|
};
|
|
78
77
|
|
|
@@ -101,10 +100,7 @@ interface FgTheme {
|
|
|
101
100
|
* Canonical SelectList theme for interactive overlays.
|
|
102
101
|
* accent = active/selected, muted = descriptions, dim = scroll/hints, warning = no-match.
|
|
103
102
|
*/
|
|
104
|
-
export function selectListTheme(
|
|
105
|
-
theme: FgTheme,
|
|
106
|
-
accent = "accent",
|
|
107
|
-
): SelectListThemeConfig {
|
|
103
|
+
export function selectListTheme(theme: FgTheme, accent = "accent"): SelectListThemeConfig {
|
|
108
104
|
return {
|
|
109
105
|
selectedPrefix: (t) => theme.fg(accent, t),
|
|
110
106
|
selectedText: (t) => theme.fg(accent, t),
|
package/src/progress.ts
CHANGED
|
@@ -55,11 +55,7 @@ const SPINNER_INTERVAL_MS = 120;
|
|
|
55
55
|
* Open a modal progress overlay. Returns a handle to update the label and
|
|
56
56
|
* close it. The overlay swallows all keystrokes until closed.
|
|
57
57
|
*/
|
|
58
|
-
export function openProgress(
|
|
59
|
-
ui: ProgressUI,
|
|
60
|
-
title: string,
|
|
61
|
-
accent = "accent",
|
|
62
|
-
): ProgressHandle {
|
|
58
|
+
export function openProgress(ui: ProgressUI, title: string, accent = "accent"): ProgressHandle {
|
|
63
59
|
let setLabelImpl: (label: string) => void = () => {};
|
|
64
60
|
let closeImpl: () => void = () => {};
|
|
65
61
|
|
package/src/renderers.ts
CHANGED
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
BOLD,
|
|
5
|
-
FG_BLUE,
|
|
6
|
-
FG_DIM,
|
|
7
|
-
FG_GREEN,
|
|
8
|
-
FG_RED,
|
|
9
|
-
FG_RULE,
|
|
10
|
-
FG_YELLOW,
|
|
11
|
-
RST,
|
|
12
|
-
} from "./ansi.js";
|
|
3
|
+
import { BOLD, FG_BLUE, FG_DIM, FG_GREEN, FG_RED, FG_RULE, FG_YELLOW, RST } from "./ansi.js";
|
|
13
4
|
import { MAX_PREVIEW_LINES } from "./config.js";
|
|
14
5
|
import { hlBlock } from "./highlight.js";
|
|
15
6
|
import { dirIcon, fileIcon } from "./icons.js";
|
|
@@ -49,9 +40,7 @@ export async function renderFileContent(
|
|
|
49
40
|
|
|
50
41
|
out.push(rule(tw));
|
|
51
42
|
if (total > maxLines) {
|
|
52
|
-
out.push(
|
|
53
|
-
`${FG_DIM} … ${pluralize(total - maxLines, "more line")} (${total} total)${RST}`,
|
|
54
|
-
);
|
|
43
|
+
out.push(`${FG_DIM} … ${pluralize(total - maxLines, "more line")} (${total} total)${RST}`);
|
|
55
44
|
}
|
|
56
45
|
return out.join("\n");
|
|
57
46
|
}
|
package/src/types-diff.d.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -41,28 +41,19 @@ export type ToolImageContent = ImageContent;
|
|
|
41
41
|
|
|
42
42
|
export type ToolContent = TextContent | ImageContent;
|
|
43
43
|
|
|
44
|
-
export type ToolResultLike<TDetails = unknown> = AgentToolResult<
|
|
45
|
-
TDetails | undefined
|
|
46
|
-
>;
|
|
44
|
+
export type ToolResultLike<TDetails = unknown> = AgentToolResult<TDetails | undefined>;
|
|
47
45
|
|
|
48
46
|
type TextComponentLike = {
|
|
49
47
|
setText(value: string): void;
|
|
50
48
|
getText?: () => string;
|
|
51
49
|
};
|
|
52
50
|
|
|
53
|
-
export type TextComponentCtor = new (
|
|
54
|
-
text?: string,
|
|
55
|
-
x?: number,
|
|
56
|
-
y?: number,
|
|
57
|
-
) => TextComponentLike;
|
|
51
|
+
export type TextComponentCtor = new (text?: string, x?: number, y?: number) => TextComponentLike;
|
|
58
52
|
|
|
59
53
|
export type ThemeLike = BgTheme & FgTheme & { bold: (text: string) => string };
|
|
60
54
|
|
|
61
55
|
export type RenderContextLike<
|
|
62
|
-
TState extends Record<string, string | undefined> = Record<
|
|
63
|
-
string,
|
|
64
|
-
string | undefined
|
|
65
|
-
>,
|
|
56
|
+
TState extends Record<string, string | undefined> = Record<string, string | undefined>,
|
|
66
57
|
> = {
|
|
67
58
|
lastComponent?: TextComponentLike;
|
|
68
59
|
state: TState;
|
package/src/utils.test.ts
CHANGED
|
@@ -49,20 +49,14 @@ describe("renderDimPreview", () => {
|
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
it("adds singular overflow marker for 1 extra line", () => {
|
|
52
|
-
const body = Array.from(
|
|
53
|
-
{ length: MAX_PREVIEW_LINES + 1 },
|
|
54
|
-
(_, i) => `L${i}`,
|
|
55
|
-
);
|
|
52
|
+
const body = Array.from({ length: MAX_PREVIEW_LINES + 1 }, (_, i) => `L${i}`);
|
|
56
53
|
const out = plain(renderDimPreview(body.join("\n"), theme));
|
|
57
54
|
expect(out).toContain("… 1 more line");
|
|
58
55
|
expect(out).not.toContain("more lines");
|
|
59
56
|
});
|
|
60
57
|
|
|
61
58
|
it("adds plural overflow marker for many extra lines", () => {
|
|
62
|
-
const body = Array.from(
|
|
63
|
-
{ length: MAX_PREVIEW_LINES + 3 },
|
|
64
|
-
(_, i) => `L${i}`,
|
|
65
|
-
);
|
|
59
|
+
const body = Array.from({ length: MAX_PREVIEW_LINES + 3 }, (_, i) => `L${i}`);
|
|
66
60
|
const out = plain(renderDimPreview(body.join("\n"), theme));
|
|
67
61
|
expect(out).toContain("… 3 more lines");
|
|
68
62
|
});
|
|
@@ -86,8 +80,6 @@ describe("renderDimPreview", () => {
|
|
|
86
80
|
});
|
|
87
81
|
|
|
88
82
|
it("does not throw on an invalid highlight regex", () => {
|
|
89
|
-
expect(() =>
|
|
90
|
-
renderDimPreview("text", theme, { highlight: "(" }),
|
|
91
|
-
).not.toThrow();
|
|
83
|
+
expect(() => renderDimPreview("text", theme, { highlight: "(" })).not.toThrow();
|
|
92
84
|
});
|
|
93
85
|
});
|
package/src/utils.ts
CHANGED
|
@@ -41,21 +41,14 @@ export function fillToolBackground(text: string, bg = BG_BASE): string {
|
|
|
41
41
|
.split("\n")
|
|
42
42
|
.map((line) => {
|
|
43
43
|
const normalized = preserveToolBackground(line, bg);
|
|
44
|
-
const fitted = preserveToolBackground(
|
|
45
|
-
truncateToWidth(normalized, width, ""),
|
|
46
|
-
bg,
|
|
47
|
-
);
|
|
44
|
+
const fitted = preserveToolBackground(truncateToWidth(normalized, width, ""), bg);
|
|
48
45
|
const padding = Math.max(0, width - visibleWidth(fitted));
|
|
49
46
|
return `${bg}${fitted}${" ".repeat(padding)}${RST}`;
|
|
50
47
|
})
|
|
51
48
|
.join("\n");
|
|
52
49
|
}
|
|
53
50
|
|
|
54
|
-
export function pluralize(
|
|
55
|
-
count: number,
|
|
56
|
-
noun: string,
|
|
57
|
-
plural?: string,
|
|
58
|
-
): string {
|
|
51
|
+
export function pluralize(count: number, noun: string, plural?: string): string {
|
|
59
52
|
return `${count} ${count === 1 ? noun : (plural ?? `${noun}s`)}`;
|
|
60
53
|
}
|
|
61
54
|
|
|
@@ -74,18 +67,12 @@ function safeHighlightRegex(pattern: string): RegExp | null {
|
|
|
74
67
|
}
|
|
75
68
|
}
|
|
76
69
|
|
|
77
|
-
function dimLineWithHighlight(
|
|
78
|
-
line: string,
|
|
79
|
-
theme: FgTheme,
|
|
80
|
-
re: RegExp | null,
|
|
81
|
-
): string {
|
|
70
|
+
function dimLineWithHighlight(line: string, theme: FgTheme, re: RegExp | null): string {
|
|
82
71
|
if (!re) return theme.fg("dim", line);
|
|
83
72
|
// split with capture group: odd indexes are matches
|
|
84
73
|
return line
|
|
85
74
|
.split(re)
|
|
86
|
-
.map((part, i) =>
|
|
87
|
-
i % 2 ? `${FG_GREEN}${BOLD}${part}${RST}` : theme.fg("dim", part),
|
|
88
|
-
)
|
|
75
|
+
.map((part, i) => (i % 2 ? `${FG_GREEN}${BOLD}${part}${RST}` : theme.fg("dim", part)))
|
|
89
76
|
.join("");
|
|
90
77
|
}
|
|
91
78
|
|
|
@@ -204,15 +191,11 @@ export function humanSize(bytes: number): string {
|
|
|
204
191
|
// Fallback: set PRETTY_ICONS=none to disable icons.
|
|
205
192
|
// ---------------------------------------------------------------------------
|
|
206
193
|
|
|
207
|
-
export function isTextContent(
|
|
208
|
-
content: ToolContent,
|
|
209
|
-
): content is ToolTextContent {
|
|
194
|
+
export function isTextContent(content: ToolContent): content is ToolTextContent {
|
|
210
195
|
return content.type === "text";
|
|
211
196
|
}
|
|
212
197
|
|
|
213
|
-
export function isImageContent(
|
|
214
|
-
content: ToolContent,
|
|
215
|
-
): content is ToolImageContent {
|
|
198
|
+
export function isImageContent(content: ToolContent): content is ToolImageContent {
|
|
216
199
|
return content.type === "image";
|
|
217
200
|
}
|
|
218
201
|
|