@xynogen/pix-pretty 1.7.9 → 1.7.12
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 +2 -2
- package/src/commands/pretty.ts +2 -2
- package/src/diff-render.ts +41 -20
- package/src/diff.ts +10 -3
- package/src/icon-catalog.ts +3 -0
- package/src/icon-persist.ts +6 -3
- package/src/progress.ts +1 -1
- package/src/renderers.ts +1 -1
package/package.json
CHANGED
package/src/ansi.ts
CHANGED
|
@@ -23,7 +23,7 @@ function parseAnsiRgb(
|
|
|
23
23
|
const m = ansi.match(
|
|
24
24
|
new RegExp(`${ESC_RE}\\[(?:38|48);2;(\\d+);(\\d+);(\\d+)m`),
|
|
25
25
|
);
|
|
26
|
-
return m ? { r: +m[1], g: +m[2], b: +m[3] } : null;
|
|
26
|
+
return m ? { r: +(m[1] ?? 0), g: +(m[2] ?? 0), b: +(m[3] ?? 0) } : null;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
function getThemeBgAnsi(theme: BgTheme, key: string): string | null {
|
|
@@ -63,7 +63,7 @@ function isLowContrastShikiFg(params: string): boolean {
|
|
|
63
63
|
const parts = params.split(";").map(Number);
|
|
64
64
|
if (parts.length !== 5 || parts.some((n) => !Number.isFinite(n)))
|
|
65
65
|
return false;
|
|
66
|
-
const [, , r, g, b] = parts;
|
|
66
|
+
const [, , r = 0, g = 0, b = 0] = parts;
|
|
67
67
|
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
68
68
|
return luminance < 72;
|
|
69
69
|
}
|
package/src/commands/pretty.ts
CHANGED
|
@@ -62,7 +62,7 @@ export function registerPrettyCommand(pi: PiPrettyApi): void {
|
|
|
62
62
|
// Headless / no custom-UI host: cycle to the next mode + notify.
|
|
63
63
|
if (typeof ui.custom !== "function") {
|
|
64
64
|
const cur = ICON_MODES.indexOf(getIconMode());
|
|
65
|
-
const next = ICON_MODES[(cur + 1) % ICON_MODES.length]
|
|
65
|
+
const next = ICON_MODES[(cur + 1) % ICON_MODES.length] ?? "nerd";
|
|
66
66
|
applyMode(next);
|
|
67
67
|
ui.notify(`pix-pretty icons: ${next}`, "info");
|
|
68
68
|
return;
|
|
@@ -82,7 +82,7 @@ export function registerPrettyCommand(pi: PiPrettyApi): void {
|
|
|
82
82
|
|
|
83
83
|
const choose = (i: number) => {
|
|
84
84
|
selected = (i + ICON_MODES.length) % ICON_MODES.length;
|
|
85
|
-
applyMode(ICON_MODES[selected]
|
|
85
|
+
applyMode(ICON_MODES[selected] ?? "nerd");
|
|
86
86
|
};
|
|
87
87
|
|
|
88
88
|
return {
|
package/src/diff-render.ts
CHANGED
|
@@ -464,9 +464,12 @@ function injectBg(
|
|
|
464
464
|
continue;
|
|
465
465
|
}
|
|
466
466
|
}
|
|
467
|
-
while (ri < ranges.length && vis >= ranges[ri][1])
|
|
467
|
+
while (ri < ranges.length && vis >= (ranges[ri] as [number, number])[1])
|
|
468
|
+
ri++;
|
|
468
469
|
const want =
|
|
469
|
-
ri < ranges.length &&
|
|
470
|
+
ri < ranges.length &&
|
|
471
|
+
vis >= (ranges[ri] as [number, number])[0] &&
|
|
472
|
+
vis < (ranges[ri] as [number, number])[1];
|
|
470
473
|
if (want !== inHL) {
|
|
471
474
|
inHL = want;
|
|
472
475
|
out += inHL ? hlBg : baseBg;
|
|
@@ -497,6 +500,12 @@ function plainWordDiff(
|
|
|
497
500
|
return { old: o, new: n };
|
|
498
501
|
}
|
|
499
502
|
|
|
503
|
+
/** Type-safe index into an array that noUncheckedIndexedAccess marks as T|undefined.
|
|
504
|
+
* Only call when the index is provably in-bounds (loop condition, length check, etc.). */
|
|
505
|
+
function at<T>(arr: T[], i: number): T {
|
|
506
|
+
return arr[i] as T;
|
|
507
|
+
}
|
|
508
|
+
|
|
500
509
|
// ---------------------------------------------------------------------------
|
|
501
510
|
// Split-vs-unified decision
|
|
502
511
|
// ---------------------------------------------------------------------------
|
|
@@ -605,7 +614,7 @@ export async function renderUnified(
|
|
|
605
614
|
}
|
|
606
615
|
|
|
607
616
|
while (idx < vis.length) {
|
|
608
|
-
const l = vis
|
|
617
|
+
const l = at(vis, idx);
|
|
609
618
|
|
|
610
619
|
if (l.type === "sep") {
|
|
611
620
|
const gap = l.newNum;
|
|
@@ -638,29 +647,35 @@ export async function renderUnified(
|
|
|
638
647
|
}
|
|
639
648
|
|
|
640
649
|
const dels: Array<{ l: DiffLine; hl: string }> = [];
|
|
641
|
-
while (idx < vis.length
|
|
642
|
-
|
|
650
|
+
while (idx < vis.length) {
|
|
651
|
+
const entry = at(vis, idx);
|
|
652
|
+
if (entry.type !== "del") break;
|
|
653
|
+
dels.push({ l: entry, hl: oldHL[oI] ?? entry.content });
|
|
643
654
|
oI++;
|
|
644
655
|
idx++;
|
|
645
656
|
}
|
|
646
657
|
const adds: Array<{ l: DiffLine; hl: string }> = [];
|
|
647
|
-
while (idx < vis.length
|
|
648
|
-
|
|
658
|
+
while (idx < vis.length) {
|
|
659
|
+
const entry = at(vis, idx);
|
|
660
|
+
if (entry.type !== "add") break;
|
|
661
|
+
adds.push({ l: entry, hl: newHL[nI] ?? entry.content });
|
|
649
662
|
nI++;
|
|
650
663
|
idx++;
|
|
651
664
|
}
|
|
652
665
|
|
|
653
666
|
const isPaired = dels.length === 1 && adds.length === 1;
|
|
654
667
|
const wd = isPaired
|
|
655
|
-
? wordDiffAnalysis(dels
|
|
668
|
+
? wordDiffAnalysis(at(dels, 0).l.content, at(adds, 0).l.content)
|
|
656
669
|
: null;
|
|
657
670
|
const wdBalanced = wd && wd.oldRanges.length > 0 && wd.newRanges.length > 0;
|
|
658
671
|
|
|
659
672
|
if (isPaired && wdBalanced && wd.similarity >= WORD_DIFF_MIN_SIM && canHL) {
|
|
660
|
-
const
|
|
661
|
-
const
|
|
673
|
+
const del0 = at(dels, 0);
|
|
674
|
+
const add0 = at(adds, 0);
|
|
675
|
+
const delBody = injectBg(del0.hl, wd.oldRanges, BG_DEL, BG_DEL_W);
|
|
676
|
+
const addBody = injectBg(add0.hl, wd.newRanges, BG_ADD, BG_ADD_W);
|
|
662
677
|
emitRow(
|
|
663
|
-
|
|
678
|
+
del0.l.oldNum,
|
|
664
679
|
"-",
|
|
665
680
|
BG_GUTTER_DEL,
|
|
666
681
|
`${dc.fgDel}${BOLD}`,
|
|
@@ -668,7 +683,7 @@ export async function renderUnified(
|
|
|
668
683
|
BG_DEL,
|
|
669
684
|
);
|
|
670
685
|
emitRow(
|
|
671
|
-
|
|
686
|
+
add0.l.newNum,
|
|
672
687
|
"+",
|
|
673
688
|
BG_GUTTER_ADD,
|
|
674
689
|
`${dc.fgAdd}${BOLD}`,
|
|
@@ -683,9 +698,11 @@ export async function renderUnified(
|
|
|
683
698
|
wd.similarity >= WORD_DIFF_MIN_SIM &&
|
|
684
699
|
!canHL
|
|
685
700
|
) {
|
|
686
|
-
const
|
|
701
|
+
const del0 = at(dels, 0);
|
|
702
|
+
const add0 = at(adds, 0);
|
|
703
|
+
const pwd = plainWordDiff(del0.l.content, add0.l.content);
|
|
687
704
|
emitRow(
|
|
688
|
-
|
|
705
|
+
del0.l.oldNum,
|
|
689
706
|
"-",
|
|
690
707
|
BG_GUTTER_DEL,
|
|
691
708
|
`${dc.fgDel}${BOLD}`,
|
|
@@ -693,7 +710,7 @@ export async function renderUnified(
|
|
|
693
710
|
BG_DEL,
|
|
694
711
|
);
|
|
695
712
|
emitRow(
|
|
696
|
-
|
|
713
|
+
add0.l.newNum,
|
|
697
714
|
"+",
|
|
698
715
|
BG_GUTTER_ADD,
|
|
699
716
|
`${dc.fgAdd}${BOLD}`,
|
|
@@ -755,7 +772,7 @@ export async function renderSplit(
|
|
|
755
772
|
const rows: Row[] = [];
|
|
756
773
|
let i = 0;
|
|
757
774
|
while (i < diff.lines.length) {
|
|
758
|
-
const l = diff.lines
|
|
775
|
+
const l = at(diff.lines, i);
|
|
759
776
|
if (l.type === "sep" || l.type === "ctx") {
|
|
760
777
|
rows.push({ left: l, right: l });
|
|
761
778
|
i++;
|
|
@@ -763,12 +780,16 @@ export async function renderSplit(
|
|
|
763
780
|
}
|
|
764
781
|
const dels: DiffLine[] = [];
|
|
765
782
|
const adds: DiffLine[] = [];
|
|
766
|
-
while (i < diff.lines.length
|
|
767
|
-
|
|
783
|
+
while (i < diff.lines.length) {
|
|
784
|
+
const entry = at(diff.lines, i);
|
|
785
|
+
if (entry.type !== "del") break;
|
|
786
|
+
dels.push(entry);
|
|
768
787
|
i++;
|
|
769
788
|
}
|
|
770
|
-
while (i < diff.lines.length
|
|
771
|
-
|
|
789
|
+
while (i < diff.lines.length) {
|
|
790
|
+
const entry = at(diff.lines, i);
|
|
791
|
+
if (entry.type !== "add") break;
|
|
792
|
+
adds.push(entry);
|
|
772
793
|
i++;
|
|
773
794
|
}
|
|
774
795
|
const n = Math.max(dels.length, adds.length);
|
package/src/diff.ts
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
import * as Diff from "diff";
|
|
6
6
|
|
|
7
|
+
/** Type-safe index into an array that noUncheckedIndexedAccess marks as T|undefined.
|
|
8
|
+
* Only call when the index is provably in-bounds (loop condition, length check, etc.). */
|
|
9
|
+
function at<T>(arr: T[], i: number): T {
|
|
10
|
+
return arr[i] as T;
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
export interface DiffLine {
|
|
8
14
|
type: "add" | "del" | "ctx" | "sep";
|
|
9
15
|
oldNum: number | null;
|
|
@@ -35,8 +41,9 @@ export function parseDiff(
|
|
|
35
41
|
|
|
36
42
|
for (let hi = 0; hi < patch.hunks.length; hi++) {
|
|
37
43
|
if (hi > 0) {
|
|
38
|
-
const prev = patch.hunks
|
|
39
|
-
const gap =
|
|
44
|
+
const prev = at(patch.hunks, hi - 1);
|
|
45
|
+
const gap =
|
|
46
|
+
at(patch.hunks, hi).oldStart - (prev.oldStart + prev.oldLines);
|
|
40
47
|
lines.push({
|
|
41
48
|
type: "sep",
|
|
42
49
|
oldNum: null,
|
|
@@ -44,7 +51,7 @@ export function parseDiff(
|
|
|
44
51
|
content: "",
|
|
45
52
|
});
|
|
46
53
|
}
|
|
47
|
-
const h = patch.hunks
|
|
54
|
+
const h = at(patch.hunks, hi);
|
|
48
55
|
const shift = baseLine > 0 ? baseLine - 1 : 0;
|
|
49
56
|
let oL = h.oldStart + shift;
|
|
50
57
|
let nL = h.newStart + shift;
|
package/src/icon-catalog.ts
CHANGED
|
@@ -74,6 +74,9 @@ const CATALOG = {
|
|
|
74
74
|
"opt.toon": { nerd: "\u{F05C0}", unicode: `\u2662${VS}`, ascii: "Tn" },
|
|
75
75
|
"opt.ponytail": { nerd: "\u{F0190}", unicode: `\u2667${VS}`, ascii: "Pt" },
|
|
76
76
|
"opt.title": { nerd: "\u{F0DAB}", unicode: `\u25C8${VS}`, ascii: "*" },
|
|
77
|
+
|
|
78
|
+
// ── subagent widget (pix-subagent) ────────────────────────────────────
|
|
79
|
+
agent: { nerd: "\u{F0BA0}", unicode: `\u2699${VS}`, ascii: "@" },
|
|
77
80
|
} as const;
|
|
78
81
|
|
|
79
82
|
/** Every valid semantic icon key. */
|
package/src/icon-persist.ts
CHANGED
|
@@ -16,6 +16,10 @@ import { dirname, join } from "node:path";
|
|
|
16
16
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
17
17
|
import { ICON_MODES, type IconMode, setIconMode } from "./icon-catalog.js";
|
|
18
18
|
|
|
19
|
+
function isIconMode(m: string): m is IconMode {
|
|
20
|
+
return (ICON_MODES as readonly string[]).includes(m);
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
function statePath(): string {
|
|
20
24
|
return join(getAgentDir(), "pretty.json");
|
|
21
25
|
}
|
|
@@ -27,9 +31,8 @@ export function loadIconMode(): IconMode | undefined {
|
|
|
27
31
|
if (!existsSync(p)) return undefined;
|
|
28
32
|
const raw = JSON.parse(readFileSync(p, "utf-8")) as { icons?: string };
|
|
29
33
|
const mode = raw?.icons;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
: undefined;
|
|
34
|
+
if (mode == null) return undefined;
|
|
35
|
+
return isIconMode(mode) ? mode : undefined;
|
|
33
36
|
} catch {
|
|
34
37
|
return undefined;
|
|
35
38
|
}
|
package/src/progress.ts
CHANGED
|
@@ -89,7 +89,7 @@ export function openProgress(
|
|
|
89
89
|
width: mw,
|
|
90
90
|
lines: [
|
|
91
91
|
theme.fg(accent, theme.bold(title)),
|
|
92
|
-
`${theme.fg(accent, SPINNER[frame])} ${theme.fg("muted", labelValue)}`,
|
|
92
|
+
`${theme.fg(accent, SPINNER[frame] ?? "")} ${theme.fg("muted", labelValue)}`,
|
|
93
93
|
],
|
|
94
94
|
color: (s) => theme.fg(accent, s),
|
|
95
95
|
bg: (s) => theme.bg("customMessageBg", s),
|
package/src/renderers.ts
CHANGED
|
@@ -92,7 +92,7 @@ export function renderTree(text: string, _basePath: string): string {
|
|
|
92
92
|
const show = lines.slice(0, MAX_PREVIEW_LINES);
|
|
93
93
|
|
|
94
94
|
for (let i = 0; i < show.length; i++) {
|
|
95
|
-
const entry = show[i].trim();
|
|
95
|
+
const entry = (show[i] ?? "").trim();
|
|
96
96
|
const isLast = i === show.length - 1 && total <= MAX_PREVIEW_LINES;
|
|
97
97
|
const prefix = isLast ? "└── " : "├── ";
|
|
98
98
|
const connector = `${FG_RULE}${prefix}${RST}`;
|