1import type { FileDiffContentsLoader } from "@pierre/diffs";
2import {
3 squashAtomCommandFailure,
4 type AtomCommandResult,
5} from "@t3tools/client-runtime/state/runtime";
6import type {
7 EnvironmentId,
8 PullRequestDiffFileContentsInput,
9 PullRequestDiffFileContentsResult,
10 PullRequestRef,
11 ReviewDiffFileContentsInput,
12 ReviewDiffFileContentsResult,
13 ReviewDiffPreviewSourceKind,
14} from "@t3tools/contracts";
15
16import { resolveFileDiffPath } from "./diffRendering";
17
18interface GitDiffFileContentsSource {
19 readonly environmentId: EnvironmentId;
20 readonly cwd: string;
21 readonly sourceKind: ReviewDiffPreviewSourceKind;
22 readonly baseRef: string | null;
23 readonly headRef: string | null;
24 /** The comparison identity Pierre carries into its hydrated render cache. */
25 readonly cacheKey: string;
26}
27
28interface PullRequestDiffFileContentsSource {
29 readonly environmentId: EnvironmentId;
30 readonly reference: PullRequestRef;
31 readonly commit: string | null;
32 readonly cacheKey: string;
33}
34
35type GetDiffFileContents<E> = (request: {
36 readonly environmentId: EnvironmentId;
37 readonly input: ReviewDiffFileContentsInput;
38}) => Promise<AtomCommandResult<ReviewDiffFileContentsResult, E>>;
39
40type GetPullRequestDiffFileContents<E> = (request: {
41 readonly environmentId: EnvironmentId;
42 readonly input: PullRequestDiffFileContentsInput;
43}) => Promise<AtomCommandResult<PullRequestDiffFileContentsResult, E>>;
44
45function createDiffFileContentsLoader(
46 load: (input: {
47 readonly changeType: PullRequestDiffFileContentsInput["changeType"];
48 readonly oldPath: string;
49 readonly newPath: string;
50 }) => Promise<{ readonly oldContents: string; readonly newContents: string }>,
51 cacheKey: string,
52): FileDiffContentsLoader {
53 return async (fileDiff) => {
54 const newPath = resolveFileDiffPath(fileDiff);
55 const oldPath = fileDiff.prevName
56 ? resolveFileDiffPath({ ...fileDiff, name: fileDiff.prevName })
57 : newPath;
58 const contents = await load({ changeType: fileDiff.type, oldPath, newPath });
59 const newFile = {
60 name: newPath,
61 contents: contents.newContents,
62 cacheKey: `${cacheKey}:new:${newPath}`,
63 };
64 if (fileDiff.type === "rename-pure") {
65 return { oldFile: null, newFile };
66 }
67 return {
68 oldFile: {
69 name: oldPath,
70 contents: contents.oldContents,
71 cacheKey: `${cacheKey}:old:${oldPath}`,
72 },
73 newFile,
74 };
75 };
76}
77
78/** Turns the host's Git file-content RPC into the full-file loader Pierre uses for hunk expansion. */
79export function createGitDiffFileContentsLoader<E>(
80 getDiffFileContents: GetDiffFileContents<E>,
81 source: GitDiffFileContentsSource,
82): FileDiffContentsLoader {
83 return createDiffFileContentsLoader(async ({ changeType, oldPath, newPath }) => {
84 const result = await getDiffFileContents({
85 environmentId: source.environmentId,
86 input: {
87 cwd: source.cwd,
88 sourceKind: source.sourceKind,
89 changeType,
90 baseRef: source.baseRef,
91 headRef: source.headRef,
92 oldPath,
93 newPath,
94 },
95 });
96 if (result._tag !== "Success") {
97 throw squashAtomCommandFailure(result);
98 }
99 return result.value;
100 }, source.cacheKey);
101}
102
103/** Loads host-backed PR files, which may name revisions this checkout has never fetched. */
104export function createPullRequestDiffFileContentsLoader<E>(
105 getDiffFileContents: GetPullRequestDiffFileContents<E>,
106 source: PullRequestDiffFileContentsSource,
107): FileDiffContentsLoader {
108 return createDiffFileContentsLoader(async ({ changeType, oldPath, newPath }) => {
109 const result = await getDiffFileContents({
110 environmentId: source.environmentId,
111 input: {
112 ...source.reference,
113 ...(source.commit === null ? {} : { commit: source.commit }),
114 changeType,
115 oldPath,
116 newPath,
117 },
118 });
119 if (result._tag !== "Success") {
120 throw squashAtomCommandFailure(result);
121 }
122 return result.value;
123 }, source.cacheKey);
124}