From 5535c0505584eee6e0c46e32671f73d34a1b6b5c Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 24 May 2024 11:14:49 +0200 Subject: [PATCH] Joh/homely-damselfly (#213376) * chore - `ReplyResponse` cleanup * associate hunk data with response id * Associate hunk data with response state so that accepting hunks updates the text edit group * first cut of moving N-edits conversion from inline chat to panel --- .../browser/actions/chatCodeblockActions.ts | 2 +- .../contrib/chat/browser/chatListRenderer.ts | 31 ++++++- .../contrib/chat/browser/codeBlockPart.ts | 21 ++--- .../contrib/chat/common/chatModel.ts | 10 ++- .../browser/inlineChatController.ts | 64 ++++++++++---- .../inlineChat/browser/inlineChatSession.ts | 88 +++++++------------ .../browser/inlineChatSessionServiceImpl.ts | 19 ++-- .../browser/inlineChatStrategies.ts | 2 +- .../contrib/inlineChat/common/inlineChat.ts | 23 +---- .../test/browser/inlineChatSession.test.ts | 49 ++++++++++- 10 files changed, 180 insertions(+), 129 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 455d4f15b1744..6d9b8c58a4314 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -621,7 +621,7 @@ export function registerChatCodeCompareBlockActions() { const instaService = accessor.get(IInstantiationService); const editor = instaService.createInstance(DefaultChatTextEditor); - await editor.apply(context.element, context.edit); + await editor.apply(context.element, context.edit, context.diffEditor); await editorService.openEditor({ resource: context.edit.uri, diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 66363a9abc6ef..b647454c945f1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -81,6 +81,7 @@ import { IMarkdownVulnerability, annotateSpecialMarkdownContent } from '../commo import { CodeBlockModelCollection } from '../common/codeBlockModelCollection'; import { IChatListItemRendererOptions } from './chat'; import { ChatMarkdownRenderer } from 'vs/workbench/contrib/chat/browser/chatMarkdownRenderer'; +import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; const $ = dom.$; @@ -1072,12 +1073,34 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer null); + + const editGroups: ISingleEditOperation[][] = []; + if (isResponseVM(element)) { + const chatModel = this.chatService.getSession(element.sessionId)!; + + for (const request of chatModel.getRequests()) { + if (!request.response) { + continue; + } + for (const item of request.response.response.value) { + if (item.kind !== 'textEditGroup' || item.state?.applied) { + continue; + } + for (const group of item.edits) { + const edits = group.map(TextEdit.asEditOperation); + editGroups.push(edits); + } + } + if (request.response === element.model) { + break; + } } } + + for (const edits of editGroups) { + modified.pushEditOperations(null, edits, () => null); + } + return { modified, original, diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index e4dfff5f9e764..8a768c698e295 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -775,7 +775,7 @@ export class DefaultChatTextEditor { @IDialogService private readonly dialogService: IDialogService, ) { } - async apply(response: IChatResponseModel | IChatResponseViewModel, item: IChatTextEditGroup): Promise { + async apply(response: IChatResponseModel | IChatResponseViewModel, item: IChatTextEditGroup, diffEditor: IDiffEditor | undefined): Promise { if (!response.response.value.includes(item)) { // bogous item @@ -787,15 +787,16 @@ export class DefaultChatTextEditor { return; } - let diffEditor: IDiffEditor | undefined; - for (const candidate of this.editorService.listDiffEditors()) { - if (!candidate.getContainerDomNode().isConnected) { - continue; - } - const model = candidate.getModel(); - if (!model || !isEqual(model.original.uri, item.uri) || model.modified.uri.scheme !== Schemas.vscodeChatCodeCompareBlock) { - diffEditor = candidate; - break; + if (!diffEditor) { + for (const candidate of this.editorService.listDiffEditors()) { + if (!candidate.getContainerDomNode().isConnected) { + continue; + } + const model = candidate.getModel(); + if (!model || !isEqual(model.original.uri, item.uri) || model.modified.uri.scheme !== Schemas.vscodeChatCodeCompareBlock) { + diffEditor = candidate; + break; + } } } diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index dbb3c3aef8be3..528cfc4148373 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -51,13 +51,15 @@ export interface IChatRequestModel { readonly response?: IChatResponseModel; } +export interface IChatTextEditGroupState { + sha1: string; + applied: number; +} + export interface IChatTextEditGroup { uri: URI; edits: TextEdit[][]; - state?: { - sha1: string; - applied: number; - }; + state?: IChatTextEditGroupState; kind: 'textEditGroup'; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index e579444429545..90901bc712178 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -40,12 +40,14 @@ import { StashedSession } from './inlineChatSession'; import { IModelDeltaDecoration, ITextModel, IValidEditOperation } from 'vs/editor/common/model'; import { InlineChatContentWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget'; import { MessageController } from 'vs/editor/contrib/message/browser/messageController'; -import { ChatModel, IChatRequestModel, IResponse } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatModel, IChatRequestModel, IChatTextEditGroup, IChatTextEditGroupState, IResponse } from 'vs/workbench/contrib/chat/common/chatModel'; import { InlineChatError } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; -import { isEqual } from 'vs/base/common/resources'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; +import { DefaultModelSHA1Computer } from 'vs/editor/common/services/modelService'; +import { generateUuid } from 'vs/base/common/uuid'; +import { isEqual } from 'vs/base/common/resources'; export const enum State { CREATE_SESSION = 'CREATE_SESSION', @@ -653,6 +655,13 @@ export class InlineChatController implements IEditorContribution { let lastLength = 0; let isFirstChange = true; + const sha1 = new DefaultModelSHA1Computer(); + const textModel0Sha1 = sha1.canComputeSHA1(this._session.textModel0) + ? sha1.computeSHA1(this._session.textModel0) + : generateUuid(); + const editState: IChatTextEditGroupState = { sha1: textModel0Sha1, applied: 0 }; + let localEditGroup: IChatTextEditGroup | undefined; + // apply edits store.add(response.onDidChange(() => { @@ -667,17 +676,18 @@ export class InlineChatController implements IEditorContribution { return; } - const edits = response.response.value.map(part => { - if (part.kind === 'textEditGroup' && isEqual(part.uri, this._session?.textModelN.uri)) { - return part.edits; - } else { - return []; - } - }).flat(); + if (!localEditGroup) { + localEditGroup = response.response.value.find(part => part.kind === 'textEditGroup' && isEqual(part.uri, this._session?.textModelN.uri)); + } - // const edits = response.edits.get(this._session!.textModelN.uri) ?? []; + if (!localEditGroup) { + return; + } + + localEditGroup.state ??= editState; + + const edits = localEditGroup.edits; const newEdits = edits.slice(lastLength); - // console.log('NEW edits', newEdits, edits); if (newEdits.length === 0) { return; // NO change } @@ -720,7 +730,7 @@ export class InlineChatController implements IEditorContribution { const diff = await this._editorWorkerService.computeDiff(this._session.textModel0.uri, this._session.textModelN.uri, { computeMoves: false, maxComputationTimeMs: Number.MAX_SAFE_INTEGER, ignoreTrimWhitespace: false }, 'advanced'); this._session.wholeRange.fixup(diff?.changes ?? []); - await this._session.hunkData.recompute(); + await this._session.hunkData.recompute(editState); this._zone.value.widget.updateToolbar(true); this._zone.value.widget.updateProgress(false); @@ -1007,15 +1017,33 @@ export class InlineChatController implements IEditorContribution { return; } - // TODO@jrieken REMOVE this as soon as we can mark responses as accepted - // and as soon as hunks support request-linking - const textEditsResponseCount = this._session.chatModel.getRequests().filter(request => request.response?.response.value.some(part => part.kind === 'textEditGroup')).length; - if (textEditsResponseCount > 1) { - return; + let someApplied = false; + let lastEdit: IChatTextEditGroup | undefined; + + const uri = this._editor.getModel()?.uri; + const requests = this._session.chatModel.getRequests(); + for (const request of requests) { + if (!request.response) { + continue; + } + for (const part of request.response.response.value) { + if (part.kind === 'textEditGroup' && isEqual(part.uri, uri)) { + // fully or partially applied edits + someApplied = someApplied || Boolean(part.state?.applied); + lastEdit = part; + } + } + } + + const doEdits = this._strategy.cancel(); + + if (someApplied) { + assertType(lastEdit); + lastEdit.edits = [doEdits]; } - this._strategy.cancel(); await this._instaService.invokeFunction(moveToPanelChat, this._session?.chatModel); + this.cancelSession(); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index 2bebeaede348c..89f252a0c22b8 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -6,16 +6,15 @@ import { URI } from 'vs/base/common/uri'; import { Emitter, Event } from 'vs/base/common/event'; import { ResourceEdit, ResourceFileEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; -import { IWorkspaceTextEdit, TextEdit, WorkspaceEdit } from 'vs/editor/common/languages'; +import { TextEdit } from 'vs/editor/common/languages'; import { IIdentifiedSingleEditOperation, IModelDecorationOptions, IModelDeltaDecoration, ITextModel, IValidEditOperation, TrackedRangeStickiness } from 'vs/editor/common/model'; -import { EditMode, IInlineChatSession, IInlineChatBulkEditResponse, IInlineChatEditResponse, InlineChatResponseType, CTX_INLINE_CHAT_HAS_STASHED_SESSION } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { EditMode, IInlineChatSession, CTX_INLINE_CHAT_HAS_STASHED_SESSION, IInlineChatResponse } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { isCancellationError } from 'vs/base/common/errors'; import { EditOperation, ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { DetailedLineRangeMapping, LineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ILanguageService } from 'vs/editor/common/languages/language'; @@ -32,7 +31,7 @@ import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ILogService } from 'vs/platform/log/common/log'; -import { ChatModel, IChatRequestModel, IChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatModel, IChatRequestModel, IChatResponseModel, IChatTextEditGroupState } from 'vs/workbench/contrib/chat/common/chatModel'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IChatAgent } from 'vs/workbench/contrib/chat/common/chatAgents'; @@ -316,56 +315,40 @@ export class ErrorResponse { export class ReplyResponse { - readonly allLocalEdits: TextEdit[][] = []; readonly untitledTextModel: IUntitledTextEditorModel | undefined; - readonly workspaceEdit: WorkspaceEdit | undefined; - constructor( - readonly raw: IInlineChatBulkEditResponse | IInlineChatEditResponse, - readonly mdContent: IMarkdownString, + readonly raw: IInlineChatResponse, localUri: URI, readonly modelAltVersionId: number, - progressEdits: TextEdit[][], - readonly requestId: string, - readonly chatResponse: IChatResponseModel | undefined, + readonly chatRequest: IChatRequestModel, + readonly chatResponse: IChatResponseModel, @ITextFileService private readonly _textFileService: ITextFileService, @ILanguageService private readonly _languageService: ILanguageService, ) { const editsMap = new ResourceMap(); + const edits = ResourceEdit.convert(raw.edits); - editsMap.set(localUri, [...progressEdits]); - - if (raw.type === InlineChatResponseType.EditorEdit) { - // - editsMap.get(localUri)!.push(raw.edits); - - } else if (raw.type === InlineChatResponseType.BulkEdit) { - // - const edits = ResourceEdit.convert(raw.edits); - - for (const edit of edits) { - if (edit instanceof ResourceFileEdit) { - if (edit.newResource && !edit.oldResource) { - editsMap.set(edit.newResource, []); - if (edit.options.contents) { - console.warn('CONTENT not supported'); - } - } - } else if (edit instanceof ResourceTextEdit) { - // - const array = editsMap.get(edit.resource); - if (array) { - array.push([edit.textEdit]); - } else { - editsMap.set(edit.resource, [[edit.textEdit]]); + for (const edit of edits) { + if (edit instanceof ResourceFileEdit) { + if (edit.newResource && !edit.oldResource) { + editsMap.set(edit.newResource, []); + if (edit.options.contents) { + console.warn('CONTENT not supported'); } } + } else if (edit instanceof ResourceTextEdit) { + // + const array = editsMap.get(edit.resource); + if (array) { + array.push([edit.textEdit]); + } else { + editsMap.set(edit.resource, [[edit.textEdit]]); + } } } - let needsWorkspaceEdit = false; for (const [uri, edits] of editsMap) { @@ -376,8 +359,6 @@ export class ReplyResponse { } const isLocalUri = isEqual(uri, localUri); - needsWorkspaceEdit = needsWorkspaceEdit || (uri.scheme !== Schemas.untitled && !isLocalUri); - if (uri.scheme === Schemas.untitled && !isLocalUri && !this.untitledTextModel) { //TODO@jrieken the first untitled model WINS const langSelection = this._languageService.createByFilepathOrFirstLine(uri, undefined); const untitledTextModel = this._textFileService.untitled.create({ @@ -388,18 +369,6 @@ export class ReplyResponse { untitledTextModel.resolve(); } } - - this.allLocalEdits = editsMap.get(localUri) ?? []; - - if (needsWorkspaceEdit) { - const workspaceEdits: IWorkspaceTextEdit[] = []; - for (const [uri, edits] of editsMap) { - for (const edit of edits.flat()) { - workspaceEdits.push({ resource: uri, textEdit: edit, versionId: undefined }); - } - } - this.workspaceEdit = { edits: workspaceEdits }; - } } } @@ -471,7 +440,7 @@ export class HunkData { private static readonly _HUNK_THRESHOLD = 8; private readonly _store = new DisposableStore(); - private readonly _data = new Map(); + private readonly _data = new Map(); private _ignoreChanges: boolean = false; constructor( @@ -602,7 +571,7 @@ export class HunkData { this._textModel0.pushEditOperations(null, edits, () => null); } - async recompute() { + async recompute(editState: IChatTextEditGroupState) { const diff = await this._editorWorkerService.computeDiff(this._textModel0.uri, this._textModelN.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: Number.MAX_SAFE_INTEGER, computeMoves: false }, 'advanced'); @@ -656,6 +625,7 @@ export class HunkData { } this._data.set(hunk, { + editState, textModelNDecorations, textModel0Decorations, state: HunkState.Pending @@ -689,7 +659,7 @@ export class HunkData { discardAll() { const edits: ISingleEditOperation[][] = []; for (const item of this.getInfo()) { - if (item.getState() !== HunkState.Rejected) { + if (item.getState() === HunkState.Pending) { edits.push(this._discardEdits(item)); } } @@ -746,6 +716,7 @@ export class HunkData { } this._textModel0.pushEditOperations(null, edits, () => null); data.state = HunkState.Accepted; + data.editState.applied += 1; } } }; @@ -764,6 +735,13 @@ class RawHunk { ) { } } +type RawHunkData = { + textModelNDecorations: string[]; + textModel0Decorations: string[]; + state: HunkState; + editState: IChatTextEditGroupState; +}; + export const enum HunkState { Pending = 0, Accepted = 1, diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index e76cd1738827e..63b870afbb514 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -5,7 +5,6 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { MarkdownString } from 'vs/base/common/htmlContent'; import { DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; @@ -23,7 +22,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; import { ChatAgentLocation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; -import { CTX_INLINE_CHAT_HAS_PROVIDER, EditMode, IInlineChatBulkEditResponse, IInlineChatSession, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_HAS_PROVIDER, EditMode, IInlineChatResponse, IInlineChatSession } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { EmptyResponse, ErrorResponse, HunkData, ReplyResponse, Session, SessionExchange, SessionWholeRange, StashedSession, TelemetryData, TelemetryDataClassification } from './inlineChatSession'; @@ -199,17 +198,11 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { inlineResponse = new EmptyResponse(); } else { // replay response - const markdownContent = new MarkdownString(); - const raw: IInlineChatBulkEditResponse = { - id: Math.random(), - type: InlineChatResponseType.BulkEdit, - message: markdownContent, + const raw: IInlineChatResponse = { edits: { edits: [] }, }; for (const item of response.response.value) { - if (item.kind === 'markdownContent') { - markdownContent.value += item.content.value; - } else if (item.kind === 'textEditGroup') { + if (item.kind === 'textEditGroup') { for (const group of item.edits) { for (const edit of group) { raw.edits.edits.push({ @@ -225,12 +218,10 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { inlineResponse = this._instaService.createInstance( ReplyResponse, raw, - markdownContent, session.textModelN.uri, modelAltVersionIdNow, - [], - e.request.id, - e.request.response + e.request, + response ); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 458c0da69a622..c98fcfa30f8d0 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -99,7 +99,7 @@ export abstract class EditModeStrategy { continue; } - await editor.apply(request.response, item); + await editor.apply(request.response, item, undefined); if (item.uri.scheme === Schemas.untitled) { const untitled = this._textFileService.untitled.get(item.uri); diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 241986e3f3b99..bf23fd716319d 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -5,7 +5,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IRange } from 'vs/editor/common/core/range'; -import { TextEdit, WorkspaceEdit } from 'vs/editor/common/languages'; +import { WorkspaceEdit } from 'vs/editor/common/languages'; import { localize } from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; @@ -24,13 +24,6 @@ export interface IInlineChatSession { wholeRange?: IRange; } -export type IInlineChatResponse = IInlineChatEditResponse | IInlineChatBulkEditResponse; - -export const enum InlineChatResponseType { - EditorEdit = 'editorEdit', - BulkEdit = 'bulkEdit' -} - export const enum InlineChatResponseTypes { Empty = 'empty', OnlyEdits = 'onlyEdits', @@ -38,18 +31,7 @@ export const enum InlineChatResponseTypes { Mixed = 'mixed' } -export interface IInlineChatEditResponse { - id: number; - type: InlineChatResponseType.EditorEdit; - edits: TextEdit[]; - message?: IMarkdownString; - placeholder?: string; - wholeRange?: IRange; -} - -export interface IInlineChatBulkEditResponse { - id: number; - type: InlineChatResponseType.BulkEdit; +export interface IInlineChatResponse { edits: WorkspaceEdit; message?: IMarkdownString; placeholder?: string; @@ -77,7 +59,6 @@ export const CTX_INLINE_CHAT_INNER_CURSOR_END = new RawContextKey('inli export const CTX_INLINE_CHAT_OUTER_CURSOR_POSITION = new RawContextKey<'above' | 'below' | ''>('inlineChatOuterCursorPosition', '', localize('inlineChatOuterCursorPosition', "Whether the cursor of the outer editor is above or below the interactive editor input")); export const CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST = new RawContextKey('inlineChatHasActiveRequest', false, localize('inlineChatHasActiveRequest', "Whether interactive editor has an active request")); export const CTX_INLINE_CHAT_HAS_STASHED_SESSION = new RawContextKey('inlineChatHasStashedSession', false, localize('inlineChatHasStashedSession', "Whether interactive editor has kept a session for quick restore")); -export const CTX_INLINE_CHAT_LAST_RESPONSE_TYPE = new RawContextKey('inlineChatLastResponseType', undefined, localize('inlineChatResponseType', "What type was the last response of the current interactive editor session")); export const CTX_INLINE_CHAT_RESPONSE_TYPES = new RawContextKey('inlineChatResponseTypes', InlineChatResponseTypes.Empty, localize('inlineChatResponseTypes', "What type was the responses have been receieved")); export const CTX_INLINE_CHAT_DID_EDIT = new RawContextKey('inlineChatDidEdit', undefined, localize('inlineChatDidEdit', "Whether interactive editor did change any code")); export const CTX_INLINE_CHAT_USER_DID_EDIT = new RawContextKey('inlineChatUserDidEdit', undefined, localize('inlineChatUserDidEdit', "Whether the user did changes ontop of the inline chat")); diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts index 3731977e58b82..86a3b614ff278 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts @@ -160,7 +160,7 @@ suite('InlineChatSession', function () { } finally { session.hunkData.ignoreTextModelNChanges = false; } - await session.hunkData.recompute(); + await session.hunkData.recompute({ applied: 0, sha1: 'fakeSha1' }); } function makeEdit(edit: EditOperation | EditOperation[]) { @@ -433,4 +433,51 @@ suite('InlineChatSession', function () { assert.strictEqual(session.textModelN.getValue(), session.textModel0.getValue()); }); + test('HunkData, accept, discardAll', async function () { + + const session = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None); + assertType(session); + + await makeEditAsAi([EditOperation.insert(new Position(1, 1), 'AI_EDIT\n'), EditOperation.insert(new Position(10, 1), 'AI_EDIT\n')]); + + assert.strictEqual(session.hunkData.size, 2); + assert.ok(!session.textModel0.equalsTextBuffer(session.textModelN.getTextBuffer())); + + const textModeNNow = session.textModelN.getValue(); + + session.hunkData.getInfo()[0].acceptChanges(); + assert.strictEqual(textModeNNow, session.textModelN.getValue()); + + session.hunkData.discardAll(); // all remaining + assert.strictEqual(session.textModelN.getValue(), 'AI_EDIT\none\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten\neleven'); + assert.strictEqual(session.textModelN.getValue(), session.textModel0.getValue()); + + inlineChatSessionService.releaseSession(session); + }); + + test('HunkData, discardAll return undo edits', async function () { + + const session = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None); + assertType(session); + + await makeEditAsAi([EditOperation.insert(new Position(1, 1), 'AI_EDIT\n'), EditOperation.insert(new Position(10, 1), 'AI_EDIT\n')]); + + assert.strictEqual(session.hunkData.size, 2); + assert.ok(!session.textModel0.equalsTextBuffer(session.textModelN.getTextBuffer())); + + const textModeNNow = session.textModelN.getValue(); + + session.hunkData.getInfo()[0].acceptChanges(); + assert.strictEqual(textModeNNow, session.textModelN.getValue()); + + const undoEdits = session.hunkData.discardAll(); // all remaining + assert.strictEqual(session.textModelN.getValue(), 'AI_EDIT\none\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten\neleven'); + assert.strictEqual(session.textModelN.getValue(), session.textModel0.getValue()); + + // undo the discards + session.textModelN.pushEditOperations(null, undoEdits, () => null); + assert.strictEqual(textModeNNow, session.textModelN.getValue()); + + inlineChatSessionService.releaseSession(session); + }); });