diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 3f017c151f3..1bf071d09c5 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -74,6 +74,8 @@ export namespace Schemas { /** Scheme used for code blocks in chat. */ export const vscodeChatCodeBlock = 'vscode-chat-code-block'; + /** Scheme used for LHS of code compare (aka diff) blocks in chat. */ + export const vscodeChatCodeCompreBlock = 'vscode-chat-code-compare-block'; /** Scheme used for the chat input editor. */ export const vscodeChatSesssion = 'vscode-chat-editor'; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 4975ed11dbe..8bb76ed3bd1 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -10,27 +10,25 @@ import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/edito import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { IBulkEditService, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { EditOperation, ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { DocumentContextItem, WorkspaceEdit } from 'vs/editor/common/languages'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { DefaultModelSHA1Computer } from 'vs/editor/common/services/modelService'; import { CopyAction } from 'vs/editor/contrib/clipboard/browser/clipboard'; -import { localize, localize2 } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { TerminalLocation } from 'vs/platform/terminal/common/terminal'; import { IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor'; import { accessibleViewInCodeBlock } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { IChatWidgetService, IChatCodeBlockContextProviderService } from 'vs/workbench/contrib/chat/browser/chat'; -import { ICodeBlockActionContext, ICodeCompareBlockActionContext } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; +import { DefaultChatTextEditor, ICodeBlockActionContext, ICodeCompareBlockActionContext } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_EDIT_APPLIED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatCopyKind, IChatService, IDocumentContext } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; @@ -619,47 +617,14 @@ export function registerChatCodeCompareBlockActions() { async runWithContext(accessor: ServicesAccessor, context: ICodeCompareBlockActionContext): Promise { - const diaglogService = accessor.get(IDialogService); const editorService = accessor.get(IEditorService); + const instaService = accessor.get(IInstantiationService); - if (!context.edit.state || context.edit.state.applied) { - return; - } - const model = context.diffEditor.getModel(); - if (!model) { - return; - } - const diff = context.diffEditor.getDiffComputationResult(); - if (!diff || diff.identical) { - return; - } - - const sha1 = new DefaultModelSHA1Computer(); - if (sha1.computeSHA1(model.original) && sha1.computeSHA1(model.original) !== context.edit.state.sha1) { - const result = await diaglogService.confirm({ - message: localize('interactive.compare.apply.confirm', "The original file has been modified."), - detail: localize('interactive.compare.apply.confirm.detail', "Do you want to apply the changes anyway?"), - }); - if (!result.confirmed) { - return; - } - } - - const edits: ISingleEditOperation[] = []; - for (const item of diff.changes2) { - const range = item.original.toExclusiveRange(); - const newText = model.modified.getValueInRange(item.modified.toExclusiveRange()); - edits.push(EditOperation.replace(range, newText)); - } - - model.original.pushStackElement(); - model.original.pushEditOperations(null, edits, () => null); - model.original.pushStackElement(); - - context.element.setEditApplied(context.edit, edits.length); + const editor = instaService.createInstance(DefaultChatTextEditor); + await editor.apply(context.element, context.edit); await editorService.openEditor({ - resource: model.original.uri, + resource: context.edit.uri, options: { revealIfVisible: true }, }); } diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 57801f875b0..9cbc3b561f1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -971,7 +971,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { + + if (!response.response.value.includes(item)) { + // bogous item + return; + } + + if (item.state?.applied) { + // already applied + 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.vscodeChatCodeCompreBlock) { + diffEditor = candidate; + break; + } + } + + const edits = diffEditor + ? await this._applyWithDiffEditor(diffEditor, item) + : await this._apply(item); + + response.setEditApplied(item, edits); + } + + private async _applyWithDiffEditor(diffEditor: IDiffEditor, item: IChatTextEditGroup) { + const model = diffEditor.getModel(); + if (!model) { + return 0; + } + + const diff = diffEditor.getDiffComputationResult(); + if (!diff || diff.identical) { + return 0; + } + + + if (!await this._checkSha1(model.original, item)) { + return 0; + } + + const edits: ISingleEditOperation[] = []; + for (const item of diff.changes2) { + const range = item.original.toExclusiveRange(); + const newText = model.modified.getValueInRange(item.modified.toExclusiveRange()); + edits.push(EditOperation.replace(range, newText)); + } + + model.original.pushStackElement(); + model.original.pushEditOperations(null, edits, () => null); + model.original.pushStackElement(); + + return edits.length; + } + + private async _apply(item: IChatTextEditGroup) { + const ref = await this.modelService.createModelReference(item.uri); + try { + + if (!await this._checkSha1(ref.object.textEditorModel, item)) { + return 0; + } + + ref.object.textEditorModel.pushStackElement(); + let total = 0; + for (const group of item.edits) { + const edits = group.map(TextEdit.asEditOperation); + ref.object.textEditorModel.pushEditOperations(null, edits, () => null); + total += edits.length; + } + ref.object.textEditorModel.pushStackElement(); + return total; + + } finally { + ref.dispose(); + } + } + + private async _checkSha1(model: ITextModel, item: IChatTextEditGroup) { + if (item.state?.sha1 && this._sha1.computeSHA1(model) && this._sha1.computeSHA1(model) !== item.state.sha1) { + const result = await this.dialogService.confirm({ + message: localize('interactive.compare.apply.confirm', "The original file has been modified."), + detail: localize('interactive.compare.apply.confirm.detail', "Do you want to apply the changes anyway?"), + }); + + if (!result.confirmed) { + return false; + } + } + return true; + } +} diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 92e5e608c6b..84d6a8db13e 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -35,12 +35,12 @@ import { IModelService } from 'vs/editor/common/services/model'; import { performAsyncTextEdit, asProgressiveEdit } from './utils'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { TextEdit } from 'vs/editor/common/languages'; -import { isEqual } from 'vs/base/common/resources'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; import { Schemas } from 'vs/base/common/network'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { DefaultChatTextEditor } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; +import { isEqual } from 'vs/base/common/resources'; export interface IEditObserver { start(): void; @@ -71,8 +71,8 @@ export abstract class EditModeStrategy { protected readonly _session: Session, protected readonly _editor: ICodeEditor, protected readonly _zone: InlineChatZoneWidget, - @ITextModelService private readonly _modelService: ITextModelService, @ITextFileService private readonly _textFileService: ITextFileService, + @IInstantiationService private readonly _instaService: IInstantiationService, ) { } dispose(): void { @@ -83,6 +83,8 @@ export abstract class EditModeStrategy { const untitledModels: IUntitledTextEditorModel[] = []; + const editor = this._instaService.createInstance(DefaultChatTextEditor); + for (const request of this._session.chatModel.getRequests()) { @@ -94,28 +96,11 @@ export abstract class EditModeStrategy { if (item.kind !== 'textEditGroup') { continue; } - if (item.state?.applied) { - continue; - } if (ignoreLocal && isEqual(item.uri, this._session.textModelN.uri)) { continue; } - const ref = await this._modelService.createModelReference(item.uri); - try { - ref.object.textEditorModel.pushStackElement(); - let total = 0; - for (const group of item.edits) { - const edits = group.map(TextEdit.asEditOperation); - ref.object.textEditorModel.pushEditOperations(null, edits, () => null); - total += edits.length; - } - ref.object.textEditorModel.pushStackElement(); - request.response.setEditApplied(item, total); - - } finally { - ref.dispose(); - } + await editor.apply(request.response, item); if (item.uri.scheme === Schemas.untitled) { const untitled = this._textFileService.untitled.get(item.uri); @@ -207,10 +192,10 @@ export class PreviewStrategy extends EditModeStrategy { zone: InlineChatZoneWidget, @IModelService modelService: IModelService, @IContextKeyService contextKeyService: IContextKeyService, - @ITextModelService textModelService: ITextModelService, @ITextFileService textFileService: ITextFileService, + @IInstantiationService instaService: IInstantiationService ) { - super(session, editor, zone, textModelService, textFileService); + super(session, editor, zone, textFileService, instaService); this._ctxDocumentChanged = CTX_INLINE_CHAT_DOCUMENT_CHANGED.bindTo(contextKeyService); @@ -316,10 +301,10 @@ export class LiveStrategy extends EditModeStrategy { @IEditorWorkerService protected readonly _editorWorkerService: IEditorWorkerService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, @IConfigurationService private readonly _configService: IConfigurationService, - @ITextModelService textModelService: ITextModelService, @ITextFileService textFileService: ITextFileService, + @IInstantiationService instaService: IInstantiationService ) { - super(session, editor, zone, textModelService, textFileService); + super(session, editor, zone, textFileService, instaService); this._ctxCurrentChangeHasDiff = CTX_INLINE_CHAT_CHANGE_HAS_DIFF.bindTo(contextKeyService); this._ctxCurrentChangeShowsDiff = CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF.bindTo(contextKeyService);