3.9 KB106 lines
Blame
1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8import type {CommitRev} from '../common';
9import type {PartiallySelectedDiffCommit} from '../diffSplitTypes';
10
11import {List} from 'immutable';
12import {getDefaultStore} from 'jotai';
13import serverAPI from '../../ClientToServerAPI';
14import {readAtom, writeAtom} from '../../jotaiUtils';
15import {registerDisposable} from '../../utils';
16import {applyDiffSplit} from '../diffSplit';
17import {next} from '../revMath';
18import {editingStackIntentionHashes, SplitRangeRecord, stackEditState} from './stackEditState';
19
20// Helper function to apply the AI split commits to the stack edit state
21function applyAISplitCommits(commits: PartiallySelectedDiffCommit[]) {
22 // Get the current stack edit state
23 const state = readAtom(stackEditState);
24
25 if (state.history.state !== 'hasValue') {
26 throw new Error('Stack edit state is not loaded');
27 }
28
29 const history = state.history.value;
30 const commitStack = history.current.state;
31
32 // When the intention is 'split', we're splitting a single commit
33 const intention = state.intention;
34 if (intention !== 'split') {
35 throw new Error('Cannot apply AI split when not in split mode');
36 }
37
38 // In split mode, startRev is 1 and endRev is size-1
39 const startRev = 1 as CommitRev;
40 const endRev = (commitStack.size - 1) as CommitRev;
41
42 // Extract a dense substack containing only the commit(s) to be split
43 // For split mode with a single commit, this is just [startRev]
44 const subStack = commitStack.denseSubStack(List([startRev]));
45
46 // Apply the diff split to the first commit in the subStack (position 0 relative to subStack)
47 const newSubStack = applyDiffSplit(subStack, 0 as CommitRev, commits);
48
49 // Replace the [start, end+1] range with the new stack in the commit stack
50 const newCommitStack = commitStack.applySubStack(startRev, next(endRev), newSubStack);
51
52 // Calculate the split range for UI selection
53 const endOffset = newCommitStack.size - commitStack.size;
54
55 // The split commits start at position startRev (the first new split commit)
56 // and end at position startRev + endOffset
57 const startKey = newCommitStack.get(startRev)?.key ?? '';
58 const endKey = newCommitStack.get(next(startRev, endOffset))?.key ?? '';
59 const splitRange = SplitRangeRecord({startKey, endKey});
60
61 // Update the state with the new split
62 writeAtom(stackEditState, prev => {
63 if (prev.history.state !== 'hasValue') {
64 return prev;
65 }
66
67 return {
68 ...prev,
69 history: {
70 state: 'hasValue' as const,
71 value: prev.history.value.push(newCommitStack, {name: 'splitWithAI'}, {splitRange}),
72 },
73 };
74 });
75}
76
77// Handle openSplitViewForCommit message - opens the split UI for a specific commit
78// and optionally applies AI split commits after the stack is loaded
79registerDisposable(
80 serverAPI,
81 serverAPI.onMessageOfType('openSplitViewForCommit', event => {
82 // Open the split panel by setting the editing stack intention
83 // Use getDefaultStore().set() directly since editingStackIntentionHashes has a custom write type
84 const store = getDefaultStore();
85 store.set(editingStackIntentionHashes, ['split', new Set([event.commitHash])]);
86
87 // If commits are provided, wait for the stack to load and then apply the split
88 if (event.commits && event.commits.length > 0) {
89 const commitsToApply = event.commits;
90 let hasApplied = false;
91 const unsubscribe = store.sub(stackEditState, () => {
92 if (hasApplied) {
93 return;
94 }
95 const state = store.get(stackEditState);
96 if (state.history.state === 'hasValue' && state.intention === 'split') {
97 // Stack is loaded, apply the AI split commits
98 hasApplied = true;
99 unsubscribe();
100 applyAISplitCommits(commitsToApply);
101 }
102 });
103 }
104 }),
105);
106