Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,14 @@ import { TextEdit } from '../../../../../editor/common/languages.js';
import { ITextModelService } from '../../../../../editor/common/services/resolverService.js';
import { localize, localize2 } from '../../../../../nls.js';
import { IContextKey, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
import { EditorActivation } from '../../../../../platform/editor/common/editor.js';
import { IFileService } from '../../../../../platform/files/common/files.js';
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
import { bindContextKey } from '../../../../../platform/observable/common/platformObservableUtils.js';
import { IProgressService, ProgressLocation } from '../../../../../platform/progress/common/progress.js';
import { EditorInput } from '../../../../common/editor/editorInput.js';
import { IWorkbenchAssignmentService } from '../../../../services/assignment/common/assignmentService.js';
import { IDecorationData, IDecorationsProvider, IDecorationsService } from '../../../../services/decorations/common/decorations.js';
import { IEditorGroup, IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js';
import { IEditorService } from '../../../../services/editor/common/editorService.js';
import { ILifecycleService } from '../../../../services/lifecycle/common/lifecycle.js';
import { MultiDiffEditor } from '../../../multiDiffEditor/browser/multiDiffEditor.js';
import { IMultiDiffSourceResolver, IMultiDiffSourceResolverService, IResolvedMultiDiffSource, MultiDiffEditorItem } from '../../../multiDiffEditor/browser/multiDiffSourceResolverService.js';
import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js';
import { ChatContextKeys } from '../../common/chatContextKeys.js';
Expand Down Expand Up @@ -79,7 +75,6 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
private _chatRelatedFilesProviders = new Map<number, IChatRelatedFilesProvider>();

constructor(
@IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IMultiDiffSourceResolverService multiDiffSourceResolverService: IMultiDiffSourceResolverService,
@ITextModelService textModelService: ITextModelService,
Expand Down Expand Up @@ -204,10 +199,11 @@ export class ChatEditingService extends Disposable implements IChatEditingServic

this._currentSessionDisposables.clear();

const session = this._instantiationService.createInstance(ChatEditingSession, chatSessionId, this._editingSessionFileLimitPromise);

// listen for completed responses, run the code mapper and apply the edits to this edit session
this._currentSessionDisposables.add(this.installAutoApplyObserver(chatSessionId));
this._currentSessionDisposables.add(this.installAutoApplyObserver(session));

const session = this._instantiationService.createInstance(ChatEditingSession, chatSessionId, this._editingSessionFileLimitPromise);
await session.restoreState();

this._currentSessionDisposables.add(session.onDidDispose(() => {
Expand All @@ -233,11 +229,11 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
await this._currentSessionObs.get()?.restoreSnapshot(requestId);
}

private installAutoApplyObserver(sessionId: string): IDisposable {
private installAutoApplyObserver(session: ChatEditingSession): IDisposable {

const chatModel = this._chatService.getSession(sessionId);
const chatModel = this._chatService.getSession(session.chatSessionId);
if (!chatModel) {
throw new Error(`Edit session was created for a non-existing chat session: ${sessionId}`);
throw new Error(`Edit session was created for a non-existing chat session: ${session.chatSessionId}`);
}

const observerDisposables = new DisposableStore();
Expand All @@ -250,7 +246,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
const onResponseComplete = (responseModel: IChatResponseModel) => {
if (responseModel.result?.errorDetails && !responseModel.result.errorDetails.responseIsIncomplete) {
// Roll back everything
this.restoreSnapshot(responseModel.requestId);
session.restoreSnapshot(responseModel.requestId);
this._applyingChatEditsFailedContextKey.set(true);
}

Expand Down Expand Up @@ -293,7 +289,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic

await editsPromise;

editsPromise = this._continueEditingSession(async (builder, token) => {
editsPromise = this._continueEditingSession(session, async (builder, token) => {
for await (const item of editsSource!.asyncIterable) {
if (token.isCancellationRequested) {
break;
Expand All @@ -304,7 +300,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
builder.textEdits(item.uri, group, isLastGroup && (item.done ?? false), responseModel);
}
}
}, { silent: true }).finally(() => {
}).finally(() => {
editsPromise = undefined;
});
}
Expand All @@ -314,6 +310,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic

observerDisposables.add(chatModel.onDidChange(async e => {
if (e.kind === 'addRequest') {
session.createSnapshot(e.request.id);
this._applyingChatEditsFailedContextKey.set(false);
const responseModel = e.request.response;
if (responseModel) {
Expand All @@ -338,27 +335,11 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
return observerDisposables;
}

private async _continueEditingSession(builder: (stream: IChatEditingSessionStream, token: CancellationToken) => Promise<void>, options?: { silent?: boolean }): Promise<void> {
const session = this._currentSessionObs.get();
if (!session) {
throw new BugIndicatingError('Cannot continue missing session');
}

private async _continueEditingSession(session: ChatEditingSession, builder: (stream: IChatEditingSessionStream, token: CancellationToken) => Promise<void>): Promise<void> {
if (session.state.get() === ChatEditingSessionState.StreamingEdits) {
throw new BugIndicatingError('Cannot continue session that is still streaming');
}

let editorPane: MultiDiffEditor | undefined;
if (!options?.silent && session.isVisible) {
const groupedEditors = this._findGroupedEditors();
if (groupedEditors.length !== 1) {
throw new Error(`Unexpected number of editors: ${groupedEditors.length}`);
}
const [group, editor] = groupedEditors[0];

editorPane = await group.openEditor(editor, { pinned: true, activation: EditorActivation.ACTIVATE }) as MultiDiffEditor | undefined;
}

const stream: IChatEditingSessionStream = {
textEdits: (resource: URI, textEdits: TextEdit[], isDone: boolean, responseModel: IChatResponseModel) => {
session.acceptTextEdits(resource, textEdits, isDone, responseModel);
Expand All @@ -368,37 +349,22 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
const cancellationTokenSource = new CancellationTokenSource();
this._currentAutoApplyOperationObs.set(cancellationTokenSource, undefined);
try {
if (editorPane) {
await editorPane?.showWhile(builder(stream, cancellationTokenSource.token));
} else {
await this._progressService.withProgress({
location: ProgressLocation.Window,
title: localize2('chatEditing.startingSession', 'Generating edits...').value,
}, async () => {
await builder(stream, cancellationTokenSource.token);
},
() => cancellationTokenSource.cancel()
);
}
await this._progressService.withProgress({
location: ProgressLocation.Window,
title: localize2('chatEditing.startingSession', 'Generating edits...').value,
}, async () => {
await builder(stream, cancellationTokenSource.token);
},
() => cancellationTokenSource.cancel()
);

} finally {
cancellationTokenSource.dispose();
this._currentAutoApplyOperationObs.set(null, undefined);
session.resolve();
}
}

private _findGroupedEditors() {
const editors: [IEditorGroup, EditorInput][] = [];
for (const group of this._editorGroupsService.groups) {
for (const editor of group.editors) {
if (editor.resource?.scheme === ChatEditingMultiDiffSourceResolver.scheme) {
editors.push([group, editor]);
}
}
}
return editors;
}

hasRelatedFilesProviders(): boolean {
return this._chatRelatedFilesProviders.size > 0;
}
Expand Down
5 changes: 0 additions & 5 deletions src/vs/workbench/contrib/chat/browser/chatWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -889,11 +889,6 @@ export class ChatWidget extends Disposable implements IChatWidget {
if (e.kind === 'setAgent') {
this._onDidChangeAgent.fire({ agent: e.agent, slashCommand: e.command });
}
if (e.kind === 'addRequest') {
if (this._location.location === ChatAgentLocation.EditingSession) {
this.chatEditingService.createSnapshot(e.request.id);
}
}
Copy link
Contributor

@joyceerhl joyceerhl Nov 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we not need to take snapshots anymore? Would this not break undo/redo, or does that happen some other way now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No change, but it moved to inside the chatEditingService:

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thanks

}));

if (this.tree && this.visible) {
Expand Down