diff --git a/web/src/lib/components/diff/ConciseDiffView.svelte b/web/src/lib/components/diff/ConciseDiffView.svelte index a0fe7eb..bd1724c 100644 --- a/web/src/lib/components/diff/ConciseDiffView.svelte +++ b/web/src/lib/components/diff/ConciseDiffView.svelte @@ -13,7 +13,6 @@ type SearchSegment, } from "$lib/components/diff/concise-diff-view.svelte"; import Spinner from "$lib/components/Spinner.svelte"; - import { type StructuredPatch } from "diff"; import { onDestroy } from "svelte"; import { type MutableValue } from "$lib/util"; import { box } from "svelte-toolbelt"; @@ -33,7 +32,7 @@ cacheKey, }: ConciseDiffViewProps = $props(); - const parsedPatch: Promise = $derived.by(async () => { + const parsedPatch = $derived.by(() => { if (rawPatchContent !== undefined) { return parseSinglePatch(rawPatchContent); } else if (patch !== undefined) { diff --git a/web/src/lib/components/diff/concise-diff-view.svelte.ts b/web/src/lib/components/diff/concise-diff-view.svelte.ts index a89c861..3ce8ece 100644 --- a/web/src/lib/components/diff/concise-diff-view.svelte.ts +++ b/web/src/lib/components/diff/concise-diff-view.svelte.ts @@ -676,6 +676,19 @@ async function makeHunk( return { lines }; } +export function patchHeaderDiffOnly(patch: StructuredPatch): boolean { + if (patch.hunks.length === 0) { + return false; + } + let onlyHeaderChanges = true; + for (let j = 0; j < patch.hunks.length; j++) { + if (hasNonHeaderChanges(patch.hunks[j].lines)) { + onlyHeaderChanges = false; + } + } + return onlyHeaderChanges; +} + export function hasNonHeaderChanges(contentLines: string[]) { for (const line of contentLines) { if (lineHasNonHeaderChange(line)) { @@ -1013,7 +1026,7 @@ export function parseSinglePatch(rawPatchContent: string): StructuredPatch { export interface ConciseDiffViewProps { rawPatchContent?: string; - patch?: Promise; + patch?: StructuredPatch; syntaxHighlighting?: boolean; syntaxHighlightingTheme?: BundledTheme; @@ -1030,7 +1043,7 @@ export interface ConciseDiffViewProps { } export type ConciseDiffViewStateProps = ReadableBoxedValues<{ - patch: Promise; + patch: StructuredPatch; syntaxHighlighting: boolean; syntaxHighlightingTheme: BundledTheme | undefined; diff --git a/web/src/lib/diff-viewer-multi-file.svelte.ts b/web/src/lib/diff-viewer-multi-file.svelte.ts index 52b9864..05030d5 100644 --- a/web/src/lib/diff-viewer-multi-file.svelte.ts +++ b/web/src/lib/diff-viewer-multi-file.svelte.ts @@ -1,12 +1,12 @@ import { fetchGithubCommitDiff, fetchGithubComparison, fetchGithubPRComparison, type FileStatus, getGithubToken, type GithubDiff } from "./github.svelte"; -import { type StructuredPatch, parsePatch } from "diff"; +import { type StructuredPatch } from "diff"; import { ConciseDiffViewCachedState, DEFAULT_THEME_DARK, DEFAULT_THEME_LIGHT, - hasNonHeaderChanges, isNoNewlineAtEofLine, parseSinglePatch, + patchHeaderDiffOnly, } from "$lib/components/diff/concise-diff-view.svelte"; import type { BundledTheme } from "shiki"; import { browser } from "$app/environment"; @@ -150,8 +150,8 @@ export type CommonFileDetails = { export type TextFileDetails = CommonFileDetails & { type: "text"; - patchText: string; - structuredPatch: Promise; + structuredPatch: StructuredPatch; + patchHeaderDiffOnly: boolean; }; export type ImageFileDetails = CommonFileDetails & { @@ -160,13 +160,14 @@ export type ImageFileDetails = CommonFileDetails & { }; export function makeTextDetails(fromFile: string, toFile: string, status: FileStatus, patchText: string): TextFileDetails { + const patch = parseSinglePatch(patchText); return { type: "text", fromFile, toFile, status, - patchText, - structuredPatch: (async () => parseSinglePatch(patchText))(), + structuredPatch: patch, + patchHeaderDiffOnly: patchHeaderDiffOnly(patch), }; } @@ -285,45 +286,6 @@ export function getFileStatusProps(status: FileStatus): FileStatusProps { } } -export function findHeaderChangeOnlyPatches(fileDetails: FileDetails[]) { - const patchStrings = fileDetails.map((details) => { - if (details.type === "text") { - return details.patchText; - } - return undefined; - }); - - const result: boolean[] = []; - - for (let i = 0; i < patchStrings.length; i++) { - const patchString = patchStrings[i]; - if (patchString === undefined || patchString.length === 0) { - result.push(false); - continue; - } - // TODO: Parsing twice is wasteful - const patches = parsePatch(patchString); - if (patches.length !== 1) { - result.push(false); - continue; - } - const patch = patches[0]; - if (patch.hunks.length === 0) { - result.push(false); - continue; - } - let onlyHeaderChanges = true; - for (let j = 0; j < patch.hunks.length; j++) { - if (hasNonHeaderChanges(patch.hunks[j].lines)) { - onlyHeaderChanges = false; - } - } - result.push(onlyHeaderChanges); - } - - return result; -} - export type ViewerStatistics = { addedLines: number; removedLines: number; @@ -368,19 +330,22 @@ export class MultiFileDiffViewerState { readonly fileTreeFilterDebounced = new Debounced(() => this.fileTreeFilter, 500); readonly searchQueryDebounced = new Debounced(() => this.searchQuery, 500); - readonly stats: Promise = $derived(this.countStats()); + readonly stats: ViewerStatistics = $derived(this.countStats()); readonly fileTreeRoots: TreeNode[] = $derived(makeFileTree(this.fileDetails)); readonly filteredFileDetails: FileDetails[] = $derived( this.fileTreeFilterDebounced.current ? this.fileDetails.filter((f) => this.filterFile(f)) : this.fileDetails, ); - readonly patchHeaderDiffOnly: boolean[] = $derived(findHeaderChangeOnlyPatches(this.fileDetails)); readonly searchResults: Promise = $derived(this.findSearchResults()); private constructor() { // Auto-check all patch header diff only diffs $effect(() => { - for (let i = 0; i < this.patchHeaderDiffOnly.length; i++) { - if (this.patchHeaderDiffOnly[i] && this.checked[i] === undefined) { + for (let i = 0; i < this.fileDetails.length; i++) { + const details = this.fileDetails[i]; + if (details.type !== "text") { + continue; + } + if (details.patchHeaderDiffOnly && this.checked[i] === undefined) { this.checked[i] = true; } } @@ -543,7 +508,7 @@ export class MultiFileDiffViewerState { return false; } - private async countStats(): Promise { + private countStats(): ViewerStatistics { let addedLines = 0; let removedLines = 0; const fileAddedLines: number[] = []; @@ -554,7 +519,7 @@ export class MultiFileDiffViewerState { if (details.type !== "text") { continue; } - const diff = await details.structuredPatch; + const diff = details.structuredPatch; for (let j = 0; j < diff.hunks.length; j++) { const hunk = diff.hunks[j]; diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte index df13691..aaeab24 100644 --- a/web/src/routes/+page.svelte +++ b/web/src/routes/+page.svelte @@ -283,11 +283,7 @@
- {#await viewer.stats} - - {:then stats} - - {/await} +
@@ -325,7 +321,7 @@ {/if}
{/if} - {#if !viewer.collapsed[index] && value.type === "text" && (!viewer.patchHeaderDiffOnly[index] || !globalOptions.omitPatchHeaderOnlyHunks)} + {#if !viewer.collapsed[index] && value.type === "text" && (!value.patchHeaderDiffOnly || !globalOptions.omitPatchHeaderOnlyHunks)}
{#snippet fileName()} @@ -97,19 +99,15 @@ onkeyup={(event) => event.key === "Enter" && viewer.scrollToFile(index, { autoExpand: false, smooth: true })} > {#if value.type === "text"} - {#await viewer.stats} - - {:then stats} - - {/await} + {/if} {@render fileName()}
- {#if viewer.patchHeaderDiffOnly[index]} + {#if patchHeaderDiffOnly} Patch-header-only diff {/if} {@render actionsPopover()} - {#if !viewer.patchHeaderDiffOnly[index] || !globalOptions.omitPatchHeaderOnlyHunks || value.type === "image"} + {#if !patchHeaderDiffOnly || !globalOptions.omitPatchHeaderOnlyHunks || value.type === "image"} {@render collapseToggle()} {/if}