Skip to content

Commit 5a42b71

Browse files
authored
Preserve file selection across reloads (#40)
1 parent 8beac88 commit 5a42b71

10 files changed

Lines changed: 911 additions & 159 deletions

File tree

electron/main.cjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,9 @@ ipcMain.handle('codiff:getRepositoryState', async (event, source) => {
758758
const repositoryPath = windowRepositories.get(event.sender.id) || getLaunchPath();
759759
const launchOptions = windowLaunchOptions.get(event.sender.id);
760760
const initialState = !source ? windowInitialRepositoryStates.get(event.sender.id) : undefined;
761+
if (initialState) {
762+
windowInitialRepositoryStates.delete(event.sender.id);
763+
}
761764
const state = initialState
762765
? await initialState
763766
: await readRepositoryState(repositoryPath, source || launchOptions?.source);

src/App.tsx

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ import {
4949
shouldLoadDiffSectionContents,
5050
} from './lib/diff.ts';
5151
import { compactPath, fuzzyMatches, sortFiles } from './lib/files.ts';
52+
import {
53+
consumeReloadSelection,
54+
getReloadDeltaPaths,
55+
getReloadSelectionPath,
56+
writeReloadSelection,
57+
} from './lib/reload-selection.ts';
5258
import {
5359
buildReviewCommentsMarkdown,
5460
getCommentKey,
@@ -133,6 +139,7 @@ export default function App() {
133139
useState<CodexSkillStatus>(defaultCodexSkillStatus);
134140
const [preferences, setPreferences] = useState<CodiffPreferences>(defaultPreferences);
135141
const [reviewComments, setReviewComments] = useState<ReadonlyArray<ReviewComment>>([]);
142+
const [reloadDeltaPaths, setReloadDeltaPaths] = useState<ReadonlySet<string>>(() => new Set());
136143
const [pullRequestReviewSubmitting, setPullRequestReviewSubmitting] =
137144
useState<PullRequestReviewEvent | null>(null);
138145
const [scrollTarget, setScrollTarget] = useState<{ path: string; request: number } | null>(null);
@@ -273,6 +280,22 @@ export default function App() {
273280
[bumpItemVersion],
274281
);
275282

283+
const scrollPathIntoReview = useCallback((path: string) => {
284+
setScrollTarget((current) => ({
285+
path,
286+
request: (current?.request ?? 0) + 1,
287+
}));
288+
programmaticScrollPathRef.current = path;
289+
if (programmaticScrollTimerRef.current != null) {
290+
window.clearTimeout(programmaticScrollTimerRef.current);
291+
}
292+
293+
programmaticScrollTimerRef.current = window.setTimeout(() => {
294+
programmaticScrollPathRef.current = null;
295+
programmaticScrollTimerRef.current = null;
296+
}, 1200);
297+
}, []);
298+
276299
const saveCurrentSourceSession = useCallback(() => {
277300
const currentState = stateRef.current;
278301
if (!currentState) {
@@ -293,6 +316,7 @@ export default function App() {
293316
let canceled = false;
294317

295318
const load = async () => {
319+
const reloadSelection = consumeReloadSelection();
296320
const nextLaunchOptions = await window.codiff.getLaunchOptions();
297321
if (canceled) {
298322
return;
@@ -315,7 +339,7 @@ export default function App() {
315339
}
316340
setTerminalHelperStatus(nextTerminalHelperStatus);
317341

318-
const nextState = await window.codiff.getRepositoryState();
342+
const nextState = await window.codiff.getRepositoryState(reloadSelection?.source);
319343

320344
if (canceled) {
321345
return;
@@ -375,6 +399,8 @@ export default function App() {
375399
const initialFiles = nextLaunchOptions.walkthrough
376400
? orderFilesByWalkthrough(orderedState.files, nextWalkthrough)
377401
: orderedState.files;
402+
const reloadSelectedPath = getReloadSelectionPath(reloadSelection, orderedState);
403+
const nextReloadDeltaPaths = getReloadDeltaPaths(reloadSelection, orderedState);
378404

379405
setHistoryEntries(history.entries);
380406
setHistoryHasMore(history.entries.length >= HISTORY_PAGE_SIZE);
@@ -396,9 +422,14 @@ export default function App() {
396422
setItemVersionByPath({});
397423
setFocusCommentId(null);
398424
setFocusCommentRequest(0);
425+
setReloadDeltaPaths(nextReloadDeltaPaths);
399426
setReviewComments(getReviewCommentsFromState(orderedState));
400427
setViewed(nextViewed);
401-
setSelectedPath((current) => current ?? initialFiles[0]?.path ?? null);
428+
const nextSelectedPath = reloadSelectedPath ?? initialFiles[0]?.path ?? null;
429+
setSelectedPath(nextSelectedPath);
430+
if (reloadSelectedPath) {
431+
scrollPathIntoReview(reloadSelectedPath);
432+
}
402433
};
403434

404435
load().catch((error: unknown) => {
@@ -413,7 +444,7 @@ export default function App() {
413444
return () => {
414445
canceled = true;
415446
};
416-
}, []);
447+
}, [scrollPathIntoReview]);
417448

418449
useEffect(
419450
() =>
@@ -861,21 +892,25 @@ export default function App() {
861892
setSelectedPath(path);
862893
}, []);
863894

864-
const activatePath = useCallback((path: string) => {
865-
setSelectedPath(path);
866-
setScrollTarget((current) => ({
867-
path,
868-
request: (current?.request ?? 0) + 1,
869-
}));
870-
programmaticScrollPathRef.current = path;
871-
if (programmaticScrollTimerRef.current != null) {
872-
window.clearTimeout(programmaticScrollTimerRef.current);
873-
}
895+
const activatePath = useCallback(
896+
(path: string) => {
897+
setSelectedPath(path);
898+
scrollPathIntoReview(path);
899+
},
900+
[scrollPathIntoReview],
901+
);
874902

875-
programmaticScrollTimerRef.current = window.setTimeout(() => {
876-
programmaticScrollPathRef.current = null;
877-
programmaticScrollTimerRef.current = null;
878-
}, 1200);
903+
const reloadWindow = useCallback(() => {
904+
window.location.reload();
905+
}, []);
906+
907+
useEffect(() => {
908+
const writeCurrentReloadSelection = () => {
909+
writeReloadSelection(stateRef.current, selectedPathRef.current);
910+
};
911+
912+
window.addEventListener('beforeunload', writeCurrentReloadSelection);
913+
return () => window.removeEventListener('beforeunload', writeCurrentReloadSelection);
879914
}, []);
880915

881916
const selectSource = useCallback(
@@ -894,6 +929,7 @@ export default function App() {
894929
setLoadError(null);
895930
setFocusCommentId(null);
896931
setFocusCommentRequest(0);
932+
setReloadDeltaPaths(new Set());
897933
setDiffSearchQuery('');
898934
setActiveDiffSearchMatchIndex(0);
899935
setScrollTarget(null);
@@ -936,6 +972,7 @@ export default function App() {
936972
setCollapsed(new Set(nextCollapsed));
937973
setItemVersionByPath({});
938974
setReviewComments(session?.reviewComments ?? getReviewCommentsFromState(orderedState));
975+
setReloadDeltaPaths(new Set());
939976
setViewed(nextViewed);
940977
setSelectedPath(nextSelectedPath);
941978
setWalkthrough(session?.walkthrough ?? null);
@@ -1248,7 +1285,7 @@ export default function App() {
12481285
title: 'Toggle Word Wrap',
12491286
}),
12501287
registry.register({
1251-
execute: () => window.location.reload(),
1288+
execute: reloadWindow,
12521289
id: 'reload',
12531290
title: 'Reload Window',
12541291
}),
@@ -1260,7 +1297,14 @@ export default function App() {
12601297
unregister();
12611298
}
12621299
};
1263-
}, [bumpItemVersion, changeSidebarMode, expandSidebar, openDiffSearch, toggleSidebar]);
1300+
}, [
1301+
bumpItemVersion,
1302+
changeSidebarMode,
1303+
expandSidebar,
1304+
openDiffSearch,
1305+
reloadWindow,
1306+
toggleSidebar,
1307+
]);
12641308

12651309
const toggleCollapsed = useCallback(
12661310
(file: ChangedFile, isCollapsed: boolean) => {
@@ -1744,6 +1788,7 @@ export default function App() {
17441788
</div>
17451789
) : null}
17461790
<RepositoryChangeBanner
1791+
onReload={reloadWindow}
17471792
visible={localChangesDetected && (pendingSource ?? state.source).type === 'working-tree'}
17481793
/>
17491794
<DiffSearchPanel
@@ -1828,6 +1873,7 @@ export default function App() {
18281873
onSelectPath={selectPath}
18291874
onSelectSource={selectSource}
18301875
pullRequestSource={historySource?.type === 'pull-request' ? historySource : null}
1876+
reloadDeltaPaths={reloadDeltaPaths}
18311877
searchQuery={sidebarMode === 'history' ? historySearchQuery : fileSearchQuery}
18321878
selectedPath={visibleSelectedPath}
18331879
showWhitespace={showWhitespace}

0 commit comments

Comments
 (0)