Files
hikari-desktop/src/lib/utils/diffParser.ts
T
hikari 1ae440659c
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 58s
CI / Lint & Test (push) Successful in 16m33s
CI / Build Linux (push) Successful in 20m56s
CI / Build Windows (cross-compile) (push) Successful in 31m1s
feat: fix git window and add pretty diff viewer (#178)
## 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>
2026-03-06 09:19:16 -08:00

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";
}