generated from nhcarrigan/template
1ae440659c
## Summary - **Fix git window "Not a git repository" error** — The working directory received from Claude Code is a WSL Linux path (e.g. `/home/naomi/...`), but git commands were being run as native Windows processes with `.current_dir()`. Windows can't resolve WSL paths, causing `git rev-parse --git-dir` to fail. Fixed by routing git commands through `wsl -- git -C <path>` when the working directory starts with `/`. - **Add syntax highlighting and line numbers to diff view** — Replaced the raw `<pre>` block with a proper `DiffViewer` component featuring: - Old/new line number columns with correct tracking across hunks - Colour-coded gutter (`+`/`-`) with green/red row backgrounds - Syntax highlighting via `highlight.js` using the detected file language, respecting all app themes via `--hljs-*` CSS variables - Styled hunk headers and file headers ## New files - `src/lib/utils/diffParser.ts` — pure diff parsing logic - `src/lib/utils/diffParser.test.ts` — 30 tests covering all line types, line number tracking, and language detection - `src/lib/components/DiffViewer.svelte` — the pretty diff viewer component ✨ This pull request was created with help from Hikari~ 🌸 Reviewed-on: #178 Co-authored-by: Hikari <hikari@nhcarrigan.com> Co-committed-by: Hikari <hikari@nhcarrigan.com>
112 lines
2.6 KiB
TypeScript
112 lines
2.6 KiB
TypeScript
export type DiffLineType =
|
|
| "file-header"
|
|
| "hunk-header"
|
|
| "added"
|
|
| "removed"
|
|
| "context"
|
|
| "no-newline";
|
|
|
|
export interface ParsedDiffLine {
|
|
type: DiffLineType;
|
|
content: string;
|
|
oldLineNumber: number | null;
|
|
newLineNumber: number | null;
|
|
}
|
|
|
|
const FILE_HEADER_PREFIXES = [
|
|
"diff ",
|
|
"index ",
|
|
"--- ",
|
|
"+++ ",
|
|
"new file",
|
|
"deleted file",
|
|
"old mode",
|
|
"new mode",
|
|
"rename ",
|
|
"similarity ",
|
|
];
|
|
|
|
export function parseDiff(diffContent: string): ParsedDiffLine[] {
|
|
const result: ParsedDiffLine[] = [];
|
|
let oldLine = 0;
|
|
let newLine = 0;
|
|
|
|
for (const line of diffContent.split("\n")) {
|
|
if (FILE_HEADER_PREFIXES.some((prefix) => line.startsWith(prefix))) {
|
|
result.push({ type: "file-header", content: line, oldLineNumber: null, newLineNumber: null });
|
|
} else if (line.startsWith("@@")) {
|
|
const match = line.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
|
if (match) {
|
|
oldLine = parseInt(match[1], 10);
|
|
newLine = parseInt(match[2], 10);
|
|
}
|
|
result.push({ type: "hunk-header", content: line, oldLineNumber: null, newLineNumber: null });
|
|
} else if (line.startsWith("+")) {
|
|
result.push({
|
|
type: "added",
|
|
content: line.slice(1),
|
|
oldLineNumber: null,
|
|
newLineNumber: newLine++,
|
|
});
|
|
} else if (line.startsWith("-")) {
|
|
result.push({
|
|
type: "removed",
|
|
content: line.slice(1),
|
|
oldLineNumber: oldLine++,
|
|
newLineNumber: null,
|
|
});
|
|
} else if (line.startsWith(" ")) {
|
|
result.push({
|
|
type: "context",
|
|
content: line.slice(1),
|
|
oldLineNumber: oldLine++,
|
|
newLineNumber: newLine++,
|
|
});
|
|
} else if (line === "\\ No newline at end of file") {
|
|
result.push({ type: "no-newline", content: line, oldLineNumber: null, newLineNumber: null });
|
|
}
|
|
// Skip empty trailing lines
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
const EXTENSION_MAP: Record<string, string> = {
|
|
ts: "typescript",
|
|
tsx: "typescript",
|
|
js: "javascript",
|
|
jsx: "javascript",
|
|
rs: "rust",
|
|
py: "python",
|
|
svelte: "xml",
|
|
css: "css",
|
|
scss: "scss",
|
|
less: "less",
|
|
html: "html",
|
|
json: "json",
|
|
md: "markdown",
|
|
toml: "ini",
|
|
yaml: "yaml",
|
|
yml: "yaml",
|
|
sh: "bash",
|
|
bash: "bash",
|
|
go: "go",
|
|
java: "java",
|
|
cpp: "cpp",
|
|
c: "c",
|
|
rb: "ruby",
|
|
php: "php",
|
|
sql: "sql",
|
|
kt: "kotlin",
|
|
swift: "swift",
|
|
cs: "csharp",
|
|
r: "r",
|
|
lua: "lua",
|
|
xml: "xml",
|
|
};
|
|
|
|
export function detectLanguage(filePath: string): string {
|
|
const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
|
|
return EXTENSION_MAP[ext] ?? "plaintext";
|
|
}
|