edit
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
<script lang="ts">
|
||||
import { ArrowRight, X } from "@lucide/svelte";
|
||||
import type { GitCommitComparison, GitDiffFile, FileStatusKind } from "../types";
|
||||
|
||||
type DiffLineKind = "meta" | "hunk" | "add" | "del" | "context";
|
||||
|
||||
interface Props {
|
||||
comparison: GitCommitComparison;
|
||||
selectedDiffPath: string;
|
||||
isBusy: boolean;
|
||||
onClose: () => void;
|
||||
onSelectFile: (file: GitDiffFile) => void;
|
||||
}
|
||||
|
||||
let {
|
||||
comparison,
|
||||
selectedDiffPath = "",
|
||||
isBusy = false,
|
||||
onClose = () => {},
|
||||
onSelectFile = () => {},
|
||||
}: Props = $props();
|
||||
|
||||
function statusLabel(kind: FileStatusKind): string {
|
||||
return kind;
|
||||
}
|
||||
|
||||
function displayDiffFile(file: GitDiffFile): string {
|
||||
return file.old_path ? `${file.old_path} -> ${file.path}` : file.path;
|
||||
}
|
||||
|
||||
function diffLineKind(line: string): DiffLineKind {
|
||||
if (line.startsWith("diff ") || line.startsWith("index ") || line.startsWith("--- ") ||
|
||||
line.startsWith("+++ ") || line.startsWith("new file") || line.startsWith("deleted file") ||
|
||||
line.startsWith("rename ") || line.startsWith("similarity ")) return "meta";
|
||||
if (line.startsWith("@@")) return "hunk";
|
||||
if (line.startsWith("+")) return "add";
|
||||
if (line.startsWith("-")) return "del";
|
||||
return "context";
|
||||
}
|
||||
|
||||
function diffLines(patch: string): { kind: DiffLineKind; text: string }[] {
|
||||
return patch.replace(/\n$/, "").split("\n").map((text) => ({ kind: diffLineKind(text), text }));
|
||||
}
|
||||
|
||||
function stripDiffPathPrefix(value: string): string {
|
||||
const trimmed = value.trim();
|
||||
if (trimmed === "/dev/null") return trimmed;
|
||||
if (trimmed.startsWith("a/") || trimmed.startsWith("b/")) return trimmed.slice(2);
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function patchFilePath(segment: string): string {
|
||||
let plusPath = "";
|
||||
let minusPath = "";
|
||||
for (const line of segment.split("\n")) {
|
||||
if (line.startsWith("+++ ")) plusPath = stripDiffPathPrefix(line.slice(4));
|
||||
else if (line.startsWith("--- ")) minusPath = stripDiffPathPrefix(line.slice(4));
|
||||
else if (line.startsWith("@@")) break;
|
||||
}
|
||||
return (plusPath && plusPath !== "/dev/null") ? plusPath : minusPath;
|
||||
}
|
||||
|
||||
function buildDiffByPath(patch: string): Map<string, string> {
|
||||
const map = new Map<string, string>();
|
||||
if (!patch.trim()) return map;
|
||||
const segments: string[] = [];
|
||||
let current: string[] = [];
|
||||
for (const line of patch.split("\n")) {
|
||||
if (line.startsWith("diff --git ") && current.length > 0) {
|
||||
segments.push(current.join("\n"));
|
||||
current = [];
|
||||
}
|
||||
current.push(line);
|
||||
}
|
||||
if (current.length > 0) segments.push(current.join("\n"));
|
||||
for (const segment of segments) {
|
||||
const path = patchFilePath(segment);
|
||||
if (path) map.set(path, segment);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
let diffByPath = $derived(buildDiffByPath(comparison.patch));
|
||||
let selectedDiffFile = $derived(comparison.files.find((f) => f.path === selectedDiffPath) ?? null);
|
||||
let selectedDiffPatch = $derived(selectedDiffFile ? (diffByPath.get(selectedDiffFile.path) ?? "") : "");
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="dialog-backdrop"
|
||||
role="presentation"
|
||||
onclick={(e) => { if (e.target === e.currentTarget) onClose(); }}
|
||||
>
|
||||
<div class="dialog" role="dialog" aria-modal="true" aria-label="Commit comparison" tabindex="-1">
|
||||
<header class="dialog-header">
|
||||
<div>
|
||||
<span class="eyebrow">Compare</span>
|
||||
<h2 class="dialog-range">
|
||||
<span class="hash">{comparison.from_short}</span>
|
||||
<ArrowRight size={16} aria-hidden="true" />
|
||||
<span class="hash">{comparison.to_short}</span>
|
||||
</h2>
|
||||
</div>
|
||||
<button class="dialog-close" type="button" onclick={onClose} title="Close">
|
||||
<X size={18} aria-hidden="true" />
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{#if comparison.files.length === 0}
|
||||
<div class="blank-state">No differences to show — these versions are identical.</div>
|
||||
{:else}
|
||||
<div class="dialog-body">
|
||||
<aside class="dialog-files" aria-label="Changed files">
|
||||
{#each comparison.files as file (`${file.old_path ?? ""}:${file.path}`)}
|
||||
<button
|
||||
class="dialog-file-row"
|
||||
class:active={selectedDiffPath === file.path}
|
||||
type="button"
|
||||
onclick={() => onSelectFile(file)}
|
||||
title={displayDiffFile(file)}
|
||||
>
|
||||
<span class={`status-badge ${file.status}`}>{statusLabel(file.status)}</span>
|
||||
<strong>{displayDiffFile(file)}</strong>
|
||||
<span class="diff-counts">
|
||||
<span class="adds">+{file.additions}</span>
|
||||
<span class="dels">-{file.deletions}</span>
|
||||
</span>
|
||||
</button>
|
||||
{/each}
|
||||
</aside>
|
||||
|
||||
<div class="dialog-diff" aria-label="File diff">
|
||||
{#if !selectedDiffFile}
|
||||
<div class="blank-state">Select a file to see its changes.</div>
|
||||
{:else if selectedDiffPatch.trim().length === 0}
|
||||
<div class="blank-state">No textual changes for this file.</div>
|
||||
{:else}
|
||||
<pre class="diff-view" aria-label="Unified diff">{#each diffLines(selectedDiffPatch) as line}<span class={`diff-line ${line.kind}`}>{line.text || " "}</span>{/each}</pre>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user