@xynogen/pix-pretty 1.7.10 → 1.7.13
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 +18 -1
- package/package.json +2 -1
- package/src/ansi.ts +2 -2
- package/src/commands/pretty.ts +2 -2
- package/src/config.ts +39 -4
- package/src/diff-render.ts +94 -32
- package/src/diff.ts +10 -3
- package/src/icon-catalog.ts +3 -0
- package/src/icon-persist.ts +16 -4
- package/src/progress.ts +1 -1
- package/src/renderers.ts +1 -1
package/README.md
CHANGED
|
@@ -61,7 +61,24 @@ pi install npm:@xynogen/pix-pretty
|
|
|
61
61
|
|
|
62
62
|
## Configuration
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
Configuration is read from **`~/.pi/agent/pix.json`** (the unified config file hosted by `@xynogen/pix-data/pix-config`). The `pretty` section of that file sets the defaults for theme, icon mode, and preview lines. Environment variables still override `pix.json` values.
|
|
65
|
+
|
|
66
|
+
> **Note:** `pix-config.ts` and `collapse.ts` previously shipped with `pix-pretty` — they have moved to `pix-data` (`@xynogen/pix-data/pix-config` and `@xynogen/pix-data/collapse`). Update any direct imports.
|
|
67
|
+
|
|
68
|
+
### `pix.json` — `pretty` section
|
|
69
|
+
|
|
70
|
+
```jsonc
|
|
71
|
+
{
|
|
72
|
+
"pretty": {
|
|
73
|
+
"theme": "monokai", // syntax-highlight theme
|
|
74
|
+
"icons": "nerd", // nerd | unicode | ascii
|
|
75
|
+
"maxPreviewLines": 50,
|
|
76
|
+
"diffColors": true
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Environment Variables (override `pix.json`)
|
|
65
82
|
|
|
66
83
|
- `PRETTY_THEME` — color theme for syntax highlighting
|
|
67
84
|
- `PRETTY_MAX_HL_CHARS` — max characters to highlight (default: 80000)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xynogen/pix-pretty",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.13",
|
|
4
4
|
"description": "Enhanced tool output rendering with syntax highlighting, file icons, tree views, diff rendering, and FFF search",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"access": "public"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
+
"@xynogen/pix-data": "workspace:*",
|
|
61
62
|
"cli-highlight": "^2.1.11",
|
|
62
63
|
"@ff-labs/fff-node": "^0.5.2",
|
|
63
64
|
"diff": "^7.0.0"
|
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/config.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
|
|
4
|
+
import { pixConfig } from "@xynogen/pix-data/pix-config";
|
|
4
5
|
import type { BundledTheme } from "./types.js";
|
|
5
6
|
|
|
6
7
|
const DEFAULT_THEME: BundledTheme = "github-dark";
|
|
@@ -29,8 +30,10 @@ function readThemeFromSettings(agentDir?: string): BundledTheme | undefined {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
function resolvePrettyTheme(agentDir?: string): BundledTheme {
|
|
33
|
+
// Precedence: env → pix.json → settings.json → default
|
|
32
34
|
return (
|
|
33
35
|
(process.env.PRETTY_THEME as BundledTheme | undefined) ??
|
|
36
|
+
(pixConfig().pretty.theme as BundledTheme) ??
|
|
34
37
|
readThemeFromSettings(agentDir) ??
|
|
35
38
|
DEFAULT_THEME
|
|
36
39
|
);
|
|
@@ -49,14 +52,46 @@ export function envInt(name: string, fallback: number): number {
|
|
|
49
52
|
return Number.isFinite(v) && v > 0 ? v : fallback;
|
|
50
53
|
}
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
// Precedence for numeric config: env var → pix.json → hardcoded default
|
|
56
|
+
function pixOrEnvInt(
|
|
57
|
+
envName: string,
|
|
58
|
+
pixValue: number,
|
|
59
|
+
fallback: number,
|
|
60
|
+
): number {
|
|
61
|
+
const env = process.env[envName];
|
|
62
|
+
if (env) {
|
|
63
|
+
const v = Number.parseInt(env, 10);
|
|
64
|
+
if (Number.isFinite(v) && v > 0) return v;
|
|
65
|
+
}
|
|
66
|
+
return pixValue !== fallback ? pixValue : fallback;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const pc = pixConfig().pretty;
|
|
70
|
+
|
|
71
|
+
export const MAX_HL_CHARS = pixOrEnvInt(
|
|
72
|
+
"PRETTY_MAX_HL_CHARS",
|
|
73
|
+
pc.maxHighlightChars,
|
|
74
|
+
80_000,
|
|
75
|
+
);
|
|
53
76
|
|
|
54
|
-
export const MAX_PREVIEW_LINES =
|
|
77
|
+
export const MAX_PREVIEW_LINES = pixOrEnvInt(
|
|
78
|
+
"PRETTY_MAX_PREVIEW_LINES",
|
|
79
|
+
pc.maxPreviewLines,
|
|
80
|
+
80,
|
|
81
|
+
);
|
|
55
82
|
|
|
56
|
-
export const CACHE_LIMIT =
|
|
83
|
+
export const CACHE_LIMIT = pixOrEnvInt(
|
|
84
|
+
"PRETTY_CACHE_LIMIT",
|
|
85
|
+
pc.cacheLimit,
|
|
86
|
+
128,
|
|
87
|
+
);
|
|
57
88
|
|
|
58
89
|
// --- Diff rendering limits (edit/write tools) ---
|
|
59
|
-
export const MAX_RENDER_LINES =
|
|
90
|
+
export const MAX_RENDER_LINES = pixOrEnvInt(
|
|
91
|
+
"PRETTY_MAX_RENDER_LINES",
|
|
92
|
+
pc.maxRenderLines,
|
|
93
|
+
150,
|
|
94
|
+
);
|
|
60
95
|
|
|
61
96
|
// Word-level emphasis only when paired del/add lines are at least this similar.
|
|
62
97
|
export const WORD_DIFF_MIN_SIM = 0.15;
|
package/src/diff-render.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// technique below works unchanged — diff backgrounds layer underneath and
|
|
9
9
|
// persist through fg switches.
|
|
10
10
|
|
|
11
|
+
import { pixConfig } from "@xynogen/pix-data/pix-config";
|
|
11
12
|
import * as Diff from "diff";
|
|
12
13
|
import { BG_BASE, BOLD, FG_DIM, FG_LNUM, FG_RULE, RST } from "./ansi.js";
|
|
13
14
|
import { MAX_HL_CHARS, MAX_RENDER_LINES, WORD_DIFF_MIN_SIM } from "./config.js";
|
|
@@ -44,21 +45,58 @@ function envBg(name: string, fallback: string): string {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
// ---------------------------------------------------------------------------
|
|
47
|
-
// Diff-specific ANSI (override via env
|
|
48
|
+
// Diff-specific ANSI (override via env → pix.json → hardcoded)
|
|
48
49
|
// ---------------------------------------------------------------------------
|
|
49
50
|
|
|
50
51
|
const DIM = "\x1b[2m";
|
|
51
52
|
|
|
53
|
+
function hexToBg(hex: string): string {
|
|
54
|
+
if (!/^#[0-9a-fA-F]{6}$/.test(hex)) return "";
|
|
55
|
+
const r = Number.parseInt(hex.slice(1, 3), 16);
|
|
56
|
+
const g = Number.parseInt(hex.slice(3, 5), 16);
|
|
57
|
+
const b = Number.parseInt(hex.slice(5, 7), 16);
|
|
58
|
+
return `\x1b[48;2;${r};${g};${b}m`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function hexToFg(hex: string): string {
|
|
62
|
+
if (!/^#[0-9a-fA-F]{6}$/.test(hex)) return "";
|
|
63
|
+
const r = Number.parseInt(hex.slice(1, 3), 16);
|
|
64
|
+
const g = Number.parseInt(hex.slice(3, 5), 16);
|
|
65
|
+
const b = Number.parseInt(hex.slice(5, 7), 16);
|
|
66
|
+
return `\x1b[38;2;${r};${g};${b}m`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const dc = pixConfig().pretty.diff;
|
|
70
|
+
|
|
52
71
|
// Subtle diff backgrounds — muted tones to let syntax fg shine through.
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
72
|
+
// Precedence: env → pix.json → hardcoded default
|
|
73
|
+
const BG_ADD = envBg("DIFF_BG_ADD", hexToBg(dc.bgAdd) || "\x1b[48;2;22;38;32m");
|
|
74
|
+
const BG_DEL = envBg("DIFF_BG_DEL", hexToBg(dc.bgDel) || "\x1b[48;2;45;25;25m");
|
|
75
|
+
const BG_ADD_W = envBg(
|
|
76
|
+
"DIFF_BG_ADD_HL",
|
|
77
|
+
hexToBg(dc.bgAddHighlight) || "\x1b[48;2;35;75;50m",
|
|
78
|
+
);
|
|
79
|
+
const BG_DEL_W = envBg(
|
|
80
|
+
"DIFF_BG_DEL_HL",
|
|
81
|
+
hexToBg(dc.bgDelHighlight) || "\x1b[48;2;80;35;35m",
|
|
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
|
+
);
|
|
62
100
|
const FG_STRIPE = "\x1b[38;2;40;40;40m"; // diagonal stripes on filler cells
|
|
63
101
|
|
|
64
102
|
const BORDER_BAR = "▌";
|
|
@@ -76,8 +114,11 @@ const MAX_TERM_WIDTH = 210;
|
|
|
76
114
|
|
|
77
115
|
const MAX_PREVIEW_LINES = envInt("PRETTY_MAX_PREVIEW_LINES", 80);
|
|
78
116
|
|
|
79
|
-
const SPLIT_MIN_WIDTH = envInt("DIFF_SPLIT_MIN_WIDTH", 150);
|
|
80
|
-
const SPLIT_MIN_CODE_WIDTH = envInt(
|
|
117
|
+
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
|
+
);
|
|
81
122
|
const SPLIT_MAX_WRAP_RATIO = 0.2;
|
|
82
123
|
const SPLIT_MAX_WRAP_LINES = 8;
|
|
83
124
|
|
|
@@ -464,9 +505,12 @@ function injectBg(
|
|
|
464
505
|
continue;
|
|
465
506
|
}
|
|
466
507
|
}
|
|
467
|
-
while (ri < ranges.length && vis >= ranges[ri][1])
|
|
508
|
+
while (ri < ranges.length && vis >= (ranges[ri] as [number, number])[1])
|
|
509
|
+
ri++;
|
|
468
510
|
const want =
|
|
469
|
-
ri < ranges.length &&
|
|
511
|
+
ri < ranges.length &&
|
|
512
|
+
vis >= (ranges[ri] as [number, number])[0] &&
|
|
513
|
+
vis < (ranges[ri] as [number, number])[1];
|
|
470
514
|
if (want !== inHL) {
|
|
471
515
|
inHL = want;
|
|
472
516
|
out += inHL ? hlBg : baseBg;
|
|
@@ -497,6 +541,12 @@ function plainWordDiff(
|
|
|
497
541
|
return { old: o, new: n };
|
|
498
542
|
}
|
|
499
543
|
|
|
544
|
+
/** Type-safe index into an array that noUncheckedIndexedAccess marks as T|undefined.
|
|
545
|
+
* Only call when the index is provably in-bounds (loop condition, length check, etc.). */
|
|
546
|
+
function at<T>(arr: T[], i: number): T {
|
|
547
|
+
return arr[i] as T;
|
|
548
|
+
}
|
|
549
|
+
|
|
500
550
|
// ---------------------------------------------------------------------------
|
|
501
551
|
// Split-vs-unified decision
|
|
502
552
|
// ---------------------------------------------------------------------------
|
|
@@ -605,7 +655,7 @@ export async function renderUnified(
|
|
|
605
655
|
}
|
|
606
656
|
|
|
607
657
|
while (idx < vis.length) {
|
|
608
|
-
const l = vis
|
|
658
|
+
const l = at(vis, idx);
|
|
609
659
|
|
|
610
660
|
if (l.type === "sep") {
|
|
611
661
|
const gap = l.newNum;
|
|
@@ -638,29 +688,35 @@ export async function renderUnified(
|
|
|
638
688
|
}
|
|
639
689
|
|
|
640
690
|
const dels: Array<{ l: DiffLine; hl: string }> = [];
|
|
641
|
-
while (idx < vis.length
|
|
642
|
-
|
|
691
|
+
while (idx < vis.length) {
|
|
692
|
+
const entry = at(vis, idx);
|
|
693
|
+
if (entry.type !== "del") break;
|
|
694
|
+
dels.push({ l: entry, hl: oldHL[oI] ?? entry.content });
|
|
643
695
|
oI++;
|
|
644
696
|
idx++;
|
|
645
697
|
}
|
|
646
698
|
const adds: Array<{ l: DiffLine; hl: string }> = [];
|
|
647
|
-
while (idx < vis.length
|
|
648
|
-
|
|
699
|
+
while (idx < vis.length) {
|
|
700
|
+
const entry = at(vis, idx);
|
|
701
|
+
if (entry.type !== "add") break;
|
|
702
|
+
adds.push({ l: entry, hl: newHL[nI] ?? entry.content });
|
|
649
703
|
nI++;
|
|
650
704
|
idx++;
|
|
651
705
|
}
|
|
652
706
|
|
|
653
707
|
const isPaired = dels.length === 1 && adds.length === 1;
|
|
654
708
|
const wd = isPaired
|
|
655
|
-
? wordDiffAnalysis(dels
|
|
709
|
+
? wordDiffAnalysis(at(dels, 0).l.content, at(adds, 0).l.content)
|
|
656
710
|
: null;
|
|
657
711
|
const wdBalanced = wd && wd.oldRanges.length > 0 && wd.newRanges.length > 0;
|
|
658
712
|
|
|
659
713
|
if (isPaired && wdBalanced && wd.similarity >= WORD_DIFF_MIN_SIM && canHL) {
|
|
660
|
-
const
|
|
661
|
-
const
|
|
714
|
+
const del0 = at(dels, 0);
|
|
715
|
+
const add0 = at(adds, 0);
|
|
716
|
+
const delBody = injectBg(del0.hl, wd.oldRanges, BG_DEL, BG_DEL_W);
|
|
717
|
+
const addBody = injectBg(add0.hl, wd.newRanges, BG_ADD, BG_ADD_W);
|
|
662
718
|
emitRow(
|
|
663
|
-
|
|
719
|
+
del0.l.oldNum,
|
|
664
720
|
"-",
|
|
665
721
|
BG_GUTTER_DEL,
|
|
666
722
|
`${dc.fgDel}${BOLD}`,
|
|
@@ -668,7 +724,7 @@ export async function renderUnified(
|
|
|
668
724
|
BG_DEL,
|
|
669
725
|
);
|
|
670
726
|
emitRow(
|
|
671
|
-
|
|
727
|
+
add0.l.newNum,
|
|
672
728
|
"+",
|
|
673
729
|
BG_GUTTER_ADD,
|
|
674
730
|
`${dc.fgAdd}${BOLD}`,
|
|
@@ -683,9 +739,11 @@ export async function renderUnified(
|
|
|
683
739
|
wd.similarity >= WORD_DIFF_MIN_SIM &&
|
|
684
740
|
!canHL
|
|
685
741
|
) {
|
|
686
|
-
const
|
|
742
|
+
const del0 = at(dels, 0);
|
|
743
|
+
const add0 = at(adds, 0);
|
|
744
|
+
const pwd = plainWordDiff(del0.l.content, add0.l.content);
|
|
687
745
|
emitRow(
|
|
688
|
-
|
|
746
|
+
del0.l.oldNum,
|
|
689
747
|
"-",
|
|
690
748
|
BG_GUTTER_DEL,
|
|
691
749
|
`${dc.fgDel}${BOLD}`,
|
|
@@ -693,7 +751,7 @@ export async function renderUnified(
|
|
|
693
751
|
BG_DEL,
|
|
694
752
|
);
|
|
695
753
|
emitRow(
|
|
696
|
-
|
|
754
|
+
add0.l.newNum,
|
|
697
755
|
"+",
|
|
698
756
|
BG_GUTTER_ADD,
|
|
699
757
|
`${dc.fgAdd}${BOLD}`,
|
|
@@ -755,7 +813,7 @@ export async function renderSplit(
|
|
|
755
813
|
const rows: Row[] = [];
|
|
756
814
|
let i = 0;
|
|
757
815
|
while (i < diff.lines.length) {
|
|
758
|
-
const l = diff.lines
|
|
816
|
+
const l = at(diff.lines, i);
|
|
759
817
|
if (l.type === "sep" || l.type === "ctx") {
|
|
760
818
|
rows.push({ left: l, right: l });
|
|
761
819
|
i++;
|
|
@@ -763,12 +821,16 @@ export async function renderSplit(
|
|
|
763
821
|
}
|
|
764
822
|
const dels: DiffLine[] = [];
|
|
765
823
|
const adds: DiffLine[] = [];
|
|
766
|
-
while (i < diff.lines.length
|
|
767
|
-
|
|
824
|
+
while (i < diff.lines.length) {
|
|
825
|
+
const entry = at(diff.lines, i);
|
|
826
|
+
if (entry.type !== "del") break;
|
|
827
|
+
dels.push(entry);
|
|
768
828
|
i++;
|
|
769
829
|
}
|
|
770
|
-
while (i < diff.lines.length
|
|
771
|
-
|
|
830
|
+
while (i < diff.lines.length) {
|
|
831
|
+
const entry = at(diff.lines, i);
|
|
832
|
+
if (entry.type !== "add") break;
|
|
833
|
+
adds.push(entry);
|
|
772
834
|
i++;
|
|
773
835
|
}
|
|
774
836
|
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
|
@@ -14,8 +14,13 @@
|
|
|
14
14
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
15
15
|
import { dirname, join } from "node:path";
|
|
16
16
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
17
|
+
import { pixConfig } from "@xynogen/pix-data/pix-config";
|
|
17
18
|
import { ICON_MODES, type IconMode, setIconMode } from "./icon-catalog.js";
|
|
18
19
|
|
|
20
|
+
function isIconMode(m: string): m is IconMode {
|
|
21
|
+
return (ICON_MODES as readonly string[]).includes(m);
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
function statePath(): string {
|
|
20
25
|
return join(getAgentDir(), "pretty.json");
|
|
21
26
|
}
|
|
@@ -27,9 +32,8 @@ export function loadIconMode(): IconMode | undefined {
|
|
|
27
32
|
if (!existsSync(p)) return undefined;
|
|
28
33
|
const raw = JSON.parse(readFileSync(p, "utf-8")) as { icons?: string };
|
|
29
34
|
const mode = raw?.icons;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
: undefined;
|
|
35
|
+
if (mode == null) return undefined;
|
|
36
|
+
return isIconMode(mode) ? mode : undefined;
|
|
33
37
|
} catch {
|
|
34
38
|
return undefined;
|
|
35
39
|
}
|
|
@@ -60,8 +64,16 @@ export function saveIconMode(mode: IconMode): void {
|
|
|
60
64
|
/**
|
|
61
65
|
* Apply the persisted mode (if any) to the catalog. Called once at extension
|
|
62
66
|
* load so the env-seeded default is overridden by the user's saved choice.
|
|
67
|
+
*
|
|
68
|
+
* Precedence: env PRETTY_ICONS → pretty.json → pix.json pretty.icons → default ("nerd")
|
|
63
69
|
*/
|
|
64
70
|
export function initIconMode(): void {
|
|
65
71
|
const saved = loadIconMode();
|
|
66
|
-
if (saved)
|
|
72
|
+
if (saved) {
|
|
73
|
+
setIconMode(saved);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// No persisted choice — try pix.json
|
|
77
|
+
const pixIcons = pixConfig().pretty.icons;
|
|
78
|
+
if (pixIcons && isIconMode(pixIcons)) setIconMode(pixIcons);
|
|
67
79
|
}
|
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}`;
|