Path: blob/main/src/vs/workbench/contrib/chat/browser/actions/reviewEdits.ts
13406 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { raceCancellation } from '../../../../../base/common/async.js';6import { CancellationToken } from '../../../../../base/common/cancellation.js';7import { DisposableStore } from '../../../../../base/common/lifecycle.js';8import { derived, waitForState } from '../../../../../base/common/observable.js';9import { assertType } from '../../../../../base/common/types.js';10import { URI } from '../../../../../base/common/uri.js';11import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js';12import { TextEdit } from '../../../../../editor/common/languages.js';13import { EditSuggestionId } from '../../../../../editor/common/textModelEditSource.js';14import { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js';15import { IChatService } from '../../common/chatService/chatService.js';16import { ChatAgentLocation } from '../../common/constants.js';17import { ModifiedFileEntryState } from '../../common/editing/chatEditingService.js';18import { ChatModel } from '../../common/model/chatModel.js';19import { ICellEditOperation } from '../../../notebook/common/notebookCommon.js';20import { INotebookService } from '../../../notebook/common/notebookService.js';212223export async function reviewEdits(accessor: ServicesAccessor, editor: ICodeEditor, stream: AsyncIterable<TextEdit[]>, token: CancellationToken, applyCodeBlockSuggestionId: EditSuggestionId | undefined): Promise<boolean> {24if (!editor.hasModel()) {25return false;26}2728const chatService = accessor.get(IChatService);29const uri = editor.getModel().uri;30const chatModelRef = chatService.startNewLocalSession(ChatAgentLocation.EditorInline);31const chatModel = chatModelRef.object as ChatModel;3233chatModel.startEditingSession(true);3435const store = new DisposableStore();36store.add(chatModelRef);3738// STREAM39const chatRequest = chatModel?.addRequest({ text: '', parts: [] }, { variables: [] }, 0, {40kind: undefined,41modeId: 'applyCodeBlock',42modeInstructions: undefined,43isBuiltin: true,44applyCodeBlockSuggestionId,45});46assertType(chatRequest.response);47chatRequest.response.updateContent({ kind: 'textEdit', uri, edits: [], done: false });48for await (const chunk of stream) {4950if (token.isCancellationRequested) {51chatRequest.response.cancel();52break;53}5455chatRequest.response.updateContent({ kind: 'textEdit', uri, edits: chunk, done: false });56}57chatRequest.response.updateContent({ kind: 'textEdit', uri, edits: [], done: true });5859if (!token.isCancellationRequested) {60chatRequest.response.complete();61}6263const isSettled = derived(r => {64const entry = chatModel.editingSession?.readEntry(uri, r);65if (!entry) {66return false;67}68const state = entry.state.read(r);69return state === ModifiedFileEntryState.Accepted || state === ModifiedFileEntryState.Rejected;70});71const whenDecided = waitForState(isSettled, Boolean);72await raceCancellation(whenDecided, token);73store.dispose();74return true;75}7677export async function reviewNotebookEdits(accessor: ServicesAccessor, uri: URI, stream: AsyncIterable<[URI, TextEdit[]] | ICellEditOperation[]>, token: CancellationToken): Promise<boolean> {7879const chatService = accessor.get(IChatService);80const notebookService = accessor.get(INotebookService);81const isNotebook = notebookService.hasSupportedNotebooks(uri);82const chatModelRef = chatService.startNewLocalSession(ChatAgentLocation.EditorInline);83const chatModel = chatModelRef.object as ChatModel;8485chatModel.startEditingSession(true);8687const store = new DisposableStore();88store.add(chatModelRef);8990// STREAM91const chatRequest = chatModel?.addRequest({ text: '', parts: [] }, { variables: [] }, 0);92assertType(chatRequest.response);93if (isNotebook) {94chatRequest.response.updateContent({ kind: 'notebookEdit', uri, edits: [], done: false });95} else {96chatRequest.response.updateContent({ kind: 'textEdit', uri, edits: [], done: false });97}98for await (const chunk of stream) {99100if (token.isCancellationRequested) {101chatRequest.response.cancel();102break;103}104if (chunk.every(isCellEditOperation)) {105chatRequest.response.updateContent({ kind: 'notebookEdit', uri, edits: chunk, done: false });106} else {107chatRequest.response.updateContent({ kind: 'textEdit', uri: chunk[0], edits: chunk[1], done: false });108}109}110if (isNotebook) {111chatRequest.response.updateContent({ kind: 'notebookEdit', uri, edits: [], done: true });112} else {113chatRequest.response.updateContent({ kind: 'textEdit', uri, edits: [], done: true });114}115116if (!token.isCancellationRequested) {117chatRequest.response.complete();118}119120const isSettled = derived(r => {121const entry = chatModel.editingSession?.readEntry(uri, r);122if (!entry) {123return false;124}125const state = entry.state.read(r);126return state === ModifiedFileEntryState.Accepted || state === ModifiedFileEntryState.Rejected;127});128129const whenDecided = waitForState(isSettled, Boolean);130131await raceCancellation(whenDecided, token);132133store.dispose();134135return true;136}137function isCellEditOperation(edit: URI | TextEdit[] | ICellEditOperation): edit is ICellEditOperation {138if (URI.isUri(edit)) {139return false;140}141if (Array.isArray(edit)) {142return false;143}144return true;145}146147148