uses /edit editor UX for "Apply In Editor" (#237944)

* WIP

* uses /edit editor UX for "Apply In Editor"

fixes https://github.com/microsoft/vscode-copilot/issues/8577
This commit is contained in:
Johannes Rieken
2025-01-15 09:38:56 +01:00
committed by GitHub
parent 1db1071148
commit fce85e9d27
11 changed files with 223 additions and 91 deletions

View File

@@ -12,6 +12,7 @@ import { Emitter, Event } from '../../../../base/common/event.js';
import { Lazy } from '../../../../base/common/lazy.js';
import { DisposableStore, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
import { MovingAverage } from '../../../../base/common/numbers.js';
import { autorun } from '../../../../base/common/observable.js';
import { isEqual } from '../../../../base/common/resources.js';
import { StopWatch } from '../../../../base/common/stopwatch.js';
import { assertType } from '../../../../base/common/types.js';
@@ -40,6 +41,7 @@ import { showChatView } from '../../chat/browser/chat.js';
import { IChatWidgetLocationOptions } from '../../chat/browser/chatWidget.js';
import { ChatAgentLocation } from '../../chat/common/chatAgents.js';
import { ChatContextKeys } from '../../chat/common/chatContextKeys.js';
import { IChatEditingService, WorkingSetEntryState } from '../../chat/common/chatEditingService.js';
import { ChatModel, ChatRequestRemovalReason, IChatRequestModel, IChatTextEditGroup, IChatTextEditGroupState, IResponse } from '../../chat/common/chatModel.js';
import { IChatService } from '../../chat/common/chatService.js';
import { INotebookEditorService } from '../../notebook/browser/services/notebookEditorService.js';
@@ -144,6 +146,7 @@ export class InlineChatController implements IEditorContribution {
@IDialogService private readonly _dialogService: IDialogService,
@IContextKeyService contextKeyService: IContextKeyService,
@IChatService private readonly _chatService: IChatService,
@IChatEditingService private readonly _chatEditingService: IChatEditingService,
@IEditorService private readonly _editorService: IEditorService,
@INotebookEditorService notebookEditorService: INotebookEditorService,
) {
@@ -1134,44 +1137,56 @@ export class InlineChatController implements IEditorContribution {
return this._currentRun;
}
async reviewEdits(anchor: IRange, stream: AsyncIterable<TextEdit[]>, token: CancellationToken) {
async reviewEdits(stream: AsyncIterable<TextEdit[]>, token: CancellationToken) {
if (!this._editor.hasModel()) {
return false;
}
const session = await this._inlineChatSessionService.createSession(this._editor, { wholeRange: anchor, headless: true }, token);
if (!session) {
const uri = this._editor.getModel().uri;
const chatModel = this._chatService.startSession(ChatAgentLocation.Editor, token);
if (!chatModel) {
return false;
}
const request = session.chatModel.addRequest({ text: 'DUMMY', parts: [] }, { variables: [] }, 0);
const run = this.run({
existingSession: session,
headless: true
const editSession = await this._chatEditingService.createAdhocEditingSession(chatModel.sessionId);
//
const store = new DisposableStore();
store.add(chatModel);
store.add(editSession);
// STREAM
const chatRequest = chatModel?.addRequest({ text: '', parts: [] }, { variables: [] }, 0);
assertType(chatRequest.response);
chatRequest.response.updateContent({ kind: 'textEdit', uri, edits: [], done: false });
for await (const chunk of stream) {
if (token.isCancellationRequested) {
chatRequest.response.cancel();
break;
}
chatRequest.response.updateContent({ kind: 'textEdit', uri, edits: chunk, done: false });
}
chatRequest.response.updateContent({ kind: 'textEdit', uri, edits: [], done: true });
if (!token.isCancellationRequested) {
chatRequest.response.complete();
}
const whenDecided = new Promise(resolve => {
store.add(autorun(r => {
if (!editSession.entries.read(r).some(e => e.state.read(r) === WorkingSetEntryState.Modified)) {
resolve(undefined);
}
}));
});
await Event.toPromise(Event.filter(this._onDidEnterState.event, candidate => candidate === State.SHOW_REQUEST));
await raceCancellation(whenDecided, token);
for await (const chunk of stream) {
session.chatModel.acceptResponseProgress(request, { kind: 'textEdit', uri: this._editor.getModel()!.uri, edits: chunk });
}
store.dispose();
if (token.isCancellationRequested) {
session.chatModel.cancelRequest(request);
} else {
session.chatModel.completeResponse(request);
}
await Event.toPromise(Event.filter(this._onDidEnterState.event, candidate => candidate === State.WAIT_FOR_INPUT));
if (session.hunkData.pending === 0) {
// no real changes, just cancel
this.cancelSession();
}
const dispo = token.onCancellationRequested(() => this.cancelSession());
await raceCancellation(run, token);
dispo.dispose();
return true;
}
}