addons/isl/src/stackEdit/ui/AISplitMessageHandlers.tsblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import type {CommitRev} from '../common';
b69ab319import type {PartiallySelectedDiffCommit} from '../diffSplitTypes';
b69ab3110
b69ab3111import {List} from 'immutable';
b69ab3112import {getDefaultStore} from 'jotai';
b69ab3113import serverAPI from '../../ClientToServerAPI';
b69ab3114import {readAtom, writeAtom} from '../../jotaiUtils';
b69ab3115import {registerDisposable} from '../../utils';
b69ab3116import {applyDiffSplit} from '../diffSplit';
b69ab3117import {next} from '../revMath';
b69ab3118import {editingStackIntentionHashes, SplitRangeRecord, stackEditState} from './stackEditState';
b69ab3119
b69ab3120// Helper function to apply the AI split commits to the stack edit state
b69ab3121function applyAISplitCommits(commits: PartiallySelectedDiffCommit[]) {
b69ab3122 // Get the current stack edit state
b69ab3123 const state = readAtom(stackEditState);
b69ab3124
b69ab3125 if (state.history.state !== 'hasValue') {
b69ab3126 throw new Error('Stack edit state is not loaded');
b69ab3127 }
b69ab3128
b69ab3129 const history = state.history.value;
b69ab3130 const commitStack = history.current.state;
b69ab3131
b69ab3132 // When the intention is 'split', we're splitting a single commit
b69ab3133 const intention = state.intention;
b69ab3134 if (intention !== 'split') {
b69ab3135 throw new Error('Cannot apply AI split when not in split mode');
b69ab3136 }
b69ab3137
b69ab3138 // In split mode, startRev is 1 and endRev is size-1
b69ab3139 const startRev = 1 as CommitRev;
b69ab3140 const endRev = (commitStack.size - 1) as CommitRev;
b69ab3141
b69ab3142 // Extract a dense substack containing only the commit(s) to be split
b69ab3143 // For split mode with a single commit, this is just [startRev]
b69ab3144 const subStack = commitStack.denseSubStack(List([startRev]));
b69ab3145
b69ab3146 // Apply the diff split to the first commit in the subStack (position 0 relative to subStack)
b69ab3147 const newSubStack = applyDiffSplit(subStack, 0 as CommitRev, commits);
b69ab3148
b69ab3149 // Replace the [start, end+1] range with the new stack in the commit stack
b69ab3150 const newCommitStack = commitStack.applySubStack(startRev, next(endRev), newSubStack);
b69ab3151
b69ab3152 // Calculate the split range for UI selection
b69ab3153 const endOffset = newCommitStack.size - commitStack.size;
b69ab3154
b69ab3155 // The split commits start at position startRev (the first new split commit)
b69ab3156 // and end at position startRev + endOffset
b69ab3157 const startKey = newCommitStack.get(startRev)?.key ?? '';
b69ab3158 const endKey = newCommitStack.get(next(startRev, endOffset))?.key ?? '';
b69ab3159 const splitRange = SplitRangeRecord({startKey, endKey});
b69ab3160
b69ab3161 // Update the state with the new split
b69ab3162 writeAtom(stackEditState, prev => {
b69ab3163 if (prev.history.state !== 'hasValue') {
b69ab3164 return prev;
b69ab3165 }
b69ab3166
b69ab3167 return {
b69ab3168 ...prev,
b69ab3169 history: {
b69ab3170 state: 'hasValue' as const,
b69ab3171 value: prev.history.value.push(newCommitStack, {name: 'splitWithAI'}, {splitRange}),
b69ab3172 },
b69ab3173 };
b69ab3174 });
b69ab3175}
b69ab3176
b69ab3177// Handle openSplitViewForCommit message - opens the split UI for a specific commit
b69ab3178// and optionally applies AI split commits after the stack is loaded
b69ab3179registerDisposable(
b69ab3180 serverAPI,
b69ab3181 serverAPI.onMessageOfType('openSplitViewForCommit', event => {
b69ab3182 // Open the split panel by setting the editing stack intention
b69ab3183 // Use getDefaultStore().set() directly since editingStackIntentionHashes has a custom write type
b69ab3184 const store = getDefaultStore();
b69ab3185 store.set(editingStackIntentionHashes, ['split', new Set([event.commitHash])]);
b69ab3186
b69ab3187 // If commits are provided, wait for the stack to load and then apply the split
b69ab3188 if (event.commits && event.commits.length > 0) {
b69ab3189 const commitsToApply = event.commits;
b69ab3190 let hasApplied = false;
b69ab3191 const unsubscribe = store.sub(stackEditState, () => {
b69ab3192 if (hasApplied) {
b69ab3193 return;
b69ab3194 }
b69ab3195 const state = store.get(stackEditState);
b69ab3196 if (state.history.state === 'hasValue' && state.intention === 'split') {
b69ab3197 // Stack is loaded, apply the AI split commits
b69ab3198 hasApplied = true;
b69ab3199 unsubscribe();
b69ab31100 applyAISplitCommits(commitsToApply);
b69ab31101 }
b69ab31102 });
b69ab31103 }
b69ab31104 }),
b69ab31105);