@pi-archimedes/diff 2.8.0 → 2.9.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 +2 -1
- package/package.json +3 -3
- package/src/index.ts +11 -2
- package/src/render/shared.test.ts +110 -0
- package/src/render/shared.ts +21 -9
package/README.md
CHANGED
|
@@ -43,13 +43,14 @@ The renderer displays `edit` and `write` tool changes in the tool UI, including
|
|
|
43
43
|
|
|
44
44
|
## Appearance
|
|
45
45
|
|
|
46
|
-
Standalone, the renderer runs on fixed defaults — the `github-dark` theme, a 150-column minimum for split view,
|
|
46
|
+
Standalone, the renderer runs on fixed defaults — the `github-dark` theme, a 150-column minimum for split view, 60 code columns per side, and line-wrap safety checks enabled — and it does **not** read settings, because standalone has no config reader. In the [suite](https://github.com/danielcherubini/pi-archimedes), its `archimedes.diff` namespace supplies the settings through the suite's config reader:
|
|
47
47
|
|
|
48
48
|
| Setting | Type | Default | Description |
|
|
49
49
|
|---------|------|---------|-------------|
|
|
50
50
|
| `diffTheme` | string | `github-dark` | Shiki syntax-highlighting theme |
|
|
51
51
|
| `diffSplitMinWidth` | number | `150` | Minimum terminal columns for split view (≥ 100) |
|
|
52
52
|
| `diffSplitMinCodeWidth` | number | `60` | Minimum code columns per side in split view (≥ 30) |
|
|
53
|
+
| `diffSplitWrapCheck` | boolean | `true` | Fall back to unified view when lines wrap (>20% or ≥8 lines) |
|
|
53
54
|
|
|
54
55
|
The palette is derived from the chosen Shiki theme; it does **not** automatically pick up Pi's active theme colours. If you want a different look, pick a closer theme rather than expecting a live match.
|
|
55
56
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-archimedes/diff",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.1",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/danielcherubini/pi-archimedes.git"
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"@earendil-works/pi-tui": ">=0.1.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
32
|
-
"@earendil-works/pi-tui": "^0.
|
|
31
|
+
"@earendil-works/pi-coding-agent": "^0.87.0",
|
|
32
|
+
"@earendil-works/pi-tui": "^0.87.0",
|
|
33
33
|
"@types/diff": "^7.0.2",
|
|
34
34
|
"typescript": "^6.0.0"
|
|
35
35
|
},
|
package/src/index.ts
CHANGED
|
@@ -29,12 +29,14 @@ export interface DiffConfig {
|
|
|
29
29
|
diffTheme: string;
|
|
30
30
|
diffSplitMinWidth: number;
|
|
31
31
|
diffSplitMinCodeWidth: number;
|
|
32
|
+
diffSplitWrapCheck?: boolean;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
const DEFAULT_DIFF_CONFIG: DiffConfig = {
|
|
35
36
|
diffTheme: "github-dark",
|
|
36
37
|
diffSplitMinWidth: 150,
|
|
37
38
|
diffSplitMinCodeWidth: 60,
|
|
39
|
+
diffSplitWrapCheck: true,
|
|
38
40
|
};
|
|
39
41
|
|
|
40
42
|
let _readConfig: () => DiffConfig = () => DEFAULT_DIFF_CONFIG;
|
|
@@ -44,8 +46,8 @@ function getConfig(): DiffConfig { return _readConfig(); }
|
|
|
44
46
|
// Settings
|
|
45
47
|
// ---------------------------------------------------------------------------
|
|
46
48
|
|
|
47
|
-
export function getDiffSettingsItems(): SettingItem[] {
|
|
48
|
-
const config = getConfig();
|
|
49
|
+
export function getDiffSettingsItems(overrideConfig?: DiffConfig): SettingItem[] {
|
|
50
|
+
const config = overrideConfig ?? getConfig();
|
|
49
51
|
return [
|
|
50
52
|
{
|
|
51
53
|
id: "diffTheme",
|
|
@@ -65,6 +67,13 @@ export function getDiffSettingsItems(): SettingItem[] {
|
|
|
65
67
|
description: "Minimum code column width for split diff view",
|
|
66
68
|
currentValue: String(config.diffSplitMinCodeWidth),
|
|
67
69
|
},
|
|
70
|
+
{
|
|
71
|
+
id: "diffSplitWrapCheck",
|
|
72
|
+
label: "Split Wrap Check",
|
|
73
|
+
description: "Fall back to unified diff if lines wrap",
|
|
74
|
+
currentValue: (config.diffSplitWrapCheck ?? true) ? "On" : "Off",
|
|
75
|
+
values: ["On", "Off"],
|
|
76
|
+
},
|
|
68
77
|
];
|
|
69
78
|
}
|
|
70
79
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
+
import { parseDiff } from "../core/diff.js";
|
|
3
|
+
import {
|
|
4
|
+
shouldUseSplit,
|
|
5
|
+
setConfigGetter,
|
|
6
|
+
getConfig,
|
|
7
|
+
adaptiveWrapRows,
|
|
8
|
+
wrapAnsi,
|
|
9
|
+
} from "./shared.js";
|
|
10
|
+
|
|
11
|
+
describe("adaptiveWrapRows", () => {
|
|
12
|
+
it("returns correct row limits at width boundaries", () => {
|
|
13
|
+
expect(adaptiveWrapRows(44)).toBe(2);
|
|
14
|
+
expect(adaptiveWrapRows(45)).toBe(4);
|
|
15
|
+
expect(adaptiveWrapRows(79)).toBe(4);
|
|
16
|
+
expect(adaptiveWrapRows(80)).toBe(5);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("wrapAnsi", () => {
|
|
21
|
+
it("wraps lines without exceeding max rows", () => {
|
|
22
|
+
const str = "a".repeat(100);
|
|
23
|
+
// w=44 -> adaptiveWrapRows=2
|
|
24
|
+
const rows = wrapAnsi(str, 44);
|
|
25
|
+
expect(rows).toHaveLength(2);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("wraps up to the adaptive row limits and emits › when truncated on maxRows", () => {
|
|
29
|
+
// w=80 -> maxRows=5; input requires > 5 rows
|
|
30
|
+
const longStr = "x".repeat(80 * 6);
|
|
31
|
+
const rows = wrapAnsi(longStr, 80);
|
|
32
|
+
expect(rows).toHaveLength(5);
|
|
33
|
+
expect(rows[4]).toContain("›");
|
|
34
|
+
|
|
35
|
+
// w=44 -> maxRows=2; input requires > 2 rows
|
|
36
|
+
const narrowLongStr = "y".repeat(44 * 3);
|
|
37
|
+
const narrowRows = wrapAnsi(narrowLongStr, 44);
|
|
38
|
+
expect(narrowRows).toHaveLength(2);
|
|
39
|
+
expect(narrowRows[1]).toContain("›");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("does not emit › when content fits within maxRows", () => {
|
|
43
|
+
const str = "z".repeat(80 * 3);
|
|
44
|
+
const rows = wrapAnsi(str, 80);
|
|
45
|
+
expect(rows).toHaveLength(3);
|
|
46
|
+
expect(rows[2]).not.toContain("›");
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("render shared / shouldUseSplit", () => {
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
setConfigGetter(() => ({
|
|
53
|
+
diffSplitMinWidth: 150,
|
|
54
|
+
diffSplitMinCodeWidth: 60,
|
|
55
|
+
diffSplitWrapCheck: true,
|
|
56
|
+
}));
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("returns false for empty diff", () => {
|
|
60
|
+
const diff = parseDiff("", "");
|
|
61
|
+
expect(shouldUseSplit(diff, 200)).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("returns false if terminal width < diffSplitMinWidth", () => {
|
|
65
|
+
const old = "const x = 1;\n";
|
|
66
|
+
const updated = "const x = 2;\n";
|
|
67
|
+
const diff = parseDiff(old, updated);
|
|
68
|
+
expect(shouldUseSplit(diff, 140)).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("returns false if available code column width < diffSplitMinCodeWidth", () => {
|
|
72
|
+
setConfigGetter(() => ({
|
|
73
|
+
diffSplitMinWidth: 100,
|
|
74
|
+
diffSplitMinCodeWidth: 60,
|
|
75
|
+
diffSplitWrapCheck: true,
|
|
76
|
+
}));
|
|
77
|
+
const old = "const x = 1;\n";
|
|
78
|
+
const updated = "const x = 2;\n";
|
|
79
|
+
const diff = parseDiff(old, updated);
|
|
80
|
+
// With tw=110, half=54, nw=2, gw=7, cw=47 < 60 -> false
|
|
81
|
+
expect(shouldUseSplit(diff, 110)).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("returns true for normal short lines when terminal width and code width fit", () => {
|
|
85
|
+
const old = "const x = 1;\n";
|
|
86
|
+
const updated = "const x = 2;\n";
|
|
87
|
+
const diff = parseDiff(old, updated);
|
|
88
|
+
expect(shouldUseSplit(diff, 200)).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("falls back to false when lines wrap and diffSplitWrapCheck is true", () => {
|
|
92
|
+
const longLineOld = "const longLine = '" + "a".repeat(150) + "';\n";
|
|
93
|
+
const longLineNew = "const longLine = '" + "b".repeat(150) + "';\n";
|
|
94
|
+
const diff = parseDiff(longLineOld, longLineNew);
|
|
95
|
+
// In a 200 col terminal, cw ~ 92, line length > 150, all lines wrap (ratio = 1.0 >= 0.2)
|
|
96
|
+
expect(shouldUseSplit(diff, 200)).toBe(false);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("returns true when lines wrap but diffSplitWrapCheck is false", () => {
|
|
100
|
+
setConfigGetter(() => ({
|
|
101
|
+
diffSplitMinWidth: 150,
|
|
102
|
+
diffSplitMinCodeWidth: 60,
|
|
103
|
+
diffSplitWrapCheck: false,
|
|
104
|
+
}));
|
|
105
|
+
const longLineOld = "const longLine = '" + "a".repeat(150) + "';\n";
|
|
106
|
+
const longLineNew = "const longLine = '" + "b".repeat(150) + "';\n";
|
|
107
|
+
const diff = parseDiff(longLineOld, longLineNew);
|
|
108
|
+
expect(shouldUseSplit(diff, 200)).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
});
|
package/src/render/shared.ts
CHANGED
|
@@ -14,22 +14,32 @@ export const MAX_HL_CHARS = 80_000;
|
|
|
14
14
|
export const WORD_DIFF_MIN_SIM = 0.15;
|
|
15
15
|
const SPLIT_MAX_WRAP_RATIO = 0.2;
|
|
16
16
|
const SPLIT_MAX_WRAP_LINES = 8;
|
|
17
|
-
const MAX_WRAP_ROWS_WIDE =
|
|
18
|
-
const MAX_WRAP_ROWS_MED =
|
|
19
|
-
const MAX_WRAP_ROWS_NARROW =
|
|
17
|
+
const MAX_WRAP_ROWS_WIDE = 5;
|
|
18
|
+
const MAX_WRAP_ROWS_MED = 4;
|
|
19
|
+
const MAX_WRAP_ROWS_NARROW = 2;
|
|
20
20
|
export const DEFAULT_TERM_WIDTH = 200;
|
|
21
21
|
|
|
22
22
|
// ---------------------------------------------------------------------------
|
|
23
23
|
// Config
|
|
24
24
|
// ---------------------------------------------------------------------------
|
|
25
25
|
|
|
26
|
-
export
|
|
26
|
+
export interface DiffRenderConfig {
|
|
27
|
+
diffSplitMinWidth: number;
|
|
28
|
+
diffSplitMinCodeWidth: number;
|
|
29
|
+
diffSplitWrapCheck?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getConfig(): DiffRenderConfig {
|
|
27
33
|
return typeof _getConfig === "function" ? _getConfig() : DEFAULT_CONFIG;
|
|
28
34
|
}
|
|
29
35
|
|
|
30
|
-
const DEFAULT_CONFIG = {
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
const DEFAULT_CONFIG: DiffRenderConfig = {
|
|
37
|
+
diffSplitMinWidth: 150,
|
|
38
|
+
diffSplitMinCodeWidth: 60,
|
|
39
|
+
diffSplitWrapCheck: true,
|
|
40
|
+
};
|
|
41
|
+
let _getConfig: (() => DiffRenderConfig) | undefined;
|
|
42
|
+
export function setConfigGetter(fn: () => DiffRenderConfig): void {
|
|
33
43
|
_getConfig = fn;
|
|
34
44
|
}
|
|
35
45
|
|
|
@@ -38,8 +48,8 @@ export function setConfigGetter(fn: () => { diffSplitMinWidth: number; diffSplit
|
|
|
38
48
|
// ---------------------------------------------------------------------------
|
|
39
49
|
|
|
40
50
|
export function adaptiveWrapRows(w: number): number {
|
|
41
|
-
if (w >=
|
|
42
|
-
if (w >=
|
|
51
|
+
if (w >= 80) return MAX_WRAP_ROWS_WIDE;
|
|
52
|
+
if (w >= 45) return MAX_WRAP_ROWS_MED;
|
|
43
53
|
return MAX_WRAP_ROWS_NARROW;
|
|
44
54
|
}
|
|
45
55
|
|
|
@@ -112,6 +122,8 @@ export function shouldUseSplit(diff: ParsedDiff, tw: number, maxRows = MAX_PREVI
|
|
|
112
122
|
const cw = Math.max(1, half - gw);
|
|
113
123
|
if (cw < cfg.diffSplitMinCodeWidth) return false;
|
|
114
124
|
|
|
125
|
+
if (cfg.diffSplitWrapCheck === false) return true;
|
|
126
|
+
|
|
115
127
|
const vis = diff.lines.slice(0, maxRows);
|
|
116
128
|
let contentLines = 0, wrapCandidates = 0;
|
|
117
129
|
for (const l of vis) {
|