+server.ts
1import { json } from '@sveltejs/kit';
2import { getDiffByMode, parseDiff, getFileContent, getOldRef, getNewRef, getRepoDir } from '$lib/server/vcs';
3import type { DiffMode } from '$lib/types/index';
4import type { DiffFile, DiffScope } from '$lib/types/index';
5import { highlightLines } from '$lib/server/highlighter';
6import { readFile } from 'fs/promises';
7import { join } from 'path';
8import type { RequestHandler } from './$types.ts';
9
10async function applyHighlighting(files: DiffFile[], oldRef: string = 'HEAD', newRef: string | null = null): Promise<void> {
11 for (const file of files) {
12 // Get the full new file content for accurate highlighting
13 let newContent = '';
14 let oldContent = '';
15
16 try {
17 if (newRef) {
18 // Viewing a specific revision (not the working tree)
19 newContent = await getFileContent(file.path, newRef);
20 } else {
21 // Reading the working tree version (new content)
22 newContent = await readFile(join(getRepoDir(), file.path), 'utf-8');
23 }
24 } catch {
25 // File might be deleted
26 }
27
28 try {
29 // Get the old version (HEAD or merge-base for worktree scope)
30 oldContent = await getFileContent(file.path, oldRef);
31 } catch {
32 // File might be new
33 }
34
35 // Highlight both versions
36 const [newHighlighted, oldHighlighted] = await Promise.all([
37 newContent ? highlightLines(newContent, file.language) : Promise.resolve(new Map<number, string>()),
38 oldContent ? highlightLines(oldContent, file.language) : Promise.resolve(new Map<number, string>())
39 ]);
40
41 // Apply highlighted HTML to diff lines
42 for (const hunk of file.hunks) {
43 for (const line of hunk.lines) {
44 if (line.type === 'add' && line.newNumber !== undefined) {
45 line.html = newHighlighted.get(line.newNumber);
46 } else if (line.type === 'del' && line.oldNumber !== undefined) {
47 line.html = oldHighlighted.get(line.oldNumber);
48 } else if (line.type === 'context' && line.newNumber !== undefined) {
49 line.html = newHighlighted.get(line.newNumber);
50 }
51 }
52 }
53 }
54}
55
56const MAX_LINES_PER_FILE = 1500;
57const MAX_LINES_FOR_HIGHLIGHTING = 5000;
58
59function truncateLargeFiles(files: DiffFile[]): void {
60 for (const file of files) {
61 let totalLines = 0;
62 for (const hunk of file.hunks) {
63 totalLines += hunk.lines.length;
64 }
65 file.totalLines = totalLines;
66
67 if (totalLines > MAX_LINES_PER_FILE) {
68 file.truncated = true;
69 // Keep only first N lines across hunks
70 let remaining = MAX_LINES_PER_FILE;
71 for (const hunk of file.hunks) {
72 if (remaining <= 0) {
73 hunk.lines = [];
74 } else if (hunk.lines.length > remaining) {
75 hunk.lines = hunk.lines.slice(0, remaining);
76 }
77 remaining -= hunk.lines.length;
78 }
79 // Remove empty hunks
80 file.hunks = file.hunks.filter((h) => h.lines.length > 0);
81 }
82 }
83}
84
85export const GET: RequestHandler = async ({ url }) => {
86 try {
87 const filePath = url.searchParams.get('file') || undefined;
88 const mode = (url.searchParams.get('mode') || 'full') as DiffMode;
89 const scope = (url.searchParams.get('scope') || 'uncommitted') as DiffScope;
90 const skipTruncation = url.searchParams.get('full') === '1';
91 const raw = await getDiffByMode(mode, filePath, scope);
92 const parsed = parseDiff(raw);
93
94 // Truncate large files before highlighting (unless full load requested)
95 if (!skipTruncation) {
96 truncateLargeFiles(parsed);
97 } else {
98 // Still count total lines for display
99 for (const file of parsed) {
100 let totalLines = 0;
101 for (const hunk of file.hunks) totalLines += hunk.lines.length;
102 file.totalLines = totalLines;
103 }
104 }
105
106 // Determine the old/new refs for syntax highlighting
107 const oldRef = await getOldRef(scope);
108 const newRef = await getNewRef();
109
110 // Skip highlighting for very large files
111 const filesToHighlight = parsed.filter((f) => (f.totalLines || 0) <= MAX_LINES_FOR_HIGHLIGHTING);
112 await applyHighlighting(filesToHighlight, oldRef, newRef);
113
114 return json({ files: parsed, raw });
115 } catch (err) {
116 return json({ error: String(err) }, { status: 500 });
117 }
118};