@tuicomponents/diff 0.1.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/dist/index.cjs +400 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +417 -0
- package/dist/index.d.ts +417 -0
- package/dist/index.js +371 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
// src/diff.ts
|
|
2
|
+
import {
|
|
3
|
+
BaseTuiComponent,
|
|
4
|
+
measureLines,
|
|
5
|
+
registry
|
|
6
|
+
} from "@tuicomponents/core";
|
|
7
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
8
|
+
|
|
9
|
+
// src/schema.ts
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
var lineTypeSchema = z.enum([
|
|
12
|
+
"addition",
|
|
13
|
+
// Added line (+)
|
|
14
|
+
"deletion",
|
|
15
|
+
// Removed line (-)
|
|
16
|
+
"context"
|
|
17
|
+
// Unchanged context line
|
|
18
|
+
]);
|
|
19
|
+
var diffLineSchema = z.object({
|
|
20
|
+
/**
|
|
21
|
+
* Type of the line.
|
|
22
|
+
*/
|
|
23
|
+
type: lineTypeSchema,
|
|
24
|
+
/**
|
|
25
|
+
* Content of the line.
|
|
26
|
+
*/
|
|
27
|
+
content: z.string(),
|
|
28
|
+
/**
|
|
29
|
+
* Optional line number in the old file (for deletions and context).
|
|
30
|
+
*/
|
|
31
|
+
oldLineNumber: z.number().int().positive().optional(),
|
|
32
|
+
/**
|
|
33
|
+
* Optional line number in the new file (for additions and context).
|
|
34
|
+
*/
|
|
35
|
+
newLineNumber: z.number().int().positive().optional()
|
|
36
|
+
});
|
|
37
|
+
var hunkSchema = z.object({
|
|
38
|
+
/**
|
|
39
|
+
* Hunk header information.
|
|
40
|
+
*/
|
|
41
|
+
header: z.object({
|
|
42
|
+
oldStart: z.number().int().nonnegative(),
|
|
43
|
+
oldCount: z.number().int().nonnegative(),
|
|
44
|
+
newStart: z.number().int().nonnegative(),
|
|
45
|
+
newCount: z.number().int().nonnegative()
|
|
46
|
+
}).optional(),
|
|
47
|
+
/**
|
|
48
|
+
* Lines in the hunk.
|
|
49
|
+
*/
|
|
50
|
+
lines: z.array(diffLineSchema)
|
|
51
|
+
});
|
|
52
|
+
var markerStyleSchema = z.enum([
|
|
53
|
+
"symbol",
|
|
54
|
+
// +/- markers
|
|
55
|
+
"word",
|
|
56
|
+
// ADD/DEL markers
|
|
57
|
+
"none"
|
|
58
|
+
// No markers, rely on color/indentation
|
|
59
|
+
]);
|
|
60
|
+
var diffInputSchema = z.object({
|
|
61
|
+
/**
|
|
62
|
+
* Array of hunks containing diff lines.
|
|
63
|
+
*/
|
|
64
|
+
hunks: z.array(hunkSchema),
|
|
65
|
+
/**
|
|
66
|
+
* Optional old file name for header.
|
|
67
|
+
*/
|
|
68
|
+
oldFile: z.string().optional(),
|
|
69
|
+
/**
|
|
70
|
+
* Optional new file name for header.
|
|
71
|
+
*/
|
|
72
|
+
newFile: z.string().optional(),
|
|
73
|
+
/**
|
|
74
|
+
* Show line numbers.
|
|
75
|
+
* @default false
|
|
76
|
+
*/
|
|
77
|
+
showLineNumbers: z.boolean().default(false),
|
|
78
|
+
/**
|
|
79
|
+
* Style for line markers.
|
|
80
|
+
* @default "symbol"
|
|
81
|
+
*/
|
|
82
|
+
markerStyle: markerStyleSchema.default("symbol"),
|
|
83
|
+
/**
|
|
84
|
+
* Show hunk headers (@@ -x,y +a,b @@).
|
|
85
|
+
* @default true
|
|
86
|
+
*/
|
|
87
|
+
showHunkHeaders: z.boolean().default(true),
|
|
88
|
+
/**
|
|
89
|
+
* Number of context lines to show around changes.
|
|
90
|
+
* This is informational only - the component renders what you provide.
|
|
91
|
+
*/
|
|
92
|
+
contextLines: z.number().int().nonnegative().optional()
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// src/diff.ts
|
|
96
|
+
function getMarker(type, style) {
|
|
97
|
+
if (style === "none") {
|
|
98
|
+
return "";
|
|
99
|
+
}
|
|
100
|
+
if (style === "word") {
|
|
101
|
+
switch (type) {
|
|
102
|
+
case "addition":
|
|
103
|
+
return "ADD ";
|
|
104
|
+
case "deletion":
|
|
105
|
+
return "DEL ";
|
|
106
|
+
case "context":
|
|
107
|
+
return " ";
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
switch (type) {
|
|
111
|
+
case "addition":
|
|
112
|
+
return "+";
|
|
113
|
+
case "deletion":
|
|
114
|
+
return "-";
|
|
115
|
+
case "context":
|
|
116
|
+
return " ";
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function formatLineNumbers(line, maxOldWidth, maxNewWidth) {
|
|
120
|
+
const oldNum = line.oldLineNumber !== void 0 ? String(line.oldLineNumber).padStart(maxOldWidth, " ") : " ".repeat(maxOldWidth);
|
|
121
|
+
const newNum = line.newLineNumber !== void 0 ? String(line.newLineNumber).padStart(maxNewWidth, " ") : " ".repeat(maxNewWidth);
|
|
122
|
+
return `${oldNum} ${newNum}`;
|
|
123
|
+
}
|
|
124
|
+
function colorLine(text, type, theme) {
|
|
125
|
+
if (!theme) {
|
|
126
|
+
return text;
|
|
127
|
+
}
|
|
128
|
+
switch (type) {
|
|
129
|
+
case "addition":
|
|
130
|
+
return theme.semantic.added(text);
|
|
131
|
+
case "deletion":
|
|
132
|
+
return theme.semantic.removed(text);
|
|
133
|
+
case "context":
|
|
134
|
+
return text;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function colorHeader(text, theme) {
|
|
138
|
+
if (!theme) {
|
|
139
|
+
return text;
|
|
140
|
+
}
|
|
141
|
+
return theme.semantic.secondary(text);
|
|
142
|
+
}
|
|
143
|
+
var DiffComponent = class extends BaseTuiComponent {
|
|
144
|
+
metadata = {
|
|
145
|
+
name: "diff",
|
|
146
|
+
description: "Renders unified diff format with additions and deletions",
|
|
147
|
+
version: "0.1.0",
|
|
148
|
+
examples: [
|
|
149
|
+
{
|
|
150
|
+
name: "basic",
|
|
151
|
+
description: "Simple diff with additions and deletions",
|
|
152
|
+
input: {
|
|
153
|
+
hunks: [
|
|
154
|
+
{
|
|
155
|
+
lines: [
|
|
156
|
+
{ type: "context", content: "function greet() {" },
|
|
157
|
+
{ type: "deletion", content: ' console.log("Hello");' },
|
|
158
|
+
{
|
|
159
|
+
type: "addition",
|
|
160
|
+
content: ' console.log("Hello, World!");'
|
|
161
|
+
},
|
|
162
|
+
{ type: "context", content: "}" }
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: "with-file-headers",
|
|
170
|
+
description: "Diff with file name headers",
|
|
171
|
+
input: {
|
|
172
|
+
oldFile: "a/src/main.ts",
|
|
173
|
+
newFile: "b/src/main.ts",
|
|
174
|
+
hunks: [
|
|
175
|
+
{
|
|
176
|
+
header: { oldStart: 1, oldCount: 4, newStart: 1, newCount: 5 },
|
|
177
|
+
lines: [
|
|
178
|
+
{ type: "context", content: "import { foo } from './foo';" },
|
|
179
|
+
{ type: "addition", content: "import { bar } from './bar';" },
|
|
180
|
+
{ type: "context", content: "" },
|
|
181
|
+
{ type: "context", content: "export function main() {" },
|
|
182
|
+
{ type: "deletion", content: " foo();" },
|
|
183
|
+
{ type: "addition", content: " foo();" },
|
|
184
|
+
{ type: "addition", content: " bar();" },
|
|
185
|
+
{ type: "context", content: "}" }
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: "with-line-numbers",
|
|
193
|
+
description: "Diff with line numbers shown",
|
|
194
|
+
input: {
|
|
195
|
+
showLineNumbers: true,
|
|
196
|
+
hunks: [
|
|
197
|
+
{
|
|
198
|
+
lines: [
|
|
199
|
+
{
|
|
200
|
+
type: "context",
|
|
201
|
+
content: "const x = 1;",
|
|
202
|
+
oldLineNumber: 10,
|
|
203
|
+
newLineNumber: 10
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
type: "deletion",
|
|
207
|
+
content: "const y = 2;",
|
|
208
|
+
oldLineNumber: 11
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
type: "addition",
|
|
212
|
+
content: "const y = 3;",
|
|
213
|
+
newLineNumber: 11
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
type: "context",
|
|
217
|
+
content: "const z = x + y;",
|
|
218
|
+
oldLineNumber: 12,
|
|
219
|
+
newLineNumber: 12
|
|
220
|
+
}
|
|
221
|
+
]
|
|
222
|
+
}
|
|
223
|
+
]
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
name: "word-markers",
|
|
228
|
+
description: "Using word markers instead of symbols",
|
|
229
|
+
input: {
|
|
230
|
+
markerStyle: "word",
|
|
231
|
+
hunks: [
|
|
232
|
+
{
|
|
233
|
+
lines: [
|
|
234
|
+
{ type: "context", content: "First line" },
|
|
235
|
+
{ type: "deletion", content: "Old line" },
|
|
236
|
+
{ type: "addition", content: "New line" },
|
|
237
|
+
{ type: "context", content: "Last line" }
|
|
238
|
+
]
|
|
239
|
+
}
|
|
240
|
+
]
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: "multiple-hunks",
|
|
245
|
+
description: "Diff with multiple hunks",
|
|
246
|
+
input: {
|
|
247
|
+
oldFile: "config.json",
|
|
248
|
+
newFile: "config.json",
|
|
249
|
+
hunks: [
|
|
250
|
+
{
|
|
251
|
+
header: { oldStart: 2, oldCount: 3, newStart: 2, newCount: 3 },
|
|
252
|
+
lines: [
|
|
253
|
+
{ type: "context", content: ' "name": "my-app",' },
|
|
254
|
+
{ type: "deletion", content: ' "version": "1.0.0",' },
|
|
255
|
+
{ type: "addition", content: ' "version": "1.1.0",' },
|
|
256
|
+
{ type: "context", content: ' "description": "...",' }
|
|
257
|
+
]
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
header: { oldStart: 10, oldCount: 2, newStart: 10, newCount: 3 },
|
|
261
|
+
lines: [
|
|
262
|
+
{ type: "context", content: ' "dependencies": {' },
|
|
263
|
+
{ type: "addition", content: ' "lodash": "^4.17.21",' },
|
|
264
|
+
{ type: "context", content: ' "express": "^4.18.0"' }
|
|
265
|
+
]
|
|
266
|
+
}
|
|
267
|
+
]
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
]
|
|
271
|
+
};
|
|
272
|
+
schema = diffInputSchema;
|
|
273
|
+
/**
|
|
274
|
+
* Override getJsonSchema to use direct schema generation.
|
|
275
|
+
*/
|
|
276
|
+
getJsonSchema() {
|
|
277
|
+
return zodToJsonSchema(this.schema, {
|
|
278
|
+
name: this.metadata.name,
|
|
279
|
+
$refStrategy: "none"
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
render(input, context) {
|
|
283
|
+
const parsed = this.schema.parse(input);
|
|
284
|
+
const theme = context.theme;
|
|
285
|
+
if (parsed.hunks.length === 0) {
|
|
286
|
+
return { output: "", actualWidth: 0, lineCount: 0 };
|
|
287
|
+
}
|
|
288
|
+
const lines = [];
|
|
289
|
+
if (parsed.oldFile) {
|
|
290
|
+
lines.push(colorHeader(`--- ${parsed.oldFile}`, theme));
|
|
291
|
+
}
|
|
292
|
+
if (parsed.newFile) {
|
|
293
|
+
lines.push(colorHeader(`+++ ${parsed.newFile}`, theme));
|
|
294
|
+
}
|
|
295
|
+
const { maxOldWidth, maxNewWidth } = this.calculateLineNumberWidths(
|
|
296
|
+
parsed.hunks
|
|
297
|
+
);
|
|
298
|
+
for (const hunk of parsed.hunks) {
|
|
299
|
+
this.renderHunk(hunk, parsed, maxOldWidth, maxNewWidth, lines, theme);
|
|
300
|
+
}
|
|
301
|
+
const output = lines.join("\n");
|
|
302
|
+
const measured = measureLines(output);
|
|
303
|
+
return {
|
|
304
|
+
output,
|
|
305
|
+
actualWidth: measured.maxWidth,
|
|
306
|
+
lineCount: measured.lineCount
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
calculateLineNumberWidths(hunks) {
|
|
310
|
+
let maxOld = 0;
|
|
311
|
+
let maxNew = 0;
|
|
312
|
+
for (const hunk of hunks) {
|
|
313
|
+
for (const line of hunk.lines) {
|
|
314
|
+
if (line.oldLineNumber !== void 0 && line.oldLineNumber > maxOld) {
|
|
315
|
+
maxOld = line.oldLineNumber;
|
|
316
|
+
}
|
|
317
|
+
if (line.newLineNumber !== void 0 && line.newLineNumber > maxNew) {
|
|
318
|
+
maxNew = line.newLineNumber;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
maxOldWidth: Math.max(1, String(maxOld).length),
|
|
324
|
+
maxNewWidth: Math.max(1, String(maxNew).length)
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
renderHunk(hunk, config, maxOldWidth, maxNewWidth, lines, theme) {
|
|
328
|
+
if (config.showHunkHeaders && hunk.header) {
|
|
329
|
+
const { oldStart, oldCount, newStart, newCount } = hunk.header;
|
|
330
|
+
const header = `@@ -${String(oldStart)},${String(oldCount)} +${String(newStart)},${String(newCount)} @@`;
|
|
331
|
+
lines.push(colorHeader(header, theme));
|
|
332
|
+
}
|
|
333
|
+
for (const line of hunk.lines) {
|
|
334
|
+
const renderedLine = this.renderLine(
|
|
335
|
+
line,
|
|
336
|
+
config,
|
|
337
|
+
maxOldWidth,
|
|
338
|
+
maxNewWidth,
|
|
339
|
+
theme
|
|
340
|
+
);
|
|
341
|
+
lines.push(renderedLine);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
renderLine(line, config, maxOldWidth, maxNewWidth, theme) {
|
|
345
|
+
const parts = [];
|
|
346
|
+
if (config.showLineNumbers) {
|
|
347
|
+
parts.push(formatLineNumbers(line, maxOldWidth, maxNewWidth));
|
|
348
|
+
}
|
|
349
|
+
const marker = getMarker(line.type, config.markerStyle);
|
|
350
|
+
if (marker) {
|
|
351
|
+
parts.push(marker);
|
|
352
|
+
}
|
|
353
|
+
parts.push(line.content);
|
|
354
|
+
const rawLine = config.showLineNumbers ? parts.join(" ") : parts.join("");
|
|
355
|
+
return colorLine(rawLine, line.type, theme);
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
function createDiff() {
|
|
359
|
+
return new DiffComponent();
|
|
360
|
+
}
|
|
361
|
+
registry.register(createDiff);
|
|
362
|
+
export {
|
|
363
|
+
DiffComponent,
|
|
364
|
+
createDiff,
|
|
365
|
+
diffInputSchema,
|
|
366
|
+
diffLineSchema,
|
|
367
|
+
hunkSchema,
|
|
368
|
+
lineTypeSchema,
|
|
369
|
+
markerStyleSchema
|
|
370
|
+
};
|
|
371
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/diff.ts","../src/schema.ts"],"sourcesContent":["import {\n BaseTuiComponent,\n type ComponentMetadata,\n type RenderContext,\n type RenderResult,\n type TuiTheme,\n measureLines,\n registry,\n} from \"@tuicomponents/core\";\nimport { zodToJsonSchema } from \"zod-to-json-schema\";\nimport {\n diffInputSchema,\n type DiffInput,\n type DiffInputWithDefaults,\n type DiffLine,\n type Hunk,\n type MarkerStyle,\n} from \"./schema.js\";\n\n/**\n * Get the marker for a line type.\n */\nfunction getMarker(type: DiffLine[\"type\"], style: MarkerStyle): string {\n if (style === \"none\") {\n return \"\";\n }\n\n if (style === \"word\") {\n switch (type) {\n case \"addition\":\n return \"ADD \";\n case \"deletion\":\n return \"DEL \";\n case \"context\":\n return \" \";\n }\n }\n\n // symbol style\n switch (type) {\n case \"addition\":\n return \"+\";\n case \"deletion\":\n return \"-\";\n case \"context\":\n return \" \";\n }\n}\n\n/**\n * Format line numbers for display.\n */\nfunction formatLineNumbers(\n line: DiffLine,\n maxOldWidth: number,\n maxNewWidth: number\n): string {\n const oldNum =\n line.oldLineNumber !== undefined\n ? String(line.oldLineNumber).padStart(maxOldWidth, \" \")\n : \" \".repeat(maxOldWidth);\n\n const newNum =\n line.newLineNumber !== undefined\n ? String(line.newLineNumber).padStart(maxNewWidth, \" \")\n : \" \".repeat(maxNewWidth);\n\n return `${oldNum} ${newNum}`;\n}\n\n/**\n * Apply color to a diff line based on its type.\n */\nfunction colorLine(\n text: string,\n type: DiffLine[\"type\"],\n theme: TuiTheme | undefined\n): string {\n if (!theme) {\n return text;\n }\n\n switch (type) {\n case \"addition\":\n return theme.semantic.added(text);\n case \"deletion\":\n return theme.semantic.removed(text);\n case \"context\":\n return text; // Context lines keep default color\n }\n}\n\n/**\n * Apply color to header text.\n */\nfunction colorHeader(text: string, theme: TuiTheme | undefined): string {\n if (!theme) {\n return text;\n }\n return theme.semantic.secondary(text);\n}\n\n/**\n * Diff component for rendering unified diff output.\n */\nclass DiffComponent extends BaseTuiComponent<\n DiffInput,\n typeof diffInputSchema\n> {\n readonly metadata: ComponentMetadata<DiffInput> = {\n name: \"diff\",\n description: \"Renders unified diff format with additions and deletions\",\n version: \"0.1.0\",\n examples: [\n {\n name: \"basic\",\n description: \"Simple diff with additions and deletions\",\n input: {\n hunks: [\n {\n lines: [\n { type: \"context\", content: \"function greet() {\" },\n { type: \"deletion\", content: ' console.log(\"Hello\");' },\n {\n type: \"addition\",\n content: ' console.log(\"Hello, World!\");',\n },\n { type: \"context\", content: \"}\" },\n ],\n },\n ],\n },\n },\n {\n name: \"with-file-headers\",\n description: \"Diff with file name headers\",\n input: {\n oldFile: \"a/src/main.ts\",\n newFile: \"b/src/main.ts\",\n hunks: [\n {\n header: { oldStart: 1, oldCount: 4, newStart: 1, newCount: 5 },\n lines: [\n { type: \"context\", content: \"import { foo } from './foo';\" },\n { type: \"addition\", content: \"import { bar } from './bar';\" },\n { type: \"context\", content: \"\" },\n { type: \"context\", content: \"export function main() {\" },\n { type: \"deletion\", content: \" foo();\" },\n { type: \"addition\", content: \" foo();\" },\n { type: \"addition\", content: \" bar();\" },\n { type: \"context\", content: \"}\" },\n ],\n },\n ],\n },\n },\n {\n name: \"with-line-numbers\",\n description: \"Diff with line numbers shown\",\n input: {\n showLineNumbers: true,\n hunks: [\n {\n lines: [\n {\n type: \"context\",\n content: \"const x = 1;\",\n oldLineNumber: 10,\n newLineNumber: 10,\n },\n {\n type: \"deletion\",\n content: \"const y = 2;\",\n oldLineNumber: 11,\n },\n {\n type: \"addition\",\n content: \"const y = 3;\",\n newLineNumber: 11,\n },\n {\n type: \"context\",\n content: \"const z = x + y;\",\n oldLineNumber: 12,\n newLineNumber: 12,\n },\n ],\n },\n ],\n },\n },\n {\n name: \"word-markers\",\n description: \"Using word markers instead of symbols\",\n input: {\n markerStyle: \"word\",\n hunks: [\n {\n lines: [\n { type: \"context\", content: \"First line\" },\n { type: \"deletion\", content: \"Old line\" },\n { type: \"addition\", content: \"New line\" },\n { type: \"context\", content: \"Last line\" },\n ],\n },\n ],\n },\n },\n {\n name: \"multiple-hunks\",\n description: \"Diff with multiple hunks\",\n input: {\n oldFile: \"config.json\",\n newFile: \"config.json\",\n hunks: [\n {\n header: { oldStart: 2, oldCount: 3, newStart: 2, newCount: 3 },\n lines: [\n { type: \"context\", content: ' \"name\": \"my-app\",' },\n { type: \"deletion\", content: ' \"version\": \"1.0.0\",' },\n { type: \"addition\", content: ' \"version\": \"1.1.0\",' },\n { type: \"context\", content: ' \"description\": \"...\",' },\n ],\n },\n {\n header: { oldStart: 10, oldCount: 2, newStart: 10, newCount: 3 },\n lines: [\n { type: \"context\", content: ' \"dependencies\": {' },\n { type: \"addition\", content: ' \"lodash\": \"^4.17.21\",' },\n { type: \"context\", content: ' \"express\": \"^4.18.0\"' },\n ],\n },\n ],\n },\n },\n ],\n };\n\n readonly schema = diffInputSchema;\n\n /**\n * Override getJsonSchema to use direct schema generation.\n */\n override getJsonSchema(): object {\n return zodToJsonSchema(this.schema, {\n name: this.metadata.name,\n $refStrategy: \"none\",\n });\n }\n\n render(input: DiffInput, context: RenderContext): RenderResult {\n const parsed: DiffInputWithDefaults = this.schema.parse(input);\n const theme = context.theme;\n\n if (parsed.hunks.length === 0) {\n return { output: \"\", actualWidth: 0, lineCount: 0 };\n }\n\n const lines: string[] = [];\n\n // Render file headers if provided (with muted color)\n if (parsed.oldFile) {\n lines.push(colorHeader(`--- ${parsed.oldFile}`, theme));\n }\n if (parsed.newFile) {\n lines.push(colorHeader(`+++ ${parsed.newFile}`, theme));\n }\n\n // Calculate max line number widths for padding\n const { maxOldWidth, maxNewWidth } = this.calculateLineNumberWidths(\n parsed.hunks\n );\n\n // Render each hunk\n for (const hunk of parsed.hunks) {\n this.renderHunk(hunk, parsed, maxOldWidth, maxNewWidth, lines, theme);\n }\n\n const output = lines.join(\"\\n\");\n const measured = measureLines(output);\n\n return {\n output,\n actualWidth: measured.maxWidth,\n lineCount: measured.lineCount,\n };\n }\n\n private calculateLineNumberWidths(hunks: Hunk[]): {\n maxOldWidth: number;\n maxNewWidth: number;\n } {\n let maxOld = 0;\n let maxNew = 0;\n\n for (const hunk of hunks) {\n for (const line of hunk.lines) {\n if (line.oldLineNumber !== undefined && line.oldLineNumber > maxOld) {\n maxOld = line.oldLineNumber;\n }\n if (line.newLineNumber !== undefined && line.newLineNumber > maxNew) {\n maxNew = line.newLineNumber;\n }\n }\n }\n\n return {\n maxOldWidth: Math.max(1, String(maxOld).length),\n maxNewWidth: Math.max(1, String(maxNew).length),\n };\n }\n\n private renderHunk(\n hunk: Hunk,\n config: DiffInputWithDefaults,\n maxOldWidth: number,\n maxNewWidth: number,\n lines: string[],\n theme: TuiTheme | undefined\n ): void {\n // Render hunk header if enabled and header data provided (with muted color)\n if (config.showHunkHeaders && hunk.header) {\n const { oldStart, oldCount, newStart, newCount } = hunk.header;\n const header = `@@ -${String(oldStart)},${String(oldCount)} +${String(newStart)},${String(newCount)} @@`;\n lines.push(colorHeader(header, theme));\n }\n\n // Render each line\n for (const line of hunk.lines) {\n const renderedLine = this.renderLine(\n line,\n config,\n maxOldWidth,\n maxNewWidth,\n theme\n );\n lines.push(renderedLine);\n }\n }\n\n private renderLine(\n line: DiffLine,\n config: DiffInputWithDefaults,\n maxOldWidth: number,\n maxNewWidth: number,\n theme: TuiTheme | undefined\n ): string {\n const parts: string[] = [];\n\n // Add line numbers if enabled\n if (config.showLineNumbers) {\n parts.push(formatLineNumbers(line, maxOldWidth, maxNewWidth));\n }\n\n // Add marker\n const marker = getMarker(line.type, config.markerStyle);\n if (marker) {\n parts.push(marker);\n }\n\n // Add content\n parts.push(line.content);\n\n // Join with appropriate separators\n const rawLine = config.showLineNumbers ? parts.join(\" \") : parts.join(\"\");\n\n // Apply color based on line type\n return colorLine(rawLine, line.type, theme);\n }\n}\n\n/**\n * Factory function to create a diff component.\n */\nexport function createDiff(): DiffComponent {\n return new DiffComponent();\n}\n\n// Register with global registry\nregistry.register(createDiff);\n\nexport { DiffComponent };\n","import { z } from \"zod\";\n\n/**\n * Type of diff line.\n */\nexport const lineTypeSchema = z.enum([\n \"addition\", // Added line (+)\n \"deletion\", // Removed line (-)\n \"context\", // Unchanged context line\n]);\n\nexport type LineType = z.infer<typeof lineTypeSchema>;\n\n/**\n * Schema for a single diff line.\n */\nexport const diffLineSchema = z.object({\n /**\n * Type of the line.\n */\n type: lineTypeSchema,\n\n /**\n * Content of the line.\n */\n content: z.string(),\n\n /**\n * Optional line number in the old file (for deletions and context).\n */\n oldLineNumber: z.number().int().positive().optional(),\n\n /**\n * Optional line number in the new file (for additions and context).\n */\n newLineNumber: z.number().int().positive().optional(),\n});\n\nexport type DiffLine = z.infer<typeof diffLineSchema>;\n\n/**\n * Schema for a hunk (section of changes).\n */\nexport const hunkSchema = z.object({\n /**\n * Hunk header information.\n */\n header: z\n .object({\n oldStart: z.number().int().nonnegative(),\n oldCount: z.number().int().nonnegative(),\n newStart: z.number().int().nonnegative(),\n newCount: z.number().int().nonnegative(),\n })\n .optional(),\n\n /**\n * Lines in the hunk.\n */\n lines: z.array(diffLineSchema),\n});\n\nexport type Hunk = z.infer<typeof hunkSchema>;\n\n/**\n * Style options for line markers.\n */\nexport const markerStyleSchema = z.enum([\n \"symbol\", // +/- markers\n \"word\", // ADD/DEL markers\n \"none\", // No markers, rely on color/indentation\n]);\n\nexport type MarkerStyle = z.infer<typeof markerStyleSchema>;\n\n/**\n * Schema for diff component input.\n */\nexport const diffInputSchema = z.object({\n /**\n * Array of hunks containing diff lines.\n */\n hunks: z.array(hunkSchema),\n\n /**\n * Optional old file name for header.\n */\n oldFile: z.string().optional(),\n\n /**\n * Optional new file name for header.\n */\n newFile: z.string().optional(),\n\n /**\n * Show line numbers.\n * @default false\n */\n showLineNumbers: z.boolean().default(false),\n\n /**\n * Style for line markers.\n * @default \"symbol\"\n */\n markerStyle: markerStyleSchema.default(\"symbol\"),\n\n /**\n * Show hunk headers (@@ -x,y +a,b @@).\n * @default true\n */\n showHunkHeaders: z.boolean().default(true),\n\n /**\n * Number of context lines to show around changes.\n * This is informational only - the component renders what you provide.\n */\n contextLines: z.number().int().nonnegative().optional(),\n});\n\nexport type DiffInput = z.input<typeof diffInputSchema>;\nexport type DiffInputWithDefaults = z.output<typeof diffInputSchema>;\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tuicomponents/diff",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Diff component for TUI - displays unified diff format",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"tui",
|
|
26
|
+
"terminal",
|
|
27
|
+
"diff",
|
|
28
|
+
"unified"
|
|
29
|
+
],
|
|
30
|
+
"license": "UNLICENSED",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"zod": "^3.25.56",
|
|
33
|
+
"zod-to-json-schema": "^3.24.5",
|
|
34
|
+
"@tuicomponents/core": "0.1.1"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^22.0.0"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsup",
|
|
41
|
+
"typecheck": "tsc --noEmit",
|
|
42
|
+
"lint": "eslint src",
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"test:watch": "vitest",
|
|
45
|
+
"api-report": "api-extractor run",
|
|
46
|
+
"api-report:update": "api-extractor run --local",
|
|
47
|
+
"clean": "rm -rf dist"
|
|
48
|
+
}
|
|
49
|
+
}
|