@nkzw/codiff-core 1.14.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/components/ReviewCodeView.d.ts +4 -1
- package/dist/app/hooks/code-editing-store.d.ts +68 -0
- package/dist/app/hooks/useCodeEditing.d.ts +34 -0
- package/dist/config/types.d.ts +4 -0
- package/dist/lib/code-view-options.d.ts +1 -2
- package/dist/lib/narrative-walkthrough-diff.d.ts +5 -1
- package/dist/lib/narrative-walkthrough-diff.mjs +4 -2
- package/dist/lib/narrative-walkthrough.d.ts +1 -1
- package/dist/react.mjs +836 -189
- package/dist/style.css +106 -27
- package/dist/types.d.ts +15 -0
- package/package.json +3 -3
package/dist/react.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { filterPatchToHunkIds, getSectionWalkthroughHunks, isGeneratedWalkthroughFile, isSyntheticWalkthroughHunk } from "./lib/narrative-walkthrough-diff.mjs";
|
|
1
|
+
import { filterPatchToHunkIds, getHunkOrdinal, getSectionWalkthroughHunks, isGeneratedWalkthroughFile, isSyntheticWalkthroughHunk } from "./lib/narrative-walkthrough-diff.mjs";
|
|
2
2
|
import { Slot } from "@radix-ui/react-slot";
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
4
|
-
import { Fragment, Suspense, forwardRef, lazy, useCallback, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useOptimistic, useRef, useState, useTransition } from "react";
|
|
4
|
+
import { Fragment, Suspense, forwardRef, lazy, useCallback, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useOptimistic, useRef, useState, useSyncExternalStore, useTransition } from "react";
|
|
5
5
|
import { createPortal, useFormStatus } from "react-dom";
|
|
6
6
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
import { MarkdownEditor } from "@nkzw/mdx-editor";
|
|
@@ -14,7 +14,7 @@ import { ChatCircleIcon } from "@phosphor-icons/react/ChatCircle";
|
|
|
14
14
|
import { PathIcon } from "@phosphor-icons/react/Path";
|
|
15
15
|
import { TreeStructureIcon } from "@phosphor-icons/react/TreeStructure";
|
|
16
16
|
import { FileTree, useFileTree } from "@pierre/trees/react";
|
|
17
|
-
import { hydratePartialDiff, parseDiffFromFile, parsePatchFiles, registerCustomTheme, setCustomExtension } from "@pierre/diffs";
|
|
17
|
+
import { cloneFileDiffMetadata, getFiletypeFromFileName, getHighlighterOptions, hydratePartialDiff, parseDiffFromFile, parsePatchFiles, preloadHighlighter, registerCustomTheme, setCustomExtension } from "@pierre/diffs";
|
|
18
18
|
import useRelativeTime from "@nkzw/use-relative-time";
|
|
19
19
|
import { CaretDownIcon } from "@phosphor-icons/react/CaretDown";
|
|
20
20
|
import "@phosphor-icons/react/CaretUp";
|
|
@@ -26,8 +26,9 @@ import { WarningOctagonIcon } from "@phosphor-icons/react/WarningOctagon";
|
|
|
26
26
|
import { XIcon } from "@phosphor-icons/react/X";
|
|
27
27
|
import { ColumnsIcon } from "@phosphor-icons/react/Columns";
|
|
28
28
|
import { ImageBrokenIcon } from "@phosphor-icons/react/ImageBroken";
|
|
29
|
+
import { PencilSimpleIcon } from "@phosphor-icons/react/PencilSimple";
|
|
29
30
|
import { SquareSplitVerticalIcon } from "@phosphor-icons/react/SquareSplitVertical";
|
|
30
|
-
import { CodeView, WorkerPoolContextProvider } from "@pierre/diffs/react";
|
|
31
|
+
import { CodeView, EditProvider, WorkerPoolContextProvider } from "@pierre/diffs/react";
|
|
31
32
|
import { CrosshairSimpleIcon } from "@phosphor-icons/react/CrosshairSimple";
|
|
32
33
|
import { ProhibitIcon } from "@phosphor-icons/react/Prohibit";
|
|
33
34
|
import { PersistentMarkdownEditor } from "@nkzw/mdx-editor/persistence";
|
|
@@ -3574,14 +3575,6 @@ registerCustomTheme("Licht", async () => licht_default);
|
|
|
3574
3575
|
registerCustomTheme("Dunkel", async () => dunkel_default);
|
|
3575
3576
|
setCustomExtension("cts", "typescript");
|
|
3576
3577
|
setCustomExtension("mts", "typescript");
|
|
3577
|
-
const statusLabel = {
|
|
3578
|
-
added: "Added",
|
|
3579
|
-
conflicted: "Conflicted",
|
|
3580
|
-
deleted: "Deleted",
|
|
3581
|
-
modified: "Modified",
|
|
3582
|
-
renamed: "Renamed",
|
|
3583
|
-
untracked: "Untracked"
|
|
3584
|
-
};
|
|
3585
3578
|
const sectionLabel = {
|
|
3586
3579
|
commit: "Commit",
|
|
3587
3580
|
"pull-request": "PR",
|
|
@@ -3601,7 +3594,7 @@ const workerHighlighterOptions = {
|
|
|
3601
3594
|
light: "Licht"
|
|
3602
3595
|
},
|
|
3603
3596
|
tokenizeMaxLineLength: 2e4,
|
|
3604
|
-
useTokenTransformer:
|
|
3597
|
+
useTokenTransformer: true
|
|
3605
3598
|
};
|
|
3606
3599
|
const codeViewUnsafeCSS = `
|
|
3607
3600
|
:host {
|
|
@@ -3934,7 +3927,7 @@ const getItemVersion = (value) => {
|
|
|
3934
3927
|
//#region lib/keyboard.ts
|
|
3935
3928
|
const isNativeInputTarget = (target) => {
|
|
3936
3929
|
const candidate = target;
|
|
3937
|
-
return candidate?.closest?.("input, select, textarea") != null || candidate?.isContentEditable === true;
|
|
3930
|
+
return candidate?.closest?.("input, select, textarea, [contenteditable=\"true\"]") != null || candidate?.isContentEditable === true || candidate?.shadowRoot?.activeElement != null && isNativeInputTarget(candidate.shadowRoot.activeElement);
|
|
3938
3931
|
};
|
|
3939
3932
|
const isMacPlatform = (platform = navigator.platform) => platform.toLowerCase().includes("mac");
|
|
3940
3933
|
const isPrimaryModifier = (event, platform = navigator.platform) => isMacPlatform(platform) ? event.metaKey && !event.ctrlKey : event.ctrlKey && !event.metaKey;
|
|
@@ -4052,6 +4045,479 @@ const getSourceKey = (source) => source.type === "commit" ? `commit:${source.ref
|
|
|
4052
4045
|
const getShortRef = (ref) => ref.slice(0, 7);
|
|
4053
4046
|
const getSourceLabel = (source) => source.type === "commit" ? getShortRef(source.ref) : source.type === "branch" || source.type === "branch-diff" ? `Branch vs ${source.ref}` : source.type === "branch-working-tree" ? `Local + branch vs ${source.ref}` : source.type === "range" ? rangeLabel(source) : source.type === "pull-request" ? source.number ? `${source.provider === "gitlab" ? "MR" : "PR"} #${source.number}` : source.provider === "gitlab" ? "Merge request" : "Pull request" : "Uncommitted";
|
|
4054
4047
|
//#endregion
|
|
4048
|
+
//#region config/defaults.ts
|
|
4049
|
+
const defaultConfigTemplate = {
|
|
4050
|
+
settings: {
|
|
4051
|
+
"agentBackend": "codex",
|
|
4052
|
+
"checkForUpdates": true,
|
|
4053
|
+
"claudeModel": "claude-sonnet-4-6",
|
|
4054
|
+
"codeFontFamily": "",
|
|
4055
|
+
"codeFontSize": 13,
|
|
4056
|
+
"copyCommentsOnClose": false,
|
|
4057
|
+
"diffStyle": "split",
|
|
4058
|
+
"editorCommand": "",
|
|
4059
|
+
"expandUnchanged": false,
|
|
4060
|
+
"lastRepositoryPath": "",
|
|
4061
|
+
"openAIModel": "gpt-5.6-terra",
|
|
4062
|
+
"openAIReasoningEffort": "",
|
|
4063
|
+
"opencodeModel": "opencode-default",
|
|
4064
|
+
"piModel": "pi-default",
|
|
4065
|
+
"reviewCommentsPrefix": "# Address these Review Comments",
|
|
4066
|
+
"sidebarPosition": "left",
|
|
4067
|
+
"showOutdated": false,
|
|
4068
|
+
"showWhitespace": false,
|
|
4069
|
+
"theme": "system",
|
|
4070
|
+
"walkthroughPrompt": "",
|
|
4071
|
+
"wordWrap": false
|
|
4072
|
+
},
|
|
4073
|
+
keymap: {
|
|
4074
|
+
"askAgent": "Mod+Alt+Enter",
|
|
4075
|
+
"closeSearch": "Escape",
|
|
4076
|
+
"commandBar": "Mod+k",
|
|
4077
|
+
"diffSearch": "Mod+f",
|
|
4078
|
+
"discardComment": "Escape",
|
|
4079
|
+
"doneEditing": "Mod+s",
|
|
4080
|
+
"editFile": "Mod+e",
|
|
4081
|
+
"fileFilter": "Mod+p",
|
|
4082
|
+
"nextHunk": ["Ctrl+ArrowDown", "j"],
|
|
4083
|
+
"nextSearchMatch": "Enter",
|
|
4084
|
+
"openFile": "Mod+Shift+o",
|
|
4085
|
+
"prevHunk": ["Ctrl+ArrowUp", "k"],
|
|
4086
|
+
"prevSearchMatch": "Shift+Enter",
|
|
4087
|
+
"shortcutsHelp": "Shift+?",
|
|
4088
|
+
"submitComment": "Mod+Enter",
|
|
4089
|
+
"toggleSidebar": "Mod+Shift+b",
|
|
4090
|
+
"toggleWordWrap": "Alt+z"
|
|
4091
|
+
}
|
|
4092
|
+
};
|
|
4093
|
+
const createDefaultConfig = () => ({
|
|
4094
|
+
keymap: { ...defaultConfigTemplate.keymap },
|
|
4095
|
+
settings: { ...defaultConfigTemplate.settings }
|
|
4096
|
+
});
|
|
4097
|
+
const defaultKeymap = Object.freeze({ ...defaultConfigTemplate.keymap });
|
|
4098
|
+
Object.freeze({ ...defaultConfigTemplate.settings });
|
|
4099
|
+
const createFileEditingStore = (onChange) => {
|
|
4100
|
+
const sessionRef = { current: null };
|
|
4101
|
+
const draftRef = { current: "" };
|
|
4102
|
+
let timer;
|
|
4103
|
+
let pendingWrite = null;
|
|
4104
|
+
const clearTimer = () => {
|
|
4105
|
+
clearTimeout(timer);
|
|
4106
|
+
timer = void 0;
|
|
4107
|
+
};
|
|
4108
|
+
const setSession = (session) => {
|
|
4109
|
+
sessionRef.current = session ? {
|
|
4110
|
+
...session,
|
|
4111
|
+
dirty: draftRef.current !== session.document.content,
|
|
4112
|
+
hasChanges: session.finishing === "revert" ? session.hasChanges : draftRef.current !== session.originalContent
|
|
4113
|
+
} : null;
|
|
4114
|
+
if (!session) clearTimer();
|
|
4115
|
+
onChange();
|
|
4116
|
+
};
|
|
4117
|
+
const scheduleSave = () => {
|
|
4118
|
+
clearTimer();
|
|
4119
|
+
timer = setTimeout(() => void flush(), 300);
|
|
4120
|
+
};
|
|
4121
|
+
const flush = () => {
|
|
4122
|
+
clearTimer();
|
|
4123
|
+
if (pendingWrite) return pendingWrite.then((saved) => saved ? flush() : false);
|
|
4124
|
+
const current = sessionRef.current;
|
|
4125
|
+
if (!current || draftRef.current === current.document.content) return Promise.resolve(true);
|
|
4126
|
+
const content = draftRef.current;
|
|
4127
|
+
setSession({
|
|
4128
|
+
...current,
|
|
4129
|
+
saveError: void 0,
|
|
4130
|
+
saving: true
|
|
4131
|
+
});
|
|
4132
|
+
pendingWrite = Promise.resolve().then(() => window.codiff.saveRepositoryFile({
|
|
4133
|
+
...current.document,
|
|
4134
|
+
baseVersion: current.document.version,
|
|
4135
|
+
content
|
|
4136
|
+
})).then((result) => {
|
|
4137
|
+
if (result.status === "conflict") throw new Error("The file changed on disk. Your edits are kept; copy them before refreshing.");
|
|
4138
|
+
const latest = sessionRef.current;
|
|
4139
|
+
if (latest) setSession({
|
|
4140
|
+
...latest,
|
|
4141
|
+
document: result.document,
|
|
4142
|
+
saving: false
|
|
4143
|
+
});
|
|
4144
|
+
return true;
|
|
4145
|
+
}).catch((error) => {
|
|
4146
|
+
const latest = sessionRef.current;
|
|
4147
|
+
if (latest) setSession({
|
|
4148
|
+
...latest,
|
|
4149
|
+
saveError: error instanceof Error ? error.message : String(error),
|
|
4150
|
+
saving: false
|
|
4151
|
+
});
|
|
4152
|
+
return false;
|
|
4153
|
+
}).finally(() => {
|
|
4154
|
+
pendingWrite = null;
|
|
4155
|
+
const latest = sessionRef.current;
|
|
4156
|
+
if (latest && !latest.finishing && !latest.saveError && draftRef.current !== latest.document.content) scheduleSave();
|
|
4157
|
+
});
|
|
4158
|
+
return pendingWrite;
|
|
4159
|
+
};
|
|
4160
|
+
const finish = async (revert, beforeClose) => {
|
|
4161
|
+
const current = sessionRef.current;
|
|
4162
|
+
if (!current || current.finishing) return false;
|
|
4163
|
+
clearTimer();
|
|
4164
|
+
setSession({
|
|
4165
|
+
...current,
|
|
4166
|
+
finishing: revert ? "revert" : "done"
|
|
4167
|
+
});
|
|
4168
|
+
let previousDraft;
|
|
4169
|
+
try {
|
|
4170
|
+
if (pendingWrite && !await pendingWrite) return false;
|
|
4171
|
+
if (revert) {
|
|
4172
|
+
previousDraft = draftRef.current;
|
|
4173
|
+
draftRef.current = current.originalContent;
|
|
4174
|
+
}
|
|
4175
|
+
do {
|
|
4176
|
+
do
|
|
4177
|
+
if (!await flush()) {
|
|
4178
|
+
if (previousDraft !== void 0) draftRef.current = previousDraft;
|
|
4179
|
+
return false;
|
|
4180
|
+
}
|
|
4181
|
+
while (!revert && draftRef.current !== sessionRef.current?.document.content);
|
|
4182
|
+
await beforeClose?.();
|
|
4183
|
+
} while (!revert && draftRef.current !== sessionRef.current?.document.content);
|
|
4184
|
+
setSession(null);
|
|
4185
|
+
return true;
|
|
4186
|
+
} catch (error) {
|
|
4187
|
+
if (previousDraft !== void 0) draftRef.current = previousDraft;
|
|
4188
|
+
throw error;
|
|
4189
|
+
} finally {
|
|
4190
|
+
const latest = sessionRef.current;
|
|
4191
|
+
if (latest) setSession({
|
|
4192
|
+
...latest,
|
|
4193
|
+
finishing: void 0
|
|
4194
|
+
});
|
|
4195
|
+
}
|
|
4196
|
+
};
|
|
4197
|
+
return {
|
|
4198
|
+
draftRef,
|
|
4199
|
+
finish,
|
|
4200
|
+
flush,
|
|
4201
|
+
getSnapshot: () => sessionRef.current,
|
|
4202
|
+
setDraft: (content) => {
|
|
4203
|
+
const current = sessionRef.current;
|
|
4204
|
+
if (!current || current.finishing === "revert" || content === draftRef.current) return;
|
|
4205
|
+
const wasDirty = draftRef.current !== current.document.content;
|
|
4206
|
+
draftRef.current = content;
|
|
4207
|
+
const isDirty = content !== current.document.content;
|
|
4208
|
+
if (isDirty && !current.finishing && !current.saveError) scheduleSave();
|
|
4209
|
+
else clearTimer();
|
|
4210
|
+
if (wasDirty !== isDirty || current.hasChanges !== (content !== current.originalContent)) setSession({ ...current });
|
|
4211
|
+
},
|
|
4212
|
+
setSession
|
|
4213
|
+
};
|
|
4214
|
+
};
|
|
4215
|
+
const getCodeEditKey = (sourceKey, itemId) => JSON.stringify([sourceKey, itemId]);
|
|
4216
|
+
const createCodeEditingStore = () => {
|
|
4217
|
+
const entries = /* @__PURE__ */ new Map();
|
|
4218
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
4219
|
+
let snapshot = /* @__PURE__ */ new Map();
|
|
4220
|
+
const hasFile = (path) => [...snapshot.values()].some((session) => session.document.path === path);
|
|
4221
|
+
return {
|
|
4222
|
+
clear: () => {
|
|
4223
|
+
for (const entry of entries.values()) entry.setSession(null);
|
|
4224
|
+
},
|
|
4225
|
+
flush: (sourceKey) => Promise.all([...entries.values()].filter((entry) => entry.getSnapshot()?.sourceKey === sourceKey).map((entry) => entry.flush())),
|
|
4226
|
+
get: (sourceKey, itemId) => entries.get(getCodeEditKey(sourceKey, itemId)),
|
|
4227
|
+
getForItem: (item) => item.type === "diff" ? [...entries.values()].find((entry) => {
|
|
4228
|
+
const session = entry.getSnapshot();
|
|
4229
|
+
return session?.item.id === item.id && session.item.fileDiff === item.fileDiff;
|
|
4230
|
+
}) : void 0,
|
|
4231
|
+
getSnapshot: () => snapshot,
|
|
4232
|
+
hasFile,
|
|
4233
|
+
hasUnsavedEdits: () => [...entries.values()].some((entry) => {
|
|
4234
|
+
const session = entry.getSnapshot();
|
|
4235
|
+
return session && (session.saving || entry.draftRef.current !== session.document.content);
|
|
4236
|
+
}),
|
|
4237
|
+
start: (session) => {
|
|
4238
|
+
if (hasFile(session.document.path)) return null;
|
|
4239
|
+
const key = getCodeEditKey(session.sourceKey, session.item.id);
|
|
4240
|
+
const entry = createFileEditingStore(() => {
|
|
4241
|
+
const next = new Map(snapshot);
|
|
4242
|
+
const current = entry.getSnapshot();
|
|
4243
|
+
if (current) next.set(key, current);
|
|
4244
|
+
else {
|
|
4245
|
+
next.delete(key);
|
|
4246
|
+
entries.delete(key);
|
|
4247
|
+
}
|
|
4248
|
+
snapshot = next;
|
|
4249
|
+
for (const listener of listeners) listener();
|
|
4250
|
+
});
|
|
4251
|
+
entries.set(key, entry);
|
|
4252
|
+
entry.draftRef.current = session.document.content;
|
|
4253
|
+
entry.setSession(session);
|
|
4254
|
+
return entry;
|
|
4255
|
+
},
|
|
4256
|
+
subscribe: (listener) => {
|
|
4257
|
+
listeners.add(listener);
|
|
4258
|
+
return () => {
|
|
4259
|
+
listeners.delete(listener);
|
|
4260
|
+
};
|
|
4261
|
+
}
|
|
4262
|
+
};
|
|
4263
|
+
};
|
|
4264
|
+
//#endregion
|
|
4265
|
+
//#region app/hooks/useCodeEditing.ts
|
|
4266
|
+
let editorModule = null;
|
|
4267
|
+
let editorModulePromise = null;
|
|
4268
|
+
const loadEditor = () => editorModulePromise ??= import("@pierre/diffs/edit").then((module) => {
|
|
4269
|
+
editorModule = module;
|
|
4270
|
+
return module;
|
|
4271
|
+
}).catch((error) => {
|
|
4272
|
+
editorModulePromise = null;
|
|
4273
|
+
throw error;
|
|
4274
|
+
});
|
|
4275
|
+
const prepareEditor = (fileDiff) => {
|
|
4276
|
+
const lang = fileDiff.lang ?? getFiletypeFromFileName(fileDiff.name);
|
|
4277
|
+
return Promise.all([loadEditor(), preloadHighlighter(getHighlighterOptions(lang, workerHighlighterOptions))]);
|
|
4278
|
+
};
|
|
4279
|
+
const createCodeEditor = (type, options, key) => {
|
|
4280
|
+
if (!editorModule) throw new Error("The code editor has not loaded.");
|
|
4281
|
+
return new editorModule.Editor(type, options, key);
|
|
4282
|
+
};
|
|
4283
|
+
function useUnsavedCodeEditsGuard(store) {
|
|
4284
|
+
useEffect(() => {
|
|
4285
|
+
const beforeUnload = (event) => {
|
|
4286
|
+
if (store.hasUnsavedEdits()) {
|
|
4287
|
+
event.preventDefault();
|
|
4288
|
+
event.returnValue = "";
|
|
4289
|
+
}
|
|
4290
|
+
};
|
|
4291
|
+
window.addEventListener("beforeunload", beforeUnload);
|
|
4292
|
+
return () => window.removeEventListener("beforeunload", beforeUnload);
|
|
4293
|
+
}, [store]);
|
|
4294
|
+
}
|
|
4295
|
+
const errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
4296
|
+
const getFirstVisibleChangedLine = (root, viewport) => {
|
|
4297
|
+
const viewportRect = viewport.getBoundingClientRect();
|
|
4298
|
+
const header = root.querySelector("[data-diffs-header][data-sticky]");
|
|
4299
|
+
const top = Math.max(viewportRect.top, header?.getBoundingClientRect().bottom ?? viewportRect.top) + 11;
|
|
4300
|
+
for (const row of root.querySelectorAll("[data-content] > [data-line-type=\"change-addition\"]")) {
|
|
4301
|
+
const rect = row.getBoundingClientRect();
|
|
4302
|
+
if (rect.height > 0 && rect.top >= top && rect.top < viewportRect.bottom) {
|
|
4303
|
+
const line = Number(row.dataset.line);
|
|
4304
|
+
if (Number.isInteger(line) && line > 0) return line;
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
};
|
|
4308
|
+
function useCodeEditing({ codeViewRef, items, keymap = defaultKeymap, onLoadSectionContents, onRefresh, selectedItemId, sourceKey, store: sharedStore }) {
|
|
4309
|
+
const [localStore] = useState(createCodeEditingStore);
|
|
4310
|
+
const store = sharedStore ?? localStore;
|
|
4311
|
+
const sessions = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
4312
|
+
useUnsavedCodeEditsGuard(store);
|
|
4313
|
+
const busyRef = useRef(/* @__PURE__ */ new Set());
|
|
4314
|
+
const [busyItemKeys, setBusyItemKeys] = useState(() => /* @__PURE__ */ new Set());
|
|
4315
|
+
const [errors, setErrors] = useState(() => /* @__PURE__ */ new Map());
|
|
4316
|
+
const setBusy = useCallback((key, busy) => {
|
|
4317
|
+
if (busy) busyRef.current.add(key);
|
|
4318
|
+
else busyRef.current.delete(key);
|
|
4319
|
+
setBusyItemKeys(new Set(busyRef.current));
|
|
4320
|
+
}, []);
|
|
4321
|
+
const setError = useCallback((key, message) => {
|
|
4322
|
+
setErrors((current) => {
|
|
4323
|
+
const next = new Map(current);
|
|
4324
|
+
if (message) next.set(key, message);
|
|
4325
|
+
else next.delete(key);
|
|
4326
|
+
return next;
|
|
4327
|
+
});
|
|
4328
|
+
}, []);
|
|
4329
|
+
const currentSessions = useMemo(() => new Map([...sessions.values()].filter((session) => session.sourceKey === sourceKey).map((session) => [session.item.id, session])), [sessions, sourceKey]);
|
|
4330
|
+
const editorOptions = useMemo(() => {
|
|
4331
|
+
const focusedEditors = /* @__PURE__ */ new WeakSet();
|
|
4332
|
+
return { onAttach: (editor, fileInstance) => {
|
|
4333
|
+
if (!focusedEditors.has(editor)) {
|
|
4334
|
+
focusedEditors.add(editor);
|
|
4335
|
+
const viewer = codeViewRef.current?.getInstance();
|
|
4336
|
+
const root = viewer?.getRenderedItems().find(({ instance }) => instance === fileInstance)?.element.shadowRoot;
|
|
4337
|
+
const viewport = viewer?.getContainerElement();
|
|
4338
|
+
editor.focus({
|
|
4339
|
+
character: 0,
|
|
4340
|
+
lineNumber: (root && viewport ? getFirstVisibleChangedLine(root, viewport) : void 0) ?? "first-visible",
|
|
4341
|
+
offset: 11,
|
|
4342
|
+
preventScroll: true
|
|
4343
|
+
});
|
|
4344
|
+
const position = editor.getViewState().selections?.[0]?.start;
|
|
4345
|
+
const indentation = (position && editor.getEditState()?.document.getLineText(position.line))?.match(/^\s*/)?.[0].length ?? 0;
|
|
4346
|
+
if (position && indentation > 0) editor.focus({
|
|
4347
|
+
character: indentation,
|
|
4348
|
+
lineNumber: position.line + 1,
|
|
4349
|
+
preventScroll: true
|
|
4350
|
+
});
|
|
4351
|
+
}
|
|
4352
|
+
} };
|
|
4353
|
+
}, [codeViewRef]);
|
|
4354
|
+
const loadEditContents = useCallback((item, metadata) => {
|
|
4355
|
+
if (!item.fileDiff.isPartial) return Promise.resolve({
|
|
4356
|
+
newFile: {
|
|
4357
|
+
contents: item.fileDiff.additionLines.join(""),
|
|
4358
|
+
name: item.fileDiff.name
|
|
4359
|
+
},
|
|
4360
|
+
oldFile: null
|
|
4361
|
+
});
|
|
4362
|
+
return loadSectionContents(metadata.file, metadata.section, onLoadSectionContents);
|
|
4363
|
+
}, [onLoadSectionContents]);
|
|
4364
|
+
const prepareEdit = useCallback(async (item, metadata) => {
|
|
4365
|
+
if (item.type === "diff" && onLoadSectionContents) {
|
|
4366
|
+
const [, loaded] = await Promise.all([prepareEditor(item.fileDiff), loadEditContents(item, metadata)]);
|
|
4367
|
+
const rendered = codeViewRef.current?.getInstance()?.getRenderedItems().find(({ id }) => id === item.id);
|
|
4368
|
+
if (rendered?.type === "diff") {
|
|
4369
|
+
const fileDiff = item.fileDiff.isPartial ? hydratePartialDiff("clone", item.fileDiff, loaded) : item.fileDiff;
|
|
4370
|
+
await rendered.instance.primeHighlightCache(fileDiff);
|
|
4371
|
+
}
|
|
4372
|
+
}
|
|
4373
|
+
}, [
|
|
4374
|
+
codeViewRef,
|
|
4375
|
+
loadEditContents,
|
|
4376
|
+
onLoadSectionContents
|
|
4377
|
+
]);
|
|
4378
|
+
const startEdit = useCallback(async (item, metadata) => {
|
|
4379
|
+
const itemKey = getCodeEditKey(sourceKey, item.id);
|
|
4380
|
+
if (item.type !== "diff" || !onLoadSectionContents || store.hasFile(metadata.file.path) || busyRef.current.has(itemKey)) return;
|
|
4381
|
+
setBusy(itemKey, true);
|
|
4382
|
+
setError(itemKey);
|
|
4383
|
+
try {
|
|
4384
|
+
const [, loaded, document] = await Promise.all([
|
|
4385
|
+
prepareEditor(item.fileDiff),
|
|
4386
|
+
loadEditContents(item, metadata),
|
|
4387
|
+
window.codiff.getRepositoryFile(metadata.file.path)
|
|
4388
|
+
]);
|
|
4389
|
+
if (!loaded.newFile || loaded.newFile.contents !== document.content) throw new Error("The file changed on disk. Refresh the diff before editing.");
|
|
4390
|
+
const key = crypto.randomUUID();
|
|
4391
|
+
const fileDiff = item.fileDiff.isPartial ? hydratePartialDiff("clone", item.fileDiff, loaded) : cloneFileDiffMetadata(item.fileDiff);
|
|
4392
|
+
fileDiff.cacheKey ??= `code-edit:${key}`;
|
|
4393
|
+
const rendered = codeViewRef.current?.getInstance()?.getRenderedItems().find(({ id }) => id === item.id);
|
|
4394
|
+
if (rendered?.type === "diff") await rendered.instance.primeHighlightCache(fileDiff);
|
|
4395
|
+
const nextSession = {
|
|
4396
|
+
dirty: false,
|
|
4397
|
+
document,
|
|
4398
|
+
hasChanges: false,
|
|
4399
|
+
item: {
|
|
4400
|
+
...item,
|
|
4401
|
+
collapsed: false,
|
|
4402
|
+
edit: true,
|
|
4403
|
+
fileDiff,
|
|
4404
|
+
version: getItemVersion(`code-edit:${key}`)
|
|
4405
|
+
},
|
|
4406
|
+
metadata,
|
|
4407
|
+
originalContent: document.content,
|
|
4408
|
+
sourceKey
|
|
4409
|
+
};
|
|
4410
|
+
if (store.start(nextSession)) codeViewRef.current?.clearSelectedLines();
|
|
4411
|
+
} catch (error) {
|
|
4412
|
+
setError(itemKey, errorMessage(error));
|
|
4413
|
+
} finally {
|
|
4414
|
+
setBusy(itemKey, false);
|
|
4415
|
+
}
|
|
4416
|
+
}, [
|
|
4417
|
+
codeViewRef,
|
|
4418
|
+
loadEditContents,
|
|
4419
|
+
onLoadSectionContents,
|
|
4420
|
+
setBusy,
|
|
4421
|
+
setError,
|
|
4422
|
+
sourceKey,
|
|
4423
|
+
store
|
|
4424
|
+
]);
|
|
4425
|
+
const finishEdit = useCallback(async (itemId, revert) => {
|
|
4426
|
+
const itemKey = getCodeEditKey(sourceKey, itemId);
|
|
4427
|
+
const entry = store.get(sourceKey, itemId);
|
|
4428
|
+
const current = entry?.getSnapshot();
|
|
4429
|
+
if (!entry || !current || busyRef.current.has(itemKey)) return;
|
|
4430
|
+
setBusy(itemKey, true);
|
|
4431
|
+
setError(itemKey);
|
|
4432
|
+
const needsRefresh = current.hasChanges || current.document.content !== current.originalContent || current.saving;
|
|
4433
|
+
try {
|
|
4434
|
+
await entry.finish(revert, async () => {
|
|
4435
|
+
const latest = entry.getSnapshot();
|
|
4436
|
+
if ((needsRefresh || latest?.hasChanges) && onRefresh && !await onRefresh(current.metadata.file, current.metadata.section)) throw new Error("File saved. Refresh the review to see the updated diff.");
|
|
4437
|
+
});
|
|
4438
|
+
} catch (error) {
|
|
4439
|
+
setError(itemKey, errorMessage(error));
|
|
4440
|
+
} finally {
|
|
4441
|
+
setBusy(itemKey, false);
|
|
4442
|
+
}
|
|
4443
|
+
}, [
|
|
4444
|
+
onRefresh,
|
|
4445
|
+
setBusy,
|
|
4446
|
+
setError,
|
|
4447
|
+
sourceKey,
|
|
4448
|
+
store
|
|
4449
|
+
]);
|
|
4450
|
+
const doneEdit = useCallback((itemId) => finishEdit(itemId, false), [finishEdit]);
|
|
4451
|
+
const revertEdit = useCallback((itemId) => finishEdit(itemId, true), [finishEdit]);
|
|
4452
|
+
const onItemEditChange = useCallback((event, item) => {
|
|
4453
|
+
store.getForItem(item)?.setDraft(event.file.contents);
|
|
4454
|
+
}, [store]);
|
|
4455
|
+
const onItemEditComplete = useCallback((_event, item, nextItem) => {
|
|
4456
|
+
const entry = store.getForItem(item);
|
|
4457
|
+
const current = entry?.getSnapshot();
|
|
4458
|
+
if (!entry || !current || nextItem.type !== "diff") return "reject";
|
|
4459
|
+
nextItem.fileDiff.cacheKey = `code-edit:${crypto.randomUUID()}`;
|
|
4460
|
+
entry.setSession({
|
|
4461
|
+
...current,
|
|
4462
|
+
item: {
|
|
4463
|
+
...nextItem,
|
|
4464
|
+
edit: true
|
|
4465
|
+
}
|
|
4466
|
+
});
|
|
4467
|
+
return "accept";
|
|
4468
|
+
}, [store]);
|
|
4469
|
+
const editedItems = useMemo(() => {
|
|
4470
|
+
if (currentSessions.size === 0) return items;
|
|
4471
|
+
const found = /* @__PURE__ */ new Set();
|
|
4472
|
+
const next = items.map((item) => {
|
|
4473
|
+
const session = currentSessions.get(item.id);
|
|
4474
|
+
if (!session) return item;
|
|
4475
|
+
found.add(item.id);
|
|
4476
|
+
return {
|
|
4477
|
+
...session.item,
|
|
4478
|
+
collapsed: item.collapsed
|
|
4479
|
+
};
|
|
4480
|
+
});
|
|
4481
|
+
for (const session of currentSessions.values()) if (!found.has(session.item.id)) next.push(session.item);
|
|
4482
|
+
return next;
|
|
4483
|
+
}, [currentSessions, items]);
|
|
4484
|
+
useEffect(() => {
|
|
4485
|
+
if (currentSessions.size === 0) return;
|
|
4486
|
+
const keyDown = (event) => {
|
|
4487
|
+
if (event.defaultPrevented || event.repeat || event.isComposing || !matchesShortcut(event, keymap, "doneEditing")) return;
|
|
4488
|
+
const path = event.composedPath();
|
|
4489
|
+
if (isNativeInputTarget(event.target) && !path.some((node) => node.matches?.("[data-content][contenteditable=\"true\"]"))) return;
|
|
4490
|
+
const itemId = codeViewRef.current?.getInstance()?.getRenderedItems().find(({ element, id }) => currentSessions.has(id) && path.includes(element))?.id ?? selectedItemId ?? (currentSessions.size === 1 ? currentSessions.keys().next().value : void 0);
|
|
4491
|
+
if (itemId == null || !currentSessions.has(itemId)) return;
|
|
4492
|
+
event.preventDefault();
|
|
4493
|
+
event.stopPropagation();
|
|
4494
|
+
doneEdit(itemId);
|
|
4495
|
+
};
|
|
4496
|
+
window.addEventListener("keydown", keyDown, true);
|
|
4497
|
+
return () => window.removeEventListener("keydown", keyDown, true);
|
|
4498
|
+
}, [
|
|
4499
|
+
codeViewRef,
|
|
4500
|
+
currentSessions,
|
|
4501
|
+
doneEdit,
|
|
4502
|
+
keymap,
|
|
4503
|
+
selectedItemId
|
|
4504
|
+
]);
|
|
4505
|
+
return {
|
|
4506
|
+
canStartEdit: (itemId, path) => !busyItemKeys.has(getCodeEditKey(sourceKey, itemId)) && ![...sessions.values()].some((session) => session.document.path === path),
|
|
4507
|
+
doneEdit,
|
|
4508
|
+
editorOptions,
|
|
4509
|
+
getError: (itemId) => currentSessions.get(itemId)?.saveError ?? errors.get(getCodeEditKey(sourceKey, itemId)),
|
|
4510
|
+
isBusy: (itemId) => busyItemKeys.has(getCodeEditKey(sourceKey, itemId)) || currentSessions.get(itemId)?.finishing != null,
|
|
4511
|
+
items: editedItems,
|
|
4512
|
+
onItemEditChange,
|
|
4513
|
+
onItemEditComplete,
|
|
4514
|
+
prepareEdit,
|
|
4515
|
+
revertEdit,
|
|
4516
|
+
sessions: currentSessions,
|
|
4517
|
+
startEdit
|
|
4518
|
+
};
|
|
4519
|
+
}
|
|
4520
|
+
//#endregion
|
|
4055
4521
|
//#region app/hooks/useCodeViewAnnotations.ts
|
|
4056
4522
|
function useCodeViewAnnotations() {
|
|
4057
4523
|
const [getAnnotations] = useState(() => {
|
|
@@ -4484,14 +4950,17 @@ const resolveWalkthroughHunkFile = (hunk, files) => {
|
|
|
4484
4950
|
};
|
|
4485
4951
|
};
|
|
4486
4952
|
const walkthroughHunkRunKey = (item, hunks) => `${item.id}:${hunks.map((hunk) => hunk.id).join(",")}`;
|
|
4487
|
-
/** Resolve
|
|
4953
|
+
/** Resolve authored hunk order, coalescing only forward runs within each file section. */
|
|
4488
4954
|
const resolveWalkthroughHunkRuns = (item, files) => {
|
|
4489
4955
|
const runs = [];
|
|
4490
4956
|
for (const hunk of item.hunks) {
|
|
4491
4957
|
const resolved = resolveWalkthroughHunkFile(hunk, files);
|
|
4492
4958
|
if (!resolved) continue;
|
|
4493
4959
|
const previous = runs.at(-1);
|
|
4494
|
-
|
|
4960
|
+
const previousHunk = previous?.hunks.at(-1);
|
|
4961
|
+
const previousOrdinal = previousHunk ? getHunkOrdinal(resolved.section.id, previousHunk.id) : null;
|
|
4962
|
+
const ordinal = getHunkOrdinal(resolved.section.id, hunk.id);
|
|
4963
|
+
if (previous && previous.resolved.file.path === resolved.file.path && previous.resolved.section.id === resolved.section.id && previousOrdinal != null && ordinal != null && previousOrdinal < ordinal) {
|
|
4495
4964
|
const hunks = [...previous.hunks, hunk];
|
|
4496
4965
|
runs[runs.length - 1] = {
|
|
4497
4966
|
...previous,
|
|
@@ -5095,124 +5564,165 @@ function CopyFilePathButton({ path }) {
|
|
|
5095
5564
|
})
|
|
5096
5565
|
});
|
|
5097
5566
|
}
|
|
5098
|
-
function
|
|
5567
|
+
function CodeEditButton({ disabled, isBusy, item, meta, onEdit, onPrepare }) {
|
|
5568
|
+
useEffect(() => {
|
|
5569
|
+
onPrepare(item, meta).catch(() => {});
|
|
5570
|
+
}, [
|
|
5571
|
+
item,
|
|
5572
|
+
meta,
|
|
5573
|
+
onPrepare
|
|
5574
|
+
]);
|
|
5575
|
+
return /* @__PURE__ */ jsxs(Button, {
|
|
5576
|
+
"aria-label": `Edit ${meta.file.path}`,
|
|
5577
|
+
disabled: disabled || isBusy,
|
|
5578
|
+
onClick: onEdit,
|
|
5579
|
+
title: "Edit working-tree file",
|
|
5580
|
+
type: "button",
|
|
5581
|
+
children: [/* @__PURE__ */ jsx(PencilSimpleIcon, {
|
|
5582
|
+
"aria-hidden": true,
|
|
5583
|
+
size: 14,
|
|
5584
|
+
weight: "bold"
|
|
5585
|
+
}), "Edit"]
|
|
5586
|
+
});
|
|
5587
|
+
}
|
|
5588
|
+
function CodeViewHeader({ allowViewedToggle, canCreateFileComment, editActions, isEditing = false, isSectionLoading, meta, onCreateFileComment, onLoadSection, onOpenFile, onToggleCollapsed, onToggleMarkdownPreview, onToggleViewed, readOnly }) {
|
|
5099
5589
|
const { canRenderMarkdown, file, isCollapsed, isMarkdownPreview, isSelected, isViewed, lineCount, reviewIdentity, section, sectionCount, walkthroughNote } = meta;
|
|
5100
5590
|
const canOpenFile = file.status !== "deleted";
|
|
5101
5591
|
const canLoadSection = shouldLoadDiffSectionContents(section);
|
|
5592
|
+
const directoryEnd = file.path.lastIndexOf("/") + 1;
|
|
5102
5593
|
return /* @__PURE__ */ jsxs("div", {
|
|
5103
5594
|
className: `codiff-file-header${walkthroughNote ? " with-note" : ""}${isCollapsed ? " collapsed" : ""}${isSelected ? " selected" : ""}${isViewed ? " viewed" : ""}`,
|
|
5104
|
-
children: [
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5595
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
5596
|
+
"aria-expanded": !isCollapsed,
|
|
5597
|
+
"aria-label": isCollapsed ? "Expand file" : "Collapse file",
|
|
5598
|
+
className: "codiff-header-toggle",
|
|
5599
|
+
onClick: () => onToggleCollapsed(file, isCollapsed, reviewIdentity.key),
|
|
5600
|
+
onKeyDown: (event) => {
|
|
5601
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
5602
|
+
event.preventDefault();
|
|
5603
|
+
onToggleCollapsed(file, isCollapsed, reviewIdentity.key);
|
|
5604
|
+
}
|
|
5605
|
+
},
|
|
5606
|
+
role: "button",
|
|
5607
|
+
tabIndex: 0,
|
|
5608
|
+
title: isCollapsed ? "Expand" : "Collapse",
|
|
5609
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
5610
|
+
className: "codiff-chevron-box",
|
|
5611
|
+
children: /* @__PURE__ */ jsx(CaretDownIcon, {
|
|
5612
|
+
"aria-hidden": true,
|
|
5613
|
+
className: isCollapsed ? "codiff-chevron collapsed" : "codiff-chevron",
|
|
5614
|
+
size: 16,
|
|
5615
|
+
weight: "bold"
|
|
5616
|
+
})
|
|
5617
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
5618
|
+
className: "codiff-file-heading",
|
|
5119
5619
|
children: [
|
|
5120
|
-
/* @__PURE__ */ jsx("span", {
|
|
5121
|
-
className: "codiff-chevron-box",
|
|
5122
|
-
children: /* @__PURE__ */ jsx(CaretDownIcon, {
|
|
5123
|
-
"aria-hidden": true,
|
|
5124
|
-
className: isCollapsed ? "codiff-chevron collapsed" : "codiff-chevron",
|
|
5125
|
-
size: 16,
|
|
5126
|
-
weight: "bold"
|
|
5127
|
-
})
|
|
5128
|
-
}),
|
|
5129
5620
|
/* @__PURE__ */ jsxs("span", {
|
|
5130
|
-
className: "codiff-file-
|
|
5621
|
+
className: "codiff-file-path-row",
|
|
5131
5622
|
children: [
|
|
5132
5623
|
/* @__PURE__ */ jsxs("span", {
|
|
5133
|
-
className: "codiff-file-path
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5624
|
+
className: "codiff-file-path",
|
|
5625
|
+
title: file.path,
|
|
5626
|
+
children: [directoryEnd > 0 ? /* @__PURE__ */ jsx("span", {
|
|
5627
|
+
className: "codiff-file-directory",
|
|
5628
|
+
children: file.path.slice(0, directoryEnd)
|
|
5629
|
+
}) : null, /* @__PURE__ */ jsx("span", {
|
|
5630
|
+
className: "codiff-file-name",
|
|
5631
|
+
children: file.path.slice(directoryEnd)
|
|
5632
|
+
})]
|
|
5138
5633
|
}),
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5634
|
+
/* @__PURE__ */ jsx(CopyFilePathButton, { path: file.path }),
|
|
5635
|
+
!readOnly && onOpenFile ? /* @__PURE__ */ jsx("button", {
|
|
5636
|
+
"aria-label": `Open ${file.path} in editor`,
|
|
5637
|
+
className: "codiff-open-file-button",
|
|
5638
|
+
disabled: !canOpenFile,
|
|
5639
|
+
onClick: (event) => {
|
|
5640
|
+
event.stopPropagation();
|
|
5641
|
+
onOpenFile(file);
|
|
5642
|
+
},
|
|
5643
|
+
onKeyDown: (event) => {
|
|
5644
|
+
if (event.key === "Enter" || event.key === " ") event.stopPropagation();
|
|
5645
|
+
},
|
|
5646
|
+
title: canOpenFile ? "Open file in editor" : "Deleted files cannot be opened",
|
|
5647
|
+
type: "button",
|
|
5648
|
+
children: /* @__PURE__ */ jsx(ArrowSquareOutIcon, {
|
|
5649
|
+
"aria-hidden": true,
|
|
5650
|
+
size: 16,
|
|
5651
|
+
weight: "bold"
|
|
5652
|
+
})
|
|
5146
5653
|
}) : null
|
|
5147
5654
|
]
|
|
5148
5655
|
}),
|
|
5149
|
-
|
|
5150
|
-
className:
|
|
5151
|
-
children:
|
|
5656
|
+
file.oldPath ? /* @__PURE__ */ jsx("span", {
|
|
5657
|
+
className: "codiff-file-old-path",
|
|
5658
|
+
children: file.oldPath
|
|
5152
5659
|
}) : null,
|
|
5153
|
-
|
|
5154
|
-
className: "codiff-
|
|
5155
|
-
|
|
5156
|
-
children: "Generated"
|
|
5660
|
+
walkthroughNote ? /* @__PURE__ */ jsx("span", {
|
|
5661
|
+
className: "codiff-file-note",
|
|
5662
|
+
children: walkthroughNote.reason
|
|
5157
5663
|
}) : null
|
|
5158
5664
|
]
|
|
5159
|
-
})
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
"
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
type: "button",
|
|
5183
|
-
children: isMarkdownPreview ? "View as Diff" : "View as Markdown"
|
|
5184
|
-
}) : null,
|
|
5185
|
-
canLoadSection && !readOnly ? /* @__PURE__ */ jsx("button", {
|
|
5186
|
-
className: "codiff-load-button",
|
|
5187
|
-
disabled: isSectionLoading,
|
|
5188
|
-
onClick: () => onLoadSection(file, section),
|
|
5189
|
-
title: isSectionLoading ? "Loading file contents" : "Load file contents",
|
|
5190
|
-
type: "button",
|
|
5191
|
-
children: isSectionLoading ? "Loading..." : "Load"
|
|
5192
|
-
}) : null,
|
|
5193
|
-
!readOnly && onOpenFile ? /* @__PURE__ */ jsx(Button, {
|
|
5194
|
-
disabled: !canOpenFile,
|
|
5195
|
-
onClick: () => onOpenFile(file),
|
|
5196
|
-
title: canOpenFile ? "Open file in editor" : "Deleted files cannot be opened",
|
|
5197
|
-
type: "button",
|
|
5198
|
-
children: "Open"
|
|
5199
|
-
}) : null,
|
|
5200
|
-
!readOnly || allowViewedToggle ? /* @__PURE__ */ jsxs("button", {
|
|
5201
|
-
"aria-pressed": isViewed,
|
|
5202
|
-
className: `codiff-viewed-button${isViewed ? " active" : ""}`,
|
|
5203
|
-
onClick: () => onToggleViewed(file, isViewed, reviewIdentity),
|
|
5204
|
-
type: "button",
|
|
5205
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
5206
|
-
"aria-hidden": true,
|
|
5207
|
-
className: "codiff-viewed-checkbox",
|
|
5208
|
-
children: isViewed ? /* @__PURE__ */ jsx(CheckIcon, {
|
|
5209
|
-
className: "codiff-viewed-check",
|
|
5210
|
-
size: 10,
|
|
5665
|
+
})]
|
|
5666
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
5667
|
+
className: "codiff-file-details",
|
|
5668
|
+
children: [
|
|
5669
|
+
/* @__PURE__ */ jsx(DiffLineCountBadge, { lineCount }),
|
|
5670
|
+
sectionCount > 1 ? /* @__PURE__ */ jsx("span", {
|
|
5671
|
+
className: `codiff-section-badge ${section.kind}`,
|
|
5672
|
+
children: sectionLabel[section.kind]
|
|
5673
|
+
}) : null,
|
|
5674
|
+
isGeneratedWalkthroughFile(file) ? /* @__PURE__ */ jsx("span", {
|
|
5675
|
+
className: "codiff-generated-badge",
|
|
5676
|
+
title: "Generated file",
|
|
5677
|
+
children: "Generated"
|
|
5678
|
+
}) : null,
|
|
5679
|
+
canCreateFileComment ? /* @__PURE__ */ jsxs(Button, {
|
|
5680
|
+
className: "codiff-file-comment-button",
|
|
5681
|
+
onClick: onCreateFileComment,
|
|
5682
|
+
title: "Comment on file",
|
|
5683
|
+
type: "button",
|
|
5684
|
+
children: [/* @__PURE__ */ jsx(ChatCircleIcon, {
|
|
5685
|
+
"aria-hidden": true,
|
|
5686
|
+
className: "codiff-file-comment-icon",
|
|
5687
|
+
size: 14,
|
|
5211
5688
|
weight: "bold"
|
|
5212
|
-
})
|
|
5213
|
-
}),
|
|
5214
|
-
|
|
5215
|
-
|
|
5689
|
+
}), "Comment"]
|
|
5690
|
+
}) : null,
|
|
5691
|
+
canRenderMarkdown ? /* @__PURE__ */ jsx(Button, {
|
|
5692
|
+
"aria-pressed": isMarkdownPreview,
|
|
5693
|
+
className: `codiff-markdown-button${isMarkdownPreview ? " active" : ""}`,
|
|
5694
|
+
disabled: isEditing,
|
|
5695
|
+
onClick: () => onToggleMarkdownPreview(file, section),
|
|
5696
|
+
title: isMarkdownPreview ? "View as Diff" : "View as Markdown",
|
|
5697
|
+
type: "button",
|
|
5698
|
+
children: isMarkdownPreview ? "View as Diff" : "View as Markdown"
|
|
5699
|
+
}) : null,
|
|
5700
|
+
editActions,
|
|
5701
|
+
canLoadSection && !readOnly ? /* @__PURE__ */ jsx("button", {
|
|
5702
|
+
className: "codiff-load-button",
|
|
5703
|
+
disabled: isSectionLoading,
|
|
5704
|
+
onClick: () => onLoadSection(file, section),
|
|
5705
|
+
title: isSectionLoading ? "Loading file contents" : "Load file contents",
|
|
5706
|
+
type: "button",
|
|
5707
|
+
children: isSectionLoading ? "Loading..." : "Load"
|
|
5708
|
+
}) : null,
|
|
5709
|
+
!readOnly || allowViewedToggle ? /* @__PURE__ */ jsxs("button", {
|
|
5710
|
+
"aria-pressed": isViewed,
|
|
5711
|
+
className: `codiff-viewed-button${isViewed ? " active" : ""}`,
|
|
5712
|
+
onClick: () => onToggleViewed(file, isViewed, reviewIdentity),
|
|
5713
|
+
type: "button",
|
|
5714
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
5715
|
+
"aria-hidden": true,
|
|
5716
|
+
className: "codiff-viewed-checkbox",
|
|
5717
|
+
children: isViewed ? /* @__PURE__ */ jsx(CheckIcon, {
|
|
5718
|
+
className: "codiff-viewed-check",
|
|
5719
|
+
size: 10,
|
|
5720
|
+
weight: "bold"
|
|
5721
|
+
}) : null
|
|
5722
|
+
}), "Viewed"]
|
|
5723
|
+
}) : null
|
|
5724
|
+
]
|
|
5725
|
+
})]
|
|
5216
5726
|
});
|
|
5217
5727
|
}
|
|
5218
5728
|
const getReviewAuthorDisplayName = (author) => author.name || author.login || "Git user";
|
|
@@ -6701,8 +7211,27 @@ const getHunkSelectionRange = (hunk) => {
|
|
|
6701
7211
|
} : null;
|
|
6702
7212
|
};
|
|
6703
7213
|
const isSameSelection = (a, b) => a.start === b.start && a.end === b.end && a.side === b.side;
|
|
6704
|
-
function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedToggle = false, blocks, bottomInset = codeViewLayout.paddingBottom, codeQualityFindings = [], collapsed, comments, commitMetadata, diffLineHeight = 20, diffStyle, disableWorkerPool = false, expandedReviewKeys = emptyExpandedReviewKeys, files, focusCommentId, focusCommentRequest, forceExpandedPaths, gitIdentity, hunkNavigation, initialMarkdownPreviewSectionIds = emptyMarkdownPreviewSectionIds, isReadOnly = false, itemVersionByKey, keymap, loadingSectionIds, onActiveBlockChange, onAskCodex, onCommentDraftChange, onCreateComment, onDeleteComment, onFindDefinitions, onLoadImageContent, onLoadSection, onLoadSectionContents, onOpenDefinition, onOpenFile, onRefreshMarkdown, onResolveThread = noopResolveThread, onSaveCommentEdit, onSelectPathFromScroll, onSubmitComment, onToggleCollapsed, onToggleViewed, onUpdateComment, onUpdateSourceDescription, onUpdateSourceTitle, onUploadSourceDescriptionAsset, reviewIdentityByPath, scrollTarget, searchQuery, selectedPath, showSourceDescription = true, showWhitespace, source, sourceDescriptionActions, sourceDescriptionFooter, supportsReviewCommentActions, theme = "system", viewed, walkthroughNotes, wordWrap }) {
|
|
7214
|
+
function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedToggle = false, blocks, bottomInset = codeViewLayout.paddingBottom, codeEditingStore, codeQualityFindings = [], collapsed, comments, commitMetadata, diffLineHeight = 20, diffStyle, disableWorkerPool = false, expandedReviewKeys = emptyExpandedReviewKeys, expandUnchanged = false, files, focusCommentId, focusCommentRequest, forceExpandedPaths, gitIdentity, hunkNavigation, initialMarkdownPreviewSectionIds = emptyMarkdownPreviewSectionIds, isReadOnly = false, itemVersionByKey, keymap, loadingSectionIds, onActiveBlockChange, onAskCodex, onCommentDraftChange, onCreateComment, onDeleteComment, onFindDefinitions, onLoadImageContent, onLoadSection, onLoadSectionContents, onOpenDefinition, onOpenFile, onRefreshMarkdown, onResolveThread = noopResolveThread, onSaveCommentEdit, onSelectPathFromScroll, onSubmitComment, onToggleCollapsed, onToggleViewed, onUpdateComment, onUpdateSourceDescription, onUpdateSourceTitle, onUploadSourceDescriptionAsset, reviewIdentityByPath, scrollTarget, searchQuery, selectedPath, showSourceDescription = true, showWhitespace, source, sourceDescriptionActions, sourceDescriptionFooter, supportsReviewCommentActions, theme = "system", viewed, walkthroughNotes, wordWrap }) {
|
|
6705
7215
|
const codeViewRef = useRef(null);
|
|
7216
|
+
const [codeViewElement, setCodeViewElement] = useState(null);
|
|
7217
|
+
const [compactHeaders, setCompactHeaders] = useState(false);
|
|
7218
|
+
useLayoutEffect(() => {
|
|
7219
|
+
if (!codeViewElement) return;
|
|
7220
|
+
const measure = () => {
|
|
7221
|
+
const width = codeViewElement.getBoundingClientRect().width;
|
|
7222
|
+
if (width > 0) setCompactHeaders(width <= 760);
|
|
7223
|
+
};
|
|
7224
|
+
measure();
|
|
7225
|
+
const observer = new ResizeObserver(measure);
|
|
7226
|
+
observer.observe(codeViewElement);
|
|
7227
|
+
return () => observer.disconnect();
|
|
7228
|
+
}, [codeViewElement]);
|
|
7229
|
+
const headerHeight = compactHeaders ? 90 : codeViewItemMetrics.diffHeaderHeight;
|
|
7230
|
+
const itemMetrics = useMemo(() => ({
|
|
7231
|
+
...codeViewItemMetrics,
|
|
7232
|
+
diffHeaderHeight: headerHeight
|
|
7233
|
+
}), [headerHeight]);
|
|
7234
|
+
const codeViewStyle = { "--codiff-file-header-height": `${headerHeight}px` };
|
|
6706
7235
|
const getPlaceholderFile = useCodeViewPlaceholderFile();
|
|
6707
7236
|
const getAnnotations = useCodeViewAnnotations();
|
|
6708
7237
|
const [markdownPreviewHeights] = useState(() => /* @__PURE__ */ new Map());
|
|
@@ -6718,6 +7247,7 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
6718
7247
|
const focusedCommentEditorIdRef = useRef(null);
|
|
6719
7248
|
const definitionHighlightFrameRef = useRef(null);
|
|
6720
7249
|
const highlightFrameRef = useRef(null);
|
|
7250
|
+
const lastScrolledSearchMatchRef = useRef(null);
|
|
6721
7251
|
const ignoreNextLineSelectionEndRef = useRef(false);
|
|
6722
7252
|
const navigatedSelectionRef = useRef(null);
|
|
6723
7253
|
const initialMarkdownFiles = files.length > 0 ? files : blocks?.map((block) => block.file).filter((file) => file != null) ?? [];
|
|
@@ -7013,6 +7543,91 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
7013
7543
|
shouldShowCommitMessage,
|
|
7014
7544
|
sourceDescriptionItemId
|
|
7015
7545
|
]);
|
|
7546
|
+
const highlightedCodeItems = codeViewItems.filter((item) => itemMetadata.get(item.id)?.isSelected);
|
|
7547
|
+
const selectedCodeItemId = highlightedCodeItems.find((item) => item.id === selectedLines?.id && itemMetadata.get(item.id)?.section.kind === "unstaged")?.id ?? highlightedCodeItems.find((item) => itemMetadata.get(item.id)?.section.kind === "unstaged")?.id ?? highlightedCodeItems[0]?.id;
|
|
7548
|
+
const codeEditing = useCodeEditing({
|
|
7549
|
+
codeViewRef,
|
|
7550
|
+
items: codeViewItems,
|
|
7551
|
+
keymap,
|
|
7552
|
+
onLoadSectionContents,
|
|
7553
|
+
onRefresh: onRefreshMarkdown,
|
|
7554
|
+
selectedItemId: selectedCodeItemId,
|
|
7555
|
+
sourceKey,
|
|
7556
|
+
store: codeEditingStore
|
|
7557
|
+
});
|
|
7558
|
+
const canEditCodeItem = useCallback((item, meta) => meta != null && item.type === "diff" && !isReadOnly && !meta.section.binary && (meta.section.loadState == null || meta.section.loadState === "ready" || meta.section.loadState === "deferred") && !meta.isMarkdownPreview && meta.section.kind === "unstaged" && isEditableWorkingTreeSection(source.type, meta.file, meta.section) && onLoadSectionContents != null && window.codiff?.getRepositoryFile != null, [
|
|
7559
|
+
isReadOnly,
|
|
7560
|
+
onLoadSectionContents,
|
|
7561
|
+
source.type
|
|
7562
|
+
]);
|
|
7563
|
+
const startCodeEdit = useCallback((item, meta) => {
|
|
7564
|
+
if (meta.isCollapsed) onToggleCollapsed(meta.file, true, meta.reviewIdentity.key);
|
|
7565
|
+
codeEditing.startEdit(item, meta);
|
|
7566
|
+
}, [codeEditing, onToggleCollapsed]);
|
|
7567
|
+
const editAfterExpandRef = useRef(null);
|
|
7568
|
+
useEffect(() => {
|
|
7569
|
+
const pending = editAfterExpandRef.current;
|
|
7570
|
+
if (!pending) return;
|
|
7571
|
+
if (pending.sourceKey !== sourceKey || !highlightedCodeItems.some((item) => itemMetadata.get(item.id)?.file.path === pending.path)) {
|
|
7572
|
+
editAfterExpandRef.current = null;
|
|
7573
|
+
return;
|
|
7574
|
+
}
|
|
7575
|
+
const item = highlightedCodeItems.find((candidate) => {
|
|
7576
|
+
const meta = itemMetadata.get(candidate.id);
|
|
7577
|
+
return meta?.file.path === pending.path && canEditCodeItem(candidate, meta);
|
|
7578
|
+
});
|
|
7579
|
+
const meta = item && itemMetadata.get(item.id);
|
|
7580
|
+
if (item && meta) {
|
|
7581
|
+
editAfterExpandRef.current = null;
|
|
7582
|
+
startCodeEdit(item, meta);
|
|
7583
|
+
}
|
|
7584
|
+
}, [
|
|
7585
|
+
canEditCodeItem,
|
|
7586
|
+
highlightedCodeItems,
|
|
7587
|
+
itemMetadata,
|
|
7588
|
+
sourceKey,
|
|
7589
|
+
startCodeEdit
|
|
7590
|
+
]);
|
|
7591
|
+
useEffect(() => {
|
|
7592
|
+
const handleKeyDown = (event) => {
|
|
7593
|
+
if (event.defaultPrevented || event.repeat || event.isComposing || isNativeInputTarget(event.target) || !matchesShortcut(event, keymap, "editFile")) return;
|
|
7594
|
+
const item = codeViewItems.find((candidate) => candidate.id === selectedCodeItemId);
|
|
7595
|
+
const meta = item && itemMetadata.get(item.id);
|
|
7596
|
+
if (!item || !meta || !codeEditing.canStartEdit(item.id, meta.file.path)) return;
|
|
7597
|
+
if (!canEditCodeItem(item, meta)) {
|
|
7598
|
+
if (!meta.isCollapsed || !getVisibleDiffSections(meta.file, showWhitespace).some(({ fileDiff, section }) => canEditCodeItem({
|
|
7599
|
+
fileDiff,
|
|
7600
|
+
id: item.id,
|
|
7601
|
+
type: "diff"
|
|
7602
|
+
}, {
|
|
7603
|
+
...meta,
|
|
7604
|
+
section
|
|
7605
|
+
}))) return;
|
|
7606
|
+
event.preventDefault();
|
|
7607
|
+
editAfterExpandRef.current = {
|
|
7608
|
+
path: meta.file.path,
|
|
7609
|
+
sourceKey
|
|
7610
|
+
};
|
|
7611
|
+
onToggleCollapsed(meta.file, true, meta.reviewIdentity.key);
|
|
7612
|
+
return;
|
|
7613
|
+
}
|
|
7614
|
+
event.preventDefault();
|
|
7615
|
+
startCodeEdit(item, meta);
|
|
7616
|
+
};
|
|
7617
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
7618
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
7619
|
+
}, [
|
|
7620
|
+
canEditCodeItem,
|
|
7621
|
+
codeEditing,
|
|
7622
|
+
codeViewItems,
|
|
7623
|
+
itemMetadata,
|
|
7624
|
+
keymap,
|
|
7625
|
+
onToggleCollapsed,
|
|
7626
|
+
selectedCodeItemId,
|
|
7627
|
+
showWhitespace,
|
|
7628
|
+
sourceKey,
|
|
7629
|
+
startCodeEdit
|
|
7630
|
+
]);
|
|
7016
7631
|
const clearCommentLineHighlight = useCallback(() => {
|
|
7017
7632
|
codeViewRef.current?.clearSelectedLines();
|
|
7018
7633
|
navigatedSelectionRef.current = null;
|
|
@@ -7192,10 +7807,10 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
7192
7807
|
diffStyle,
|
|
7193
7808
|
enableGutterUtility: !isReadOnly,
|
|
7194
7809
|
enableLineSelection: !isReadOnly,
|
|
7195
|
-
expandUnchanged:
|
|
7810
|
+
expandUnchanged: expandUnchanged && loadDiffFiles != null,
|
|
7196
7811
|
expansionLineCount: 100,
|
|
7197
7812
|
hunkSeparators: "line-info-basic",
|
|
7198
|
-
itemMetrics
|
|
7813
|
+
itemMetrics,
|
|
7199
7814
|
layout: {
|
|
7200
7815
|
...codeViewLayout,
|
|
7201
7816
|
paddingBottom: bottomInset
|
|
@@ -7203,11 +7818,12 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
7203
7818
|
lineHoverHighlight: "both",
|
|
7204
7819
|
loadDiffFiles,
|
|
7205
7820
|
onGutterUtilityClick: (range, context) => {
|
|
7206
|
-
if (isReadOnly) return;
|
|
7821
|
+
if (isReadOnly || context.item.edit) return;
|
|
7207
7822
|
ignoreNextLineSelectionEndRef.current = context.item.type === "diff";
|
|
7208
7823
|
createCommentForRange(range, context);
|
|
7209
7824
|
},
|
|
7210
7825
|
onLineClick: (line, context) => {
|
|
7826
|
+
if (context.item.edit) return;
|
|
7211
7827
|
const lineElement = "lineElement" in line ? line.lineElement : null;
|
|
7212
7828
|
if (isPrimaryModifier(line.event) && onFindDefinitions) {
|
|
7213
7829
|
line.event.preventDefault();
|
|
@@ -7272,7 +7888,7 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
7272
7888
|
});
|
|
7273
7889
|
},
|
|
7274
7890
|
onLineSelectionEnd: (range, context) => {
|
|
7275
|
-
if (isReadOnly) return;
|
|
7891
|
+
if (isReadOnly || context.item.edit) return;
|
|
7276
7892
|
if (ignoreNextLineSelectionEndRef.current) {
|
|
7277
7893
|
ignoreNextLineSelectionEndRef.current = false;
|
|
7278
7894
|
return;
|
|
@@ -7287,10 +7903,10 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
7287
7903
|
node.classList.toggle("codiff-selected-item", metadata?.isSelected === true || selectedHeaderItemIds.has(context.item.id));
|
|
7288
7904
|
node.classList.toggle("codiff-markdown-preview-item", metadata?.isMarkdownPreview === true);
|
|
7289
7905
|
node.classList.toggle("codiff-image-preview-item", context.item.type === "file" && Boolean(metadata && canRenderImagePreview(metadata.file.path, metadata.section)));
|
|
7290
|
-
node.classList.toggle("codiff-loadable-summary-item", metadata != null && shouldLoadDiffSectionContents(metadata.section));
|
|
7906
|
+
node.classList.toggle("codiff-loadable-summary-item", !context.item.edit && metadata != null && shouldLoadDiffSectionContents(metadata.section));
|
|
7291
7907
|
node.classList.toggle("codiff-loading-summary-item", Boolean(metadata && loadingSectionIds.has(metadata.section.id)));
|
|
7292
7908
|
if (definitionModifierActiveRef.current) window.requestAnimationFrame(() => {
|
|
7293
|
-
if (definitionModifierActiveRef.current && node.isConnected) applyIdentifierNavigationState([{ element: node }], true);
|
|
7909
|
+
if (definitionModifierActiveRef.current && node.isConnected && !codeViewRef.current?.getItem(context.item.id)?.edit) applyIdentifierNavigationState([{ element: node }], true);
|
|
7294
7910
|
});
|
|
7295
7911
|
},
|
|
7296
7912
|
overflow: wordWrap ? "wrap" : "scroll",
|
|
@@ -7301,14 +7917,17 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
7301
7917
|
},
|
|
7302
7918
|
themeType: theme,
|
|
7303
7919
|
tokenizeMaxLength: 1e5,
|
|
7304
|
-
unsafeCSS: codeViewUnsafeCSS
|
|
7920
|
+
unsafeCSS: codeViewUnsafeCSS,
|
|
7921
|
+
useTokenTransformer: workerHighlighterOptions.useTokenTransformer
|
|
7305
7922
|
}), [
|
|
7306
7923
|
bottomInset,
|
|
7307
7924
|
cancelPendingEmptyCommentDeletes,
|
|
7308
7925
|
createCommentForRange,
|
|
7309
7926
|
diffStyle,
|
|
7927
|
+
expandUnchanged,
|
|
7310
7928
|
isReadOnly,
|
|
7311
7929
|
itemMetadata,
|
|
7930
|
+
itemMetrics,
|
|
7312
7931
|
loadDiffFiles,
|
|
7313
7932
|
loadingSectionIds,
|
|
7314
7933
|
onCreateComment,
|
|
@@ -7478,7 +8097,6 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
7478
8097
|
const viewer = handle?.getInstance();
|
|
7479
8098
|
if (!handle || !viewer) return;
|
|
7480
8099
|
handledHunkNavRef.current = hunkNavigation.request;
|
|
7481
|
-
const headerHeight = codeViewItemMetrics.diffHeaderHeight;
|
|
7482
8100
|
const anchors = [];
|
|
7483
8101
|
const seen = /* @__PURE__ */ new Set();
|
|
7484
8102
|
const lineScrollTarget = (id, lineNumber, side) => ({
|
|
@@ -7597,6 +8215,7 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
7597
8215
|
clearCommentLineHighlight,
|
|
7598
8216
|
diffLineHeight,
|
|
7599
8217
|
diffStyle,
|
|
8218
|
+
headerHeight,
|
|
7600
8219
|
hunkNavigation,
|
|
7601
8220
|
itemMetadata,
|
|
7602
8221
|
items
|
|
@@ -7625,7 +8244,7 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
7625
8244
|
if (highlightFrameRef.current != null) window.cancelAnimationFrame(highlightFrameRef.current);
|
|
7626
8245
|
highlightFrameRef.current = window.requestAnimationFrame(() => {
|
|
7627
8246
|
highlightFrameRef.current = null;
|
|
7628
|
-
applySearchHighlights(viewer.getRenderedItems(), searchQuery, resolvedActiveSearchMatch);
|
|
8247
|
+
applySearchHighlights(viewer.getRenderedItems().filter(({ item }) => !item.edit), searchQuery, resolvedActiveSearchMatch);
|
|
7629
8248
|
});
|
|
7630
8249
|
}, [resolvedActiveSearchMatch, searchQuery]);
|
|
7631
8250
|
const updateRenderedIdentifierNavigation = useCallback((active, viewer) => {
|
|
@@ -7636,12 +8255,12 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
7636
8255
|
definitionHighlightFrameRef.current = null;
|
|
7637
8256
|
}
|
|
7638
8257
|
if (!active) {
|
|
7639
|
-
applyIdentifierNavigationState(nextViewer.getRenderedItems(), false);
|
|
8258
|
+
applyIdentifierNavigationState(nextViewer.getRenderedItems().filter(({ item }) => !item.edit), false);
|
|
7640
8259
|
return;
|
|
7641
8260
|
}
|
|
7642
8261
|
definitionHighlightFrameRef.current = window.requestAnimationFrame(() => {
|
|
7643
8262
|
definitionHighlightFrameRef.current = null;
|
|
7644
|
-
applyIdentifierNavigationState(nextViewer.getRenderedItems(), true);
|
|
8263
|
+
applyIdentifierNavigationState(nextViewer.getRenderedItems().filter(({ item }) => !item.edit), true);
|
|
7645
8264
|
});
|
|
7646
8265
|
}, []);
|
|
7647
8266
|
const scheduleStickyHeaderStateUpdate = useCallback((viewer) => {
|
|
@@ -7716,9 +8335,22 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
7716
8335
|
}, []);
|
|
7717
8336
|
useEffect(() => invalidateDefinitionLookup, [invalidateDefinitionLookup]);
|
|
7718
8337
|
useEffect(() => {
|
|
8338
|
+
if (!activeSearchMatch) {
|
|
8339
|
+
lastScrolledSearchMatchRef.current = null;
|
|
8340
|
+
return;
|
|
8341
|
+
}
|
|
7719
8342
|
const handle = codeViewRef.current;
|
|
7720
8343
|
const viewer = handle?.getInstance();
|
|
7721
8344
|
if (!handle || !viewer || !resolvedActiveSearchMatch) return;
|
|
8345
|
+
const lastScrolledSearchMatch = lastScrolledSearchMatchRef.current;
|
|
8346
|
+
if (lastScrolledSearchMatch?.match === activeSearchMatch && lastScrolledSearchMatch.itemId === resolvedActiveSearchMatch.itemId) {
|
|
8347
|
+
scheduleSearchHighlights();
|
|
8348
|
+
return;
|
|
8349
|
+
}
|
|
8350
|
+
lastScrolledSearchMatchRef.current = {
|
|
8351
|
+
itemId: resolvedActiveSearchMatch.itemId,
|
|
8352
|
+
match: activeSearchMatch
|
|
8353
|
+
};
|
|
7722
8354
|
if (resolvedActiveSearchMatch.lineNumber == null) handle.scrollTo({
|
|
7723
8355
|
align: "center",
|
|
7724
8356
|
behavior: "smooth-auto",
|
|
@@ -7735,7 +8367,11 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
7735
8367
|
type: "line"
|
|
7736
8368
|
});
|
|
7737
8369
|
scheduleSearchHighlights();
|
|
7738
|
-
}, [
|
|
8370
|
+
}, [
|
|
8371
|
+
activeSearchMatch,
|
|
8372
|
+
resolvedActiveSearchMatch,
|
|
8373
|
+
scheduleSearchHighlights
|
|
8374
|
+
]);
|
|
7739
8375
|
const renderCodeViewHeader = useCallback(() => /* @__PURE__ */ jsxs("div", {
|
|
7740
8376
|
className: "codiff-source-description-panel codiff-code-view-source-description",
|
|
7741
8377
|
children: [/* @__PURE__ */ jsx(SourceDescriptionHeader, {
|
|
@@ -7783,10 +8419,47 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
7783
8419
|
toggleSourceDescriptionCollapsed
|
|
7784
8420
|
]);
|
|
7785
8421
|
const renderCustomHeader = useCallback((item) => {
|
|
7786
|
-
const
|
|
7787
|
-
|
|
8422
|
+
const editSession = codeEditing.sessions.get(item.id);
|
|
8423
|
+
const isEditing = editSession != null;
|
|
8424
|
+
const meta = editSession?.metadata ?? itemMetadata.get(item.id);
|
|
8425
|
+
const isBusy = codeEditing.isBusy(item.id);
|
|
8426
|
+
const isAutosaving = editSession?.saving && !isBusy;
|
|
8427
|
+
const canEdit = canEditCodeItem(item, meta);
|
|
8428
|
+
const editError = codeEditing.getError(item.id);
|
|
8429
|
+
return meta ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(CodeViewHeader, {
|
|
7788
8430
|
allowViewedToggle,
|
|
7789
8431
|
canCreateFileComment: canCreateFileComments,
|
|
8432
|
+
editActions: isEditing ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Button, {
|
|
8433
|
+
"aria-label": `${editSession?.hasChanges ? "Revert edits to" : "Cancel editing"} ${meta.file.path}`,
|
|
8434
|
+
className: "codiff-edit-revert-button",
|
|
8435
|
+
"data-autosaving": isAutosaving ? "" : void 0,
|
|
8436
|
+
disabled: isBusy || editSession.saving,
|
|
8437
|
+
onClick: () => void codeEditing.revertEdit(item.id),
|
|
8438
|
+
title: editSession?.hasChanges ? "Restore the file to when editing started" : "Cancel editing",
|
|
8439
|
+
type: "button",
|
|
8440
|
+
variant: editSession?.hasChanges ? "destructive" : "default",
|
|
8441
|
+
children: editSession?.hasChanges ? "Revert" : "Cancel"
|
|
8442
|
+
}), /* @__PURE__ */ jsxs(Button, {
|
|
8443
|
+
"aria-label": `Done editing ${meta.file.path}`,
|
|
8444
|
+
"data-autosaving": isAutosaving ? "" : void 0,
|
|
8445
|
+
disabled: isBusy || editSession.saving,
|
|
8446
|
+
onClick: () => void codeEditing.doneEdit(item.id),
|
|
8447
|
+
title: "Finish editing; changes are saved automatically",
|
|
8448
|
+
type: "button",
|
|
8449
|
+
children: [/* @__PURE__ */ jsx(CheckIcon, {
|
|
8450
|
+
"aria-hidden": true,
|
|
8451
|
+
size: 14,
|
|
8452
|
+
weight: "bold"
|
|
8453
|
+
}), "Done"]
|
|
8454
|
+
})] }) : canEdit ? /* @__PURE__ */ jsx(CodeEditButton, {
|
|
8455
|
+
disabled: !codeEditing.canStartEdit(item.id, meta.file.path),
|
|
8456
|
+
isBusy,
|
|
8457
|
+
item,
|
|
8458
|
+
meta,
|
|
8459
|
+
onEdit: () => startCodeEdit(item, meta),
|
|
8460
|
+
onPrepare: codeEditing.prepareEdit
|
|
8461
|
+
}) : null,
|
|
8462
|
+
isEditing,
|
|
7790
8463
|
isSectionLoading: loadingSectionIds.has(meta.section.id),
|
|
7791
8464
|
meta,
|
|
7792
8465
|
onCreateFileComment: () => createFileComment(meta, item.id),
|
|
@@ -7796,11 +8469,17 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
7796
8469
|
onToggleMarkdownPreview: toggleMarkdownPreview,
|
|
7797
8470
|
onToggleViewed,
|
|
7798
8471
|
readOnly: isReadOnly
|
|
7799
|
-
})
|
|
8472
|
+
}), editError ? /* @__PURE__ */ jsx("div", {
|
|
8473
|
+
className: "codiff-code-edit-error",
|
|
8474
|
+
role: "alert",
|
|
8475
|
+
children: editError
|
|
8476
|
+
}) : null] }) : null;
|
|
7800
8477
|
}, [
|
|
7801
8478
|
allowViewedToggle,
|
|
7802
8479
|
canCreateFileComments,
|
|
8480
|
+
canEditCodeItem,
|
|
7803
8481
|
createFileComment,
|
|
8482
|
+
codeEditing,
|
|
7804
8483
|
itemMetadata,
|
|
7805
8484
|
isReadOnly,
|
|
7806
8485
|
loadingSectionIds,
|
|
@@ -7808,6 +8487,7 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
7808
8487
|
onOpenFile,
|
|
7809
8488
|
onToggleCollapsed,
|
|
7810
8489
|
onToggleViewed,
|
|
8490
|
+
startCodeEdit,
|
|
7811
8491
|
toggleMarkdownPreview
|
|
7812
8492
|
]);
|
|
7813
8493
|
const renderAnnotation = useCallback((annotation, item) => {
|
|
@@ -7909,10 +8589,14 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
7909
8589
|
scheduleStickyHeaderStateUpdate,
|
|
7910
8590
|
updateRenderedIdentifierNavigation
|
|
7911
8591
|
]);
|
|
7912
|
-
|
|
7913
|
-
className:
|
|
8592
|
+
const codeView = /* @__PURE__ */ jsx(CodeView, {
|
|
8593
|
+
className: `code-view${compactHeaders ? " compact-file-headers" : ""}`,
|
|
8594
|
+
containerRef: setCodeViewElement,
|
|
7914
8595
|
disableWorkerPool,
|
|
7915
|
-
|
|
8596
|
+
editorOptions: codeEditing.editorOptions,
|
|
8597
|
+
items: codeEditing.items,
|
|
8598
|
+
onItemEditChange: codeEditing.onItemEditChange,
|
|
8599
|
+
onItemEditComplete: codeEditing.onItemEditComplete,
|
|
7916
8600
|
onScroll: handleScroll,
|
|
7917
8601
|
onSelectedLinesChange: setCodeViewSelectedLines,
|
|
7918
8602
|
options: codeViewOptions,
|
|
@@ -7920,14 +8604,20 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
7920
8604
|
renderAnnotation,
|
|
7921
8605
|
renderCodeViewHeader: sourceDescriptionItemId ? renderCodeViewHeader : void 0,
|
|
7922
8606
|
renderCustomHeader,
|
|
7923
|
-
selectedLines: isReadOnly ? null : selectedLines
|
|
7924
|
-
|
|
8607
|
+
selectedLines: isReadOnly ? null : selectedLines,
|
|
8608
|
+
style: codeViewStyle
|
|
8609
|
+
});
|
|
8610
|
+
const renderedCodeView = disableWorkerPool ? codeView : /* @__PURE__ */ jsx(WorkerPoolContextProvider, {
|
|
7925
8611
|
highlighterOptions: workerHighlighterOptions,
|
|
7926
8612
|
poolOptions: workerPoolOptions,
|
|
7927
8613
|
children: /* @__PURE__ */ jsx(CodeView, {
|
|
7928
|
-
className:
|
|
8614
|
+
className: `code-view${compactHeaders ? " compact-file-headers" : ""}`,
|
|
8615
|
+
containerRef: setCodeViewElement,
|
|
7929
8616
|
disableWorkerPool: false,
|
|
7930
|
-
|
|
8617
|
+
editorOptions: codeEditing.editorOptions,
|
|
8618
|
+
items: codeEditing.items,
|
|
8619
|
+
onItemEditChange: codeEditing.onItemEditChange,
|
|
8620
|
+
onItemEditComplete: codeEditing.onItemEditComplete,
|
|
7931
8621
|
onScroll: handleScroll,
|
|
7932
8622
|
onSelectedLinesChange: setCodeViewSelectedLines,
|
|
7933
8623
|
options: codeViewOptions,
|
|
@@ -7935,8 +8625,13 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
|
|
|
7935
8625
|
renderAnnotation,
|
|
7936
8626
|
renderCodeViewHeader: sourceDescriptionItemId ? renderCodeViewHeader : void 0,
|
|
7937
8627
|
renderCustomHeader,
|
|
7938
|
-
selectedLines: isReadOnly ? null : selectedLines
|
|
8628
|
+
selectedLines: isReadOnly ? null : selectedLines,
|
|
8629
|
+
style: codeViewStyle
|
|
7939
8630
|
})
|
|
8631
|
+
});
|
|
8632
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(EditProvider, {
|
|
8633
|
+
createEditor: createCodeEditor,
|
|
8634
|
+
children: renderedCodeView
|
|
7940
8635
|
}), definitionLookup?.sourceKey === sourceKey && onOpenDefinition ? /* @__PURE__ */ jsx(DefinitionPopover, {
|
|
7941
8636
|
anchor: definitionLookup.anchor,
|
|
7942
8637
|
getDestination: (candidate) => getDefinitionDiffTarget(candidate) ? "diff" : candidate.canOpenInEditor ? "editor" : "unavailable",
|
|
@@ -9653,54 +10348,6 @@ function useReviewFileState({ initialSelectedPath = null, onViewedChange } = {})
|
|
|
9653
10348
|
};
|
|
9654
10349
|
}
|
|
9655
10350
|
//#endregion
|
|
9656
|
-
//#region config/defaults.ts
|
|
9657
|
-
const defaultConfigTemplate = {
|
|
9658
|
-
settings: {
|
|
9659
|
-
"agentBackend": "codex",
|
|
9660
|
-
"checkForUpdates": true,
|
|
9661
|
-
"claudeModel": "claude-sonnet-4-6",
|
|
9662
|
-
"codeFontFamily": "",
|
|
9663
|
-
"codeFontSize": 13,
|
|
9664
|
-
"copyCommentsOnClose": false,
|
|
9665
|
-
"diffStyle": "split",
|
|
9666
|
-
"editorCommand": "",
|
|
9667
|
-
"lastRepositoryPath": "",
|
|
9668
|
-
"openAIModel": "gpt-5.6-terra",
|
|
9669
|
-
"opencodeModel": "opencode-default",
|
|
9670
|
-
"piModel": "pi-default",
|
|
9671
|
-
"reviewCommentsPrefix": "# Address these Review Comments",
|
|
9672
|
-
"sidebarPosition": "left",
|
|
9673
|
-
"showOutdated": false,
|
|
9674
|
-
"showWhitespace": false,
|
|
9675
|
-
"theme": "system",
|
|
9676
|
-
"walkthroughPrompt": "",
|
|
9677
|
-
"wordWrap": false
|
|
9678
|
-
},
|
|
9679
|
-
keymap: {
|
|
9680
|
-
"askAgent": "Mod+Alt+Enter",
|
|
9681
|
-
"closeSearch": "Escape",
|
|
9682
|
-
"commandBar": "Mod+k",
|
|
9683
|
-
"diffSearch": "Mod+f",
|
|
9684
|
-
"discardComment": "Escape",
|
|
9685
|
-
"fileFilter": "Mod+p",
|
|
9686
|
-
"nextHunk": ["Ctrl+ArrowDown", "j"],
|
|
9687
|
-
"nextSearchMatch": "Enter",
|
|
9688
|
-
"openFile": "Mod+Shift+o",
|
|
9689
|
-
"prevHunk": ["Ctrl+ArrowUp", "k"],
|
|
9690
|
-
"prevSearchMatch": "Shift+Enter",
|
|
9691
|
-
"shortcutsHelp": "Shift+?",
|
|
9692
|
-
"submitComment": "Mod+Enter",
|
|
9693
|
-
"toggleSidebar": "Mod+Shift+b",
|
|
9694
|
-
"toggleWordWrap": "Alt+z"
|
|
9695
|
-
}
|
|
9696
|
-
};
|
|
9697
|
-
const createDefaultConfig = () => ({
|
|
9698
|
-
keymap: { ...defaultConfigTemplate.keymap },
|
|
9699
|
-
settings: { ...defaultConfigTemplate.settings }
|
|
9700
|
-
});
|
|
9701
|
-
Object.freeze({ ...defaultConfigTemplate.keymap });
|
|
9702
|
-
Object.freeze({ ...defaultConfigTemplate.settings });
|
|
9703
|
-
//#endregion
|
|
9704
10351
|
//#region lib/app-constants.ts
|
|
9705
10352
|
const AGENT_LABELS = {
|
|
9706
10353
|
claude: "Claude Code",
|