From 0817778e016b931cc451f566963c2c5f6d8064ee Mon Sep 17 00:00:00 2001 From: Johannes Date: Thu, 13 Apr 2023 18:06:17 +0200 Subject: [PATCH 01/11] wip live preview --- .../browser/interactiveEditor.css | 6 + .../browser/interactiveEditorController.ts | 17 ++- .../browser/interactiveEditorDiffWidget.ts | 113 ++++++++++++++++++ .../common/interactiveEditor.ts | 16 +-- 4 files changed, 143 insertions(+), 9 deletions(-) create mode 100644 src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditor.css b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditor.css index 7c337cec8bb2a..fcf47491fa6c1 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditor.css +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditor.css @@ -187,3 +187,9 @@ .monaco-editor .interactive-editor-slash-command-detail { opacity: 0.5; } + +/* diff zone */ + +.monaco-editor .interactive-editor-diff-widget { + padding: 6px 0; +} diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts index daf05e025ed2e..4ce1bc7682c5c 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts @@ -43,6 +43,9 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { URI } from 'vs/base/common/uri'; import { isEqual } from 'vs/base/common/resources'; import { decodeBase64 } from 'vs/base/common/buffer'; +import { InteractiveEditorDiffWidget } from 'vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget'; +import { IModelService } from 'vs/editor/common/services/model'; +import { Event } from 'vs/base/common/event'; type Exchange = { req: IInteractiveEditorRequest; res: IInteractiveEditorResponse }; @@ -222,7 +225,7 @@ class LastEditorState { ) { } } -type EditMode = 'preview' | 'direct'; +type EditMode = 'preview' | 'livePreview' | 'direct'; export class InteractiveEditorController implements IEditorContribution { @@ -274,6 +277,7 @@ export class InteractiveEditorController implements IEditorContribution { @ITelemetryService private readonly _telemetryService: ITelemetryService, @IStorageService private readonly _storageService: IStorageService, @IConfigurationService private readonly _configurationService: IConfigurationService, + @IModelService private readonly _modelService: IModelService, @IContextKeyService contextKeyService: IContextKeyService, ) { @@ -412,6 +416,10 @@ export class InteractiveEditorController implements IEditorContribution { const roundStore = new DisposableStore(); store.add(roundStore); + const diffBaseModel = this._modelService.createModel(textModel.getValue(), { languageId: textModel.getLanguageId(), onDidChange: Event.None }, undefined, true); + store.add(diffBaseModel); + const diffZone = this._instaService.createInstance(InteractiveEditorDiffWidget, this._editor, diffBaseModel); + do { round += 1; @@ -423,6 +431,10 @@ export class InteractiveEditorController implements IEditorContribution { break; } + if (round > 1 && editMode === 'livePreview') { + diffZone.show(wholeRangeDecoration.getRange(0)!); + } + // visuals: add block decoration blockDecoration.set([{ range: wholeRange, @@ -618,6 +630,9 @@ export class InteractiveEditorController implements IEditorContribution { this._inlineDiffEnabled = inlineDiffDecorations.visible; this._storageService.store(InteractiveEditorController._inlineDiffStorageKey, this._inlineDiffEnabled, StorageScope.PROFILE, StorageTarget.USER); + diffZone.hide(); + diffZone.dispose(); + // done, cleanup wholeRangeDecoration.clear(); blockDecoration.clear(); diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts new file mode 100644 index 0000000000000..5bc585dbac369 --- /dev/null +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts @@ -0,0 +1,113 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { h } from 'vs/base/browser/dom'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { assertType } from 'vs/base/common/types'; +import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { Position } from 'vs/editor/common/core/position'; +import { IRange } from 'vs/editor/common/core/range'; +import { ScrollType } from 'vs/editor/common/editorCommon'; +import { ITextModel } from 'vs/editor/common/model'; +import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { interactiveEditorRegionHighlight } from 'vs/workbench/contrib/interactiveEditor/common/interactiveEditor'; + +export class InteractiveEditorDiffWidget extends ZoneWidget { + + private static readonly _hideId = 'overlayDiff'; + + private readonly _elements = h('div.interactive-editor-diff-widget@domNode'); + + private readonly _diffEditor: IDiffEditor; + private readonly _sessionStore = this._disposables.add(new DisposableStore()); + + constructor( + editor: ICodeEditor, + private readonly _originalModel: ITextModel, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + ) { + super(editor, { showArrow: false, showFrame: false, isResizeable: false, isAccessible: true }); + super.create(); + + this._diffEditor = instantiationService.createInstance(EmbeddedDiffEditorWidget, this._elements.domNode, { + scrollbar: { useShadows: false, alwaysConsumeMouseWheel: false }, + renderMarginRevertIcon: false, + diffCodeLens: false, + scrollBeyondLastLine: false, + stickyScroll: { enabled: false } + }, editor); + this._disposables.add(this._diffEditor); + + const highlightBgColor = themeService.getColorTheme().getColor(interactiveEditorRegionHighlight)?.toString() ?? ''; + this._elements.domNode.style.setProperty('--vscode-editor-background', highlightBgColor); + this._elements.domNode.style.setProperty('--vscode-editorGutter-background', highlightBgColor); + } + + protected override _fillContainer(container: HTMLElement): void { + container.appendChild(this._elements.domNode); + } + + override show(range: IRange): void { + assertType(this.editor.hasModel()); + this._sessionStore.clear(); + + this.editor.setHiddenAreas([range], InteractiveEditorDiffWidget._hideId); + const modified = this.editor.getModel(); + const lineHeightDiff = Math.max(1, Math.abs(modified.getLineCount() - this._originalModel.getLineCount()) + (range.endLineNumber - range.startLineNumber)); + const lineHeightPadding = (this.editor.getOption(EditorOption.lineHeight) / 12) /* padding-top/bottom*/; + + this._diffEditor.setModel({ original: this._originalModel, modified }); + this._diffEditor.revealRange(range, ScrollType.Immediate); + this._diffEditor.getModifiedEditor().setHiddenAreas(InteractiveEditorDiffWidget._invert(range, modified), InteractiveEditorDiffWidget._hideId); + // this._diffEditor.getOriginalEditor().setHiddenAreas(InteractiveEditorDiffWidget._invert(rangeOriginal, this._originalModel), InteractiveEditorDiffWidget._hideId); + + const updateHiddenAreasOriginal = () => { + // todo@jrieken this needs work when both are equal + const changes = this._diffEditor.getLineChanges(); + if (!changes) { + return; + } + let startLine = Number.MAX_VALUE; + let endLine = 0; + for (const change of changes) { + startLine = Math.min(startLine, change.originalStartLineNumber); + endLine = Math.max(endLine, change.originalEndLineNumber || change.originalStartLineNumber); + } + const originalRange = this._originalModel.validateRange({ startLineNumber: startLine, startColumn: 1, endLineNumber: endLine, endColumn: Number.MAX_VALUE }); + + const hiddenRanges = InteractiveEditorDiffWidget._invert(originalRange, this._originalModel); + this._diffEditor.getOriginalEditor().setHiddenAreas(hiddenRanges, InteractiveEditorDiffWidget._hideId); + }; + this._diffEditor.onDidUpdateDiff(updateHiddenAreasOriginal, undefined, this._sessionStore); + updateHiddenAreasOriginal(); + + super.show(new Position(range.endLineNumber, 1), lineHeightDiff + lineHeightPadding); + } + + private static _invert(range: IRange, model: ITextModel): IRange[] { + const result: IRange[] = []; + if (range.startLineNumber > 1) { + result.push({ startLineNumber: 1, startColumn: 1, endLineNumber: range.startLineNumber - 1, endColumn: 1 }); + } + if (range.endLineNumber < model.getLineCount()) { + result.push({ startLineNumber: range.endLineNumber + 1, startColumn: 1, endLineNumber: model.getLineCount(), endColumn: 1 }); + } + return result; + } + + override hide(): void { + this.editor.setHiddenAreas([], InteractiveEditorDiffWidget._hideId); + super.hide(); + } + + protected override _doLayout(heightInPixel: number, widthInPixel: number): void { + this._diffEditor.layout({ height: heightInPixel - 12 /* padding */, width: widthInPixel }); + } +} diff --git a/src/vs/workbench/contrib/interactiveEditor/common/interactiveEditor.ts b/src/vs/workbench/contrib/interactiveEditor/common/interactiveEditor.ts index 1da1384f1c938..808bc3da28826 100644 --- a/src/vs/workbench/contrib/interactiveEditor/common/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactiveEditor/common/interactiveEditor.ts @@ -125,13 +125,13 @@ MenuRegistry.appendMenuItem(MENU_INTERACTIVE_EDITOR_WIDGET_STATUS, { // --- colors -registerColor('interactiveEditor.border', { dark: editorWidgetBorder, light: editorWidgetBorder, hcDark: editorWidgetBorder, hcLight: editorWidgetBorder }, localize('interactiveEditor.border', "Border color of the interactive editor widget")); -registerColor('interactiveEditor.shadow', { dark: widgetShadow, light: widgetShadow, hcDark: widgetShadow, hcLight: widgetShadow }, localize('interactiveEditor.shadow', "Shadow color of the interactive editor widget")); -registerColor('interactiveEditor.regionHighlight', { dark: editorHoverHighlight, light: editorHoverHighlight, hcDark: editorHoverHighlight, hcLight: editorHoverHighlight }, localize('interactiveEditor.regionHighlight', "Background highlighting of the current interactive region. Must be transparent."), true); -registerColor('interactiveEditorInput.border', { dark: editorWidgetBorder, light: editorWidgetBorder, hcDark: editorWidgetBorder, hcLight: editorWidgetBorder }, localize('interactiveEditorInput.border', "Border color of the interactive editor input")); -registerColor('interactiveEditorInput.focusBorder', { dark: focusBorder, light: focusBorder, hcDark: focusBorder, hcLight: focusBorder }, localize('interactiveEditorInput.focusBorder', "Border color of the interactive editor input when focused")); -registerColor('interactiveEditorInput.placeholderForeground', { dark: inputPlaceholderForeground, light: inputPlaceholderForeground, hcDark: inputPlaceholderForeground, hcLight: inputPlaceholderForeground }, localize('interactiveEditorInput.placeholderForeground', "Foreground color of the interactive editor input placeholder")); -registerColor('interactiveEditorInput.background', { dark: inputBackground, light: inputBackground, hcDark: inputBackground, hcLight: inputBackground }, localize('interactiveEditorInput.background', "Background color of the interactive editor input")); +export const interactiveEditorBorder = registerColor('interactiveEditor.border', { dark: editorWidgetBorder, light: editorWidgetBorder, hcDark: editorWidgetBorder, hcLight: editorWidgetBorder }, localize('interactiveEditor.border', "Border color of the interactive editor widget")); +export const interactiveEditorShadow = registerColor('interactiveEditor.shadow', { dark: widgetShadow, light: widgetShadow, hcDark: widgetShadow, hcLight: widgetShadow }, localize('interactiveEditor.shadow', "Shadow color of the interactive editor widget")); +export const interactiveEditorRegionHighlight = registerColor('interactiveEditor.regionHighlight', { dark: editorHoverHighlight, light: editorHoverHighlight, hcDark: editorHoverHighlight, hcLight: editorHoverHighlight }, localize('interactiveEditor.regionHighlight', "Background highlighting of the current interactive region. Must be transparent."), true); +export const interactiveEditorInputBorder = registerColor('interactiveEditorInput.border', { dark: editorWidgetBorder, light: editorWidgetBorder, hcDark: editorWidgetBorder, hcLight: editorWidgetBorder }, localize('interactiveEditorInput.border', "Border color of the interactive editor input")); +export const interactiveEditorInputFocusBorder = registerColor('interactiveEditorInput.focusBorder', { dark: focusBorder, light: focusBorder, hcDark: focusBorder, hcLight: focusBorder }, localize('interactiveEditorInput.focusBorder', "Border color of the interactive editor input when focused")); +export const interactiveEditorInputPlaceholderForeground = registerColor('interactiveEditorInput.placeholderForeground', { dark: inputPlaceholderForeground, light: inputPlaceholderForeground, hcDark: inputPlaceholderForeground, hcLight: inputPlaceholderForeground }, localize('interactiveEditorInput.placeholderForeground', "Foreground color of the interactive editor input placeholder")); +export const interactiveEditorInputBackground = registerColor('interactiveEditorInput.background', { dark: inputBackground, light: inputBackground, hcDark: inputBackground, hcLight: inputBackground }, localize('interactiveEditorInput.background', "Background color of the interactive editor input")); // settings @@ -142,7 +142,7 @@ Registry.as(Extensions.Configuration).registerConfigurat description: localize('editMode', "Configure if changes crafted in the interactive editor are applied directly or previewed first"), default: 'direct', type: 'string', - enum: ['preview', 'direct'] + enum: ['preview', 'livePreview', 'direct'] } } }); From 9567c0cea0e67d224525b6574e9fb90012f35d92 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 14 Apr 2023 08:57:29 +0200 Subject: [PATCH 02/11] fix default width on first showing --- .../interactiveEditor/browser/interactiveEditorWidget.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts index f1cf3ef3b8dc1..f47e445f0bd21 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts @@ -119,9 +119,9 @@ class InteractiveEditorWidget { ]), ]), h('div.progress@progress'), - h('div.previewDiff@previewDiff'), + h('div.previewDiff.hidden@previewDiff'), h('div.previewCreateTitle.show-file-icons@previewCreateTitle'), - h('div.previewCreate@previewCreate'), + h('div.previewCreate.hidden@previewCreate'), h('div.status@status', [ h('div.actions.hidden@statusToolbar'), h('div.label@statusLabel'), From bb08d3697e01b6f4eb2ec3ff1e20d24276f1d001 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 14 Apr 2023 10:08:23 +0200 Subject: [PATCH 03/11] extract some parts into preview and live strategies --- .../browser/interactiveEditorActions.ts | 56 ++++++---- .../browser/interactiveEditorController.ts | 102 +++++++++++++++--- .../common/interactiveEditor.ts | 12 +-- 3 files changed, 123 insertions(+), 47 deletions(-) diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts index 0baa817e53325..2a2efc00fad79 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts @@ -124,25 +124,6 @@ export class StopRequestAction extends AbstractInteractiveEditorAction { } } -export class CancelSessionAction extends AbstractInteractiveEditorAction { - - constructor() { - super({ - id: 'interactiveEditor.cancel', - title: localize('cancel', 'Cancel'), - precondition: CTX_INTERACTIVE_EDITOR_VISIBLE, - keybinding: { - weight: KeybindingWeight.EditorContrib - 1, - primary: KeyCode.Escape - } - }); - } - - runInteractiveEditorCommand(_accessor: ServicesAccessor, ctrl: InteractiveEditorController, _editor: ICodeEditor, ..._args: any[]): void { - ctrl.cancelSession(); - } -} - export class ArrowOutUpAction extends AbstractInteractiveEditorAction { constructor() { super({ @@ -380,7 +361,7 @@ export class ToggleInlineDiff extends AbstractInteractiveEditorAction { id: MENU_INTERACTIVE_EDITOR_WIDGET_STATUS, when: CTX_INTERACTIVE_EDITOR_EDIT_MODE.isEqualTo('direct'), group: '0_main', - order: 1 + order: 10 } }); } @@ -397,10 +378,14 @@ export class ApplyPreviewEdits extends AbstractInteractiveEditorAction { id: 'interactiveEditor.applyEdits', title: localize('applyEdits', 'Apply Changes'), icon: Codicon.check, - precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_VISIBLE, CTX_INTERACTIVE_EDITOR_EDIT_MODE.isEqualTo('preview')), + precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_VISIBLE), + keybinding: { + weight: KeybindingWeight.EditorContrib + 10, + primary: KeyMod.CtrlCmd | KeyCode.Enter + }, menu: { id: MENU_INTERACTIVE_EDITOR_WIDGET_STATUS, - when: CTX_INTERACTIVE_EDITOR_EDIT_MODE.isEqualTo('preview'), + // when: CTX_INTERACTIVE_EDITOR_EDIT_MODE.isEqualTo('preview'), group: '0_main', order: 0 } @@ -415,7 +400,6 @@ export class ApplyPreviewEdits extends AbstractInteractiveEditorAction { logService.warn('FAILED to apply changes, no edit response'); return; } - ctrl.cancelSession(); if (edit.singleCreateFileEdit) { editorService.openEditor({ resource: edit.singleCreateFileEdit.uri }, SIDE_GROUP); } @@ -423,6 +407,32 @@ export class ApplyPreviewEdits extends AbstractInteractiveEditorAction { } } +export class CancelSessionAction extends AbstractInteractiveEditorAction { + + constructor() { + super({ + id: 'interactiveEditor.cancel', + title: localize('cancel', 'Cancel'), + icon: Codicon.clearAll, + precondition: CTX_INTERACTIVE_EDITOR_VISIBLE, + keybinding: { + weight: KeybindingWeight.EditorContrib - 1, + primary: KeyCode.Escape + }, + menu: { + id: MENU_INTERACTIVE_EDITOR_WIDGET_STATUS, + // when: CTX_INTERACTIVE_EDITOR_EDIT_MODE.isEqualTo('preview'), + group: '0_main', + order: 1 + } + }); + } + + runInteractiveEditorCommand(_accessor: ServicesAccessor, ctrl: InteractiveEditorController, _editor: ICodeEditor, ..._args: any[]): void { + ctrl.cancelSession(); + } +} + export class CopyRecordings extends AbstractInteractiveEditorAction { constructor() { diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts index 4ce1bc7682c5c..5b17201eb77f5 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts @@ -225,7 +225,7 @@ class LastEditorState { ) { } } -type EditMode = 'preview' | 'livePreview' | 'direct'; +type EditMode = 'live' | 'livePreview' | 'preview'; export class InteractiveEditorController implements IEditorContribution { @@ -261,6 +261,7 @@ export class InteractiveEditorController implements IEditorContribution { private readonly _ctxLastFeedbackKind: IContextKey<'helpful' | 'unhelpful' | ''>; private _lastEditState?: LastEditorState; + private _strategy?: LiveStrategy | PreviewStrategy; private _lastInlineDecorations?: InlineDiffDecorations; private _inlineDiffEnabled: boolean = false; @@ -271,7 +272,6 @@ export class InteractiveEditorController implements IEditorContribution { private readonly _editor: ICodeEditor, @IInstantiationService private readonly _instaService: IInstantiationService, @IInteractiveEditorService private readonly _interactiveEditorService: IInteractiveEditorService, - @IBulkEditService private readonly _bulkEditService: IBulkEditService, @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, @ILogService private readonly _logService: ILogService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @@ -299,9 +299,13 @@ export class InteractiveEditorController implements IEditorContribution { return InteractiveEditorController.ID; } + private _getMode(): EditMode { + return this._configurationService.getValue('interactiveEditor.editMode'); + } + async run(initialRange?: Range): Promise { - const editMode: EditMode = this._configurationService.getValue('interactiveEditor.editMode'); + const editMode = this._getMode(); this._ctsSession.dispose(true); @@ -336,6 +340,8 @@ export class InteractiveEditorController implements IEditorContribution { undos: '' }; + this._strategy = this._instaService.createInstance(editMode === 'preview' ? PreviewStrategy : LiveStrategy, textModel, textModel.getAlternativeVersionId()); + this._inlineDiffEnabled = this._storageService.getBoolean(InteractiveEditorController._inlineDiffStorageKey, StorageScope.PROFILE, false); const inlineDiffDecorations = new InlineDiffDecorations(this._editor, this._inlineDiffEnabled); this._lastInlineDecorations = inlineDiffDecorations; @@ -525,9 +531,8 @@ export class InteractiveEditorController implements IEditorContribution { const editResponse = new EditResponse(textModel.uri, reply); - if (editResponse.workspaceEdits && (!editResponse.singleCreateFileEdit || editMode === 'direct')) { - this._bulkEditService.apply(editResponse.workspaceEdits, { editor: this._editor, label: localize('ie', "{0}", input), showPreview: true }); - // todo@jrieken keep interactive editor? + const canContinue = this._strategy.update(editResponse); + if (!canContinue) { break; } @@ -583,7 +588,9 @@ export class InteractiveEditorController implements IEditorContribution { ignoreModelChanges = false; } - inlineDiffDecorations.update(); + if (editMode === 'live') { + inlineDiffDecorations.update(); + } // line count const lineSet = new Set(); @@ -664,10 +671,6 @@ export class InteractiveEditorController implements IEditorContribution { this._ctsRequest?.cancel(); } - cancelSession() { - this._ctsSession.cancel(); - } - arrowOut(up: boolean): void { if (this._zone.position && this._editor.hasModel()) { const { column } = this._editor.getPosition(); @@ -730,22 +733,87 @@ export class InteractiveEditorController implements IEditorContribution { if (!this._lastEditState) { return undefined; } + const { response } = this._lastEditState; + await this._strategy?.apply(); + this._ctsSession.cancel(); + return response; + } + + cancelSession() { + this._ctsSession.cancel(); + this._strategy?.cancel(); + } +} + +class PreviewStrategy { + + private _lastResponse?: EditResponse; + + constructor( + private readonly _model: ITextModel, + private readonly _versionId: number, + @IBulkEditService private readonly _bulkEditService: IBulkEditService, + ) { } + + update(response: EditResponse): boolean { + this._lastResponse = response; + if (!response.workspaceEdits || response.singleCreateFileEdit) { + // preview stategy can handle simple workspace edit (single file create) + return true; + } + this._bulkEditService.apply(response.workspaceEdits, { showPreview: true }); + return false; + } - const { model, modelVersionId, response } = this._lastEditState; + async apply() { + + const response = this._lastResponse; + if (!response) { + return; + } if (response.workspaceEdits) { await this._bulkEditService.apply(response.workspaceEdits); } else if (!response.workspaceEditsIncludeLocalEdits) { - if (model.getAlternativeVersionId() === modelVersionId) { - model.pushStackElement(); + if (this._model.getAlternativeVersionId() === this._versionId) { + this._model.pushStackElement(); const edits = response.localEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)); - model.pushEditOperations(null, edits, () => null); - model.pushStackElement(); + this._model.pushEditOperations(null, edits, () => null); + this._model.pushStackElement(); } } + } - return response; + cancel(): void { + // nothing to do + } +} + +class LiveStrategy { + + constructor( + private readonly _model: ITextModel, + private readonly _versionId: number, + @IBulkEditService private readonly _bulkEditService: IBulkEditService, + ) { } + + update(response: EditResponse): boolean { + if (response.workspaceEdits) { + this._bulkEditService.apply(response.workspaceEdits, { showPreview: true }); + return false; + } + return true; + } + + async apply() { + // nothing to do + } + + cancel(): void { + while (this._model.getAlternativeVersionId() !== this._versionId) { + this._model.undo(); + } } } diff --git a/src/vs/workbench/contrib/interactiveEditor/common/interactiveEditor.ts b/src/vs/workbench/contrib/interactiveEditor/common/interactiveEditor.ts index 808bc3da28826..3760f187ea3a6 100644 --- a/src/vs/workbench/contrib/interactiveEditor/common/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactiveEditor/common/interactiveEditor.ts @@ -14,7 +14,7 @@ import { ITextModel } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; -import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; import { editorHoverHighlight, editorWidgetBorder, focusBorder, inputBackground, inputPlaceholderForeground, registerColor, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; @@ -105,9 +105,7 @@ export const CTX_INTERACTIVE_EDITOR_HAS_RESPONSE = new RawContextKey('i export const CTX_INTERACTIVE_EDITOR_INLNE_DIFF = new RawContextKey('interactiveEditorInlineDiff', false, localize('interactiveEditorInlineDiff', "Whether interactive editor show inline diffs for changes")); export const CTX_INTERACTIVE_EDITOR_LAST_EDIT_TYPE = new RawContextKey<'simple' | ''>('interactiveEditorLastEditKind', '', localize('interactiveEditorLastEditKind', "The last kind of edit that was performed")); export const CTX_INTERACTIVE_EDITOR_LAST_FEEDBACK = new RawContextKey<'unhelpful' | 'helpful' | ''>('interactiveEditorLastFeedbackKind', '', localize('interactiveEditorLastFeedbackKind', "The last kind of feedback that was provided")); - -export const CTX_VALUE_INTERACTIVE_EDITOR_EDIT_MODE_YOLO = ContextKeyExpr.equals('config.interactiveEditor.editMode', 'direct'); -export const CTX_INTERACTIVE_EDITOR_EDIT_MODE = new RawContextKey<'direct' | 'preview'>('config.interactiveEditor.editMode', 'direct'); +export const CTX_INTERACTIVE_EDITOR_EDIT_MODE = new RawContextKey<'live' | 'livePreview' | 'preview'>('config.interactiveEditor.editMode', 'live'); // --- menus @@ -119,7 +117,7 @@ MenuRegistry.appendMenuItem(MENU_INTERACTIVE_EDITOR_WIDGET_STATUS, { title: localize('undo', "Undo..."), icon: Codicon.discard, group: '0_main', - order: 0, + order: 2, when: CTX_INTERACTIVE_EDITOR_EDIT_MODE.isEqualTo('direct') }); @@ -140,9 +138,9 @@ Registry.as(Extensions.Configuration).registerConfigurat properties: { 'interactiveEditor.editMode': { description: localize('editMode', "Configure if changes crafted in the interactive editor are applied directly or previewed first"), - default: 'direct', + default: 'live', type: 'string', - enum: ['preview', 'livePreview', 'direct'] + enum: ['live', 'livePreview', 'preview'] } } }); From d4a641763378738e30d2f2d35ac8a477ec4c6999 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 14 Apr 2023 10:14:38 +0200 Subject: [PATCH 04/11] update diff editor width on resize --- .../browser/interactiveEditorDiffWidget.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts index 5bc585dbac369..4e9754cb1a839 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { h } from 'vs/base/browser/dom'; +import { Dimension, h } from 'vs/base/browser/dom'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { assertType } from 'vs/base/common/types'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; @@ -26,6 +26,7 @@ export class InteractiveEditorDiffWidget extends ZoneWidget { private readonly _diffEditor: IDiffEditor; private readonly _sessionStore = this._disposables.add(new DisposableStore()); + private _dim: Dimension | undefined; constructor( editor: ICodeEditor, @@ -107,7 +108,18 @@ export class InteractiveEditorDiffWidget extends ZoneWidget { super.hide(); } + protected override _onWidth(widthInPixel: number): void { + if (this._dim) { + this._doLayout(this._dim.height, widthInPixel); + } + } + protected override _doLayout(heightInPixel: number, widthInPixel: number): void { - this._diffEditor.layout({ height: heightInPixel - 12 /* padding */, width: widthInPixel }); + const newDim = new Dimension(widthInPixel, heightInPixel); + if (Dimension.equals(this._dim, newDim)) { + return; + } + this._dim = newDim; + this._diffEditor.layout(this._dim.with(undefined, this._dim.height - 12 /* padding */)); } } From 0f6b22721abc39d6afc92fb62fb3b46fd0ffee5a Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 14 Apr 2023 10:27:06 +0200 Subject: [PATCH 05/11] improve hidden area in diff editor --- .../browser/interactiveEditorDiffWidget.ts | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts index 4e9754cb1a839..87b9c9db8b34d 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts @@ -66,8 +66,7 @@ export class InteractiveEditorDiffWidget extends ZoneWidget { this._diffEditor.setModel({ original: this._originalModel, modified }); this._diffEditor.revealRange(range, ScrollType.Immediate); - this._diffEditor.getModifiedEditor().setHiddenAreas(InteractiveEditorDiffWidget._invert(range, modified), InteractiveEditorDiffWidget._hideId); - // this._diffEditor.getOriginalEditor().setHiddenAreas(InteractiveEditorDiffWidget._invert(rangeOriginal, this._originalModel), InteractiveEditorDiffWidget._hideId); + this._diffEditor.getModifiedEditor().setHiddenAreas(invertRange(range, modified), InteractiveEditorDiffWidget._hideId); const updateHiddenAreasOriginal = () => { // todo@jrieken this needs work when both are equal @@ -78,13 +77,16 @@ export class InteractiveEditorDiffWidget extends ZoneWidget { let startLine = Number.MAX_VALUE; let endLine = 0; for (const change of changes) { - startLine = Math.min(startLine, change.originalStartLineNumber); - endLine = Math.max(endLine, change.originalEndLineNumber || change.originalStartLineNumber); + startLine = Math.min(startLine, change.originalStartLineNumber, change.modifiedStartLineNumber); + endLine = Math.max(endLine, change.originalEndLineNumber || change.originalStartLineNumber, change.modifiedEndLineNumber || change.modifiedStartLineNumber); } - const originalRange = this._originalModel.validateRange({ startLineNumber: startLine, startColumn: 1, endLineNumber: endLine, endColumn: Number.MAX_VALUE }); + const combinedRange = this._originalModel.validateRange({ startLineNumber: startLine, startColumn: 1, endLineNumber: endLine, endColumn: Number.MAX_VALUE }); - const hiddenRanges = InteractiveEditorDiffWidget._invert(originalRange, this._originalModel); - this._diffEditor.getOriginalEditor().setHiddenAreas(hiddenRanges, InteractiveEditorDiffWidget._hideId); + const hiddenRangesOriginal = invertRange(combinedRange, this._originalModel); + this._diffEditor.getOriginalEditor().setHiddenAreas(hiddenRangesOriginal, InteractiveEditorDiffWidget._hideId); + + const hiddenRangesModified = invertRange(combinedRange, modified); + this._diffEditor.getModifiedEditor().setHiddenAreas(hiddenRangesModified, InteractiveEditorDiffWidget._hideId); }; this._diffEditor.onDidUpdateDiff(updateHiddenAreasOriginal, undefined, this._sessionStore); updateHiddenAreasOriginal(); @@ -92,17 +94,6 @@ export class InteractiveEditorDiffWidget extends ZoneWidget { super.show(new Position(range.endLineNumber, 1), lineHeightDiff + lineHeightPadding); } - private static _invert(range: IRange, model: ITextModel): IRange[] { - const result: IRange[] = []; - if (range.startLineNumber > 1) { - result.push({ startLineNumber: 1, startColumn: 1, endLineNumber: range.startLineNumber - 1, endColumn: 1 }); - } - if (range.endLineNumber < model.getLineCount()) { - result.push({ startLineNumber: range.endLineNumber + 1, startColumn: 1, endLineNumber: model.getLineCount(), endColumn: 1 }); - } - return result; - } - override hide(): void { this.editor.setHiddenAreas([], InteractiveEditorDiffWidget._hideId); super.hide(); @@ -123,3 +114,14 @@ export class InteractiveEditorDiffWidget extends ZoneWidget { this._diffEditor.layout(this._dim.with(undefined, this._dim.height - 12 /* padding */)); } } + +function invertRange(range: IRange, model: ITextModel): IRange[] { + const result: IRange[] = []; + if (range.startLineNumber > 1) { + result.push({ startLineNumber: 1, startColumn: 1, endLineNumber: range.startLineNumber - 1, endColumn: 1 }); + } + if (range.endLineNumber < model.getLineCount()) { + result.push({ startLineNumber: range.endLineNumber + 1, startColumn: 1, endLineNumber: model.getLineCount(), endColumn: 1 }); + } + return result; +} From f2624a22056f9ef504383738879753d49b19f1e1 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 14 Apr 2023 10:28:53 +0200 Subject: [PATCH 06/11] full cancel when different model is set --- .../interactiveEditor/browser/interactiveEditorController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts index 5b17201eb77f5..c5521c3bd3712 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts @@ -377,7 +377,7 @@ export class InteractiveEditorController implements IEditorContribution { this._zone.widget.updateMessage(session.message ?? localize('welcome.1', "AI-generated code may be incorrect.")); // CANCEL when input changes - this._editor.onDidChangeModel(this._ctsSession.cancel, this._ctsSession, store); + this._editor.onDidChangeModel(this.cancelSession, this, store); // REposition the zone widget whenever the block decoration changes let lastPost: Position | undefined; From e3888ffb1d77f0633b130909bffcee09aedccb6b Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 14 Apr 2023 11:11:16 +0200 Subject: [PATCH 07/11] tweak colors of embedded diff editor (livePreview) --- .../browser/interactiveEditorDiffWidget.ts | 28 ++++++++++++++++--- .../common/interactiveEditor.ts | 5 +++- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts index 87b9c9db8b34d..0fd0ddf7d2008 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts @@ -15,8 +15,10 @@ import { ScrollType } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; +import * as editorColorRegistry from 'vs/editor/common/core/editorColorRegistry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { interactiveEditorRegionHighlight } from 'vs/workbench/contrib/interactiveEditor/common/interactiveEditor'; +import { interactiveEditorDiffInserted, interactiveEditorDiffRemoved, interactiveEditorRegionHighlight } from 'vs/workbench/contrib/interactiveEditor/common/interactiveEditor'; export class InteractiveEditorDiffWidget extends ZoneWidget { @@ -46,9 +48,27 @@ export class InteractiveEditorDiffWidget extends ZoneWidget { }, editor); this._disposables.add(this._diffEditor); - const highlightBgColor = themeService.getColorTheme().getColor(interactiveEditorRegionHighlight)?.toString() ?? ''; - this._elements.domNode.style.setProperty('--vscode-editor-background', highlightBgColor); - this._elements.domNode.style.setProperty('--vscode-editorGutter-background', highlightBgColor); + const doStyle = () => { + const theme = themeService.getColorTheme(); + const overrides: [target: string, source: string][] = [ + [colorRegistry.editorBackground, interactiveEditorRegionHighlight], + [editorColorRegistry.editorGutter, interactiveEditorRegionHighlight], + [colorRegistry.diffInsertedLine, interactiveEditorDiffInserted], + [colorRegistry.diffInserted, interactiveEditorDiffInserted], + [colorRegistry.diffRemovedLine, interactiveEditorDiffRemoved], + [colorRegistry.diffRemoved, interactiveEditorDiffRemoved], + ]; + + for (const [target, source] of overrides) { + const value = theme.getColor(source); + if (value) { + this._elements.domNode.style.setProperty(colorRegistry.asCssVariableName(target), String(value)); + } + } + }; + doStyle(); + this._disposables.add(themeService.onDidColorThemeChange(doStyle)); + } protected override _fillContainer(container: HTMLElement): void { diff --git a/src/vs/workbench/contrib/interactiveEditor/common/interactiveEditor.ts b/src/vs/workbench/contrib/interactiveEditor/common/interactiveEditor.ts index 3760f187ea3a6..0aacbece7e730 100644 --- a/src/vs/workbench/contrib/interactiveEditor/common/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactiveEditor/common/interactiveEditor.ts @@ -17,7 +17,7 @@ import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/co import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; -import { editorHoverHighlight, editorWidgetBorder, focusBorder, inputBackground, inputPlaceholderForeground, registerColor, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; +import { diffInserted, diffRemoved, editorHoverHighlight, editorWidgetBorder, focusBorder, inputBackground, inputPlaceholderForeground, registerColor, transparent, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; export interface IInteractiveEditorSlashCommand { command: string; @@ -131,6 +131,9 @@ export const interactiveEditorInputFocusBorder = registerColor('interactiveEdito export const interactiveEditorInputPlaceholderForeground = registerColor('interactiveEditorInput.placeholderForeground', { dark: inputPlaceholderForeground, light: inputPlaceholderForeground, hcDark: inputPlaceholderForeground, hcLight: inputPlaceholderForeground }, localize('interactiveEditorInput.placeholderForeground', "Foreground color of the interactive editor input placeholder")); export const interactiveEditorInputBackground = registerColor('interactiveEditorInput.background', { dark: inputBackground, light: inputBackground, hcDark: inputBackground, hcLight: inputBackground }, localize('interactiveEditorInput.background', "Background color of the interactive editor input")); +export const interactiveEditorDiffInserted = registerColor('interactiveEditorDiff.inserted', { dark: transparent(diffInserted, .5), light: transparent(diffInserted, .5), hcDark: transparent(diffInserted, .5), hcLight: transparent(diffInserted, .5) }, localize('interactiveEditorDiff.inserted', "Background color of inserted text in the interactive editor input")); +export const interactiveEditorDiffRemoved = registerColor('interactiveEditorDiff.removed', { dark: transparent(diffRemoved, .5), light: transparent(diffRemoved, .5), hcDark: transparent(diffRemoved, .5), hcLight: transparent(diffRemoved, .5) }, localize('interactiveEditorDiff.removed', "Background color of removed text in the interactive editor input")); + // settings Registry.as(Extensions.Configuration).registerConfiguration({ From 324596dcb23c729cdeb6508e672b16a15d152a23 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 14 Apr 2023 14:08:51 +0200 Subject: [PATCH 08/11] support simple file create preview with live preview --- .../browser/interactiveEditorController.ts | 60 +++++++++++++++---- .../browser/interactiveEditorDiffWidget.ts | 3 + 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts index c5521c3bd3712..d45f633964d39 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts @@ -261,7 +261,7 @@ export class InteractiveEditorController implements IEditorContribution { private readonly _ctxLastFeedbackKind: IContextKey<'helpful' | 'unhelpful' | ''>; private _lastEditState?: LastEditorState; - private _strategy?: LiveStrategy | PreviewStrategy; + private _strategy?: EditModeStrategy; private _lastInlineDecorations?: InlineDiffDecorations; private _inlineDiffEnabled: boolean = false; @@ -340,7 +340,10 @@ export class InteractiveEditorController implements IEditorContribution { undos: '' }; - this._strategy = this._instaService.createInstance(editMode === 'preview' ? PreviewStrategy : LiveStrategy, textModel, textModel.getAlternativeVersionId()); + this._strategy = this._instaService.createInstance( + editMode === 'preview' ? PreviewStrategy : editMode === 'livePreview' ? LivePreviewStrategy : LiveStrategy, + textModel, textModel.getAlternativeVersionId() + ); this._inlineDiffEnabled = this._storageService.getBoolean(InteractiveEditorController._inlineDiffStorageKey, StorageScope.PROFILE, false); const inlineDiffDecorations = new InlineDiffDecorations(this._editor, this._inlineDiffEnabled); @@ -438,7 +441,11 @@ export class InteractiveEditorController implements IEditorContribution { } if (round > 1 && editMode === 'livePreview') { - diffZone.show(wholeRangeDecoration.getRange(0)!); + if (!textModel.equalsTextBuffer(diffBaseModel.getTextBuffer())) { + diffZone.show(wholeRangeDecoration.getRange(0)!); + } else { + diffZone.hide(); + } } // visuals: add block decoration @@ -745,7 +752,16 @@ export class InteractiveEditorController implements IEditorContribution { } } -class PreviewStrategy { +abstract class EditModeStrategy { + + abstract update(response: EditResponse): boolean; + + abstract apply(): Promise; + + abstract cancel(): void; +} + +class PreviewStrategy extends EditModeStrategy { private _lastResponse?: EditResponse; @@ -753,7 +769,9 @@ class PreviewStrategy { private readonly _model: ITextModel, private readonly _versionId: number, @IBulkEditService private readonly _bulkEditService: IBulkEditService, - ) { } + ) { + super(); + } update(response: EditResponse): boolean { this._lastResponse = response; @@ -790,13 +808,15 @@ class PreviewStrategy { } } -class LiveStrategy { +class LiveStrategy extends EditModeStrategy { constructor( - private readonly _model: ITextModel, - private readonly _versionId: number, - @IBulkEditService private readonly _bulkEditService: IBulkEditService, - ) { } + protected readonly _model: ITextModel, + protected readonly _versionId: number, + @IBulkEditService protected readonly _bulkEditService: IBulkEditService, + ) { + super(); + } update(response: EditResponse): boolean { if (response.workspaceEdits) { @@ -817,6 +837,26 @@ class LiveStrategy { } } +class LivePreviewStrategy extends LiveStrategy { + + private _lastResponse?: EditResponse; + + override update(response: EditResponse): boolean { + this._lastResponse = response; + if (response.singleCreateFileEdit) { + // preview stategy can handle simple workspace edit (single file create) + return true; + } + return super.update(response); + } + + override async apply() { + if (this._lastResponse?.workspaceEdits) { + await this._bulkEditService.apply(this._lastResponse.workspaceEdits); + } + } +} + function installSlashCommandSupport(accessor: ServicesAccessor, editor: IActiveCodeEditor, commands: IInteractiveEditorSlashCommand[]) { const languageFeaturesService = accessor.get(ILanguageFeaturesService); diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts index 0fd0ddf7d2008..11d7c15351a7b 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts @@ -116,6 +116,9 @@ export class InteractiveEditorDiffWidget extends ZoneWidget { override hide(): void { this.editor.setHiddenAreas([], InteractiveEditorDiffWidget._hideId); + this._diffEditor.getOriginalEditor().setHiddenAreas([], InteractiveEditorDiffWidget._hideId); + this._diffEditor.getModifiedEditor().setHiddenAreas([], InteractiveEditorDiffWidget._hideId); + this._diffEditor.setModel(null); super.hide(); } From 006bbdf9a8626e210660c76fe9f41e6e4e8dad87 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 14 Apr 2023 14:33:47 +0200 Subject: [PATCH 09/11] tweak action name --- .../interactiveEditor/browser/interactiveEditorActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts index 2a2efc00fad79..21a8f9f956828 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts @@ -412,7 +412,7 @@ export class CancelSessionAction extends AbstractInteractiveEditorAction { constructor() { super({ id: 'interactiveEditor.cancel', - title: localize('cancel', 'Cancel'), + title: localize('discard', 'Discard Changes'), icon: Codicon.clearAll, precondition: CTX_INTERACTIVE_EDITOR_VISIBLE, keybinding: { From 1bb00fa044fffabd9a8d244803cf924f1eddfb4c Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 14 Apr 2023 14:38:22 +0200 Subject: [PATCH 10/11] use better diff editor options fixes https://github.com/microsoft/vscode-internalbacklog/issues/3910 fixes https://github.com/microsoft/vscode-internalbacklog/issues/3907 --- .../browser/interactiveEditorWidget.ts | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts index f47e445f0bd21..5036ca950192a 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts @@ -38,7 +38,7 @@ import { ILanguageSelection } from 'vs/editor/common/languages/language'; import { ResourceLabel } from 'vs/workbench/browser/labels'; import { FileKind } from 'vs/platform/files/common/files'; -const _commonEditorOptions: IEditorConstructionOptions = { +const _inputEditorOptions: IEditorConstructionOptions = { padding: { top: 3, bottom: 2 }, overviewRulerLanes: 0, glyphMargin: false, @@ -68,17 +68,12 @@ const _commonEditorOptions: IEditorConstructionOptions = { wrappingIndent: 'none', renderWhitespace: 'none', dropIntoEditor: { enabled: true }, - quickSuggestions: false, suggest: { showIcons: false, showSnippets: false, showStatusBar: false, - } -}; - -const _inputEditorOptions: IEditorConstructionOptions = { - ..._commonEditorOptions, + }, wordWrap: 'on', ariaLabel: localize('aria-label', "Interactive Editor Input"), fontFamily: DEFAULT_FONT_FAMILY, @@ -87,17 +82,15 @@ const _inputEditorOptions: IEditorConstructionOptions = { }; const _previewEditorEditorOptions: IDiffEditorConstructionOptions = { - ..._commonEditorOptions, - readOnly: true, - wordWrap: 'off', - enableSplitViewResizing: true, - isInEmbeddedEditor: true, - renderOverviewRuler: false, - ignoreTrimWhitespace: false, - renderSideBySide: true, + scrollbar: { useShadows: false, alwaysConsumeMouseWheel: false }, + renderMarginRevertIcon: false, + diffCodeLens: false, + scrollBeyondLastLine: false, + stickyScroll: { enabled: false }, originalAriaLabel: localize('modified', 'Modified'), modifiedAriaLabel: localize('original', 'Original'), diffAlgorithm: 'smart', + readOnly: true, }; class InteractiveEditorWidget { From 294bc7b4d0187742089050bf904f2f5a1f690012 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 14 Apr 2023 14:57:39 +0200 Subject: [PATCH 11/11] allow to pass widget options to embedded diff editor, don't create IE controller inside its nested editors ensure IE commands run from within nested diff editors fixes https://github.com/microsoft/vscode-internalbacklog/issues/3911 --- .../editor/browser/widget/embeddedCodeEditorWidget.ts | 5 +++-- .../browser/interactiveEditorActions.ts | 10 +++++++++- .../browser/interactiveEditorDiffWidget.ts | 3 +++ .../browser/interactiveEditorWidget.ts | 2 +- .../contrib/scm/browser/dirtydiffDecorator.ts | 2 +- .../contrib/testing/browser/testingOutputPeek.ts | 1 + 6 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts index 9fea174a584d0..553470a8494bc 100644 --- a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts +++ b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts @@ -7,7 +7,7 @@ import * as objects from 'vs/base/common/objects'; import { ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; -import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; +import { DiffEditorWidget, IDiffCodeEditorWidgetOptions } from 'vs/editor/browser/widget/diffEditorWidget'; import { ConfigurationChangedEvent, IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -75,6 +75,7 @@ export class EmbeddedDiffEditorWidget extends DiffEditorWidget { constructor( domElement: HTMLElement, options: Readonly, + codeEditorWidgetOptions: IDiffCodeEditorWidgetOptions, parentEditor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService, @IInstantiationService instantiationService: IInstantiationService, @@ -85,7 +86,7 @@ export class EmbeddedDiffEditorWidget extends DiffEditorWidget { @IClipboardService clipboardService: IClipboardService, @IEditorProgressService editorProgressService: IEditorProgressService, ) { - super(domElement, parentEditor.getRawOptions(), {}, clipboardService, contextKeyService, instantiationService, codeEditorService, themeService, notificationService, contextMenuService, editorProgressService); + super(domElement, parentEditor.getRawOptions(), codeEditorWidgetOptions, clipboardService, contextKeyService, instantiationService, codeEditorService, themeService, notificationService, contextMenuService, editorProgressService); this._parentEditor = parentEditor; this._overwriteOptions = options; diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts index 21a8f9f956828..2adf2d7650ace 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts @@ -7,7 +7,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { InteractiveEditorController, Recording } from 'vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController'; import { CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_HAS_ACTIVE_REQUEST, CTX_INTERACTIVE_EDITOR_HAS_PROVIDER, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_FIRST, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_LAST, CTX_INTERACTIVE_EDITOR_EMPTY, CTX_INTERACTIVE_EDITOR_OUTER_CURSOR_POSITION, CTX_INTERACTIVE_EDITOR_VISIBLE, MENU_INTERACTIVE_EDITOR_WIDGET, CTX_INTERACTIVE_EDITOR_LAST_EDIT_TYPE, MENU_INTERACTIVE_EDITOR_WIDGET_UNDO, MENU_INTERACTIVE_EDITOR_WIDGET_STATUS, CTX_INTERACTIVE_EDITOR_LAST_FEEDBACK, CTX_INTERACTIVE_EDITOR_INLNE_DIFF, CTX_INTERACTIVE_EDITOR_HAS_RESPONSE, CTX_INTERACTIVE_EDITOR_EDIT_MODE } from 'vs/workbench/contrib/interactiveEditor/common/interactiveEditor'; @@ -21,6 +21,7 @@ import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/commo import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor'; import { ILogService } from 'vs/platform/log/common/log'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; export class StartSessionAction extends EditorAction2 { @@ -62,6 +63,13 @@ abstract class AbstractInteractiveEditorAction extends EditorAction2 { } const ctrl = InteractiveEditorController.get(editor); if (!ctrl) { + for (const diffEditor of accessor.get(ICodeEditorService).listDiffEditors()) { + if (diffEditor.getOriginalEditor() === editor || diffEditor.getModifiedEditor() === editor) { + if (diffEditor instanceof EmbeddedDiffEditorWidget) { + this.runEditorCommand(accessor, diffEditor.getParentEditor(), ..._args); + } + } + } return; } this.runInteractiveEditorCommand(accessor, ctrl, editor, ..._args); diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts index 11d7c15351a7b..cafa4a5eedce7 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts @@ -45,6 +45,9 @@ export class InteractiveEditorDiffWidget extends ZoneWidget { diffCodeLens: false, scrollBeyondLastLine: false, stickyScroll: { enabled: false } + }, { + originalEditor: { contributions: [] }, + modifiedEditor: { contributions: [] } }, editor); this._disposables.add(this._diffEditor); diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts index 5036ca950192a..65d59d58b86a8 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts @@ -216,7 +216,7 @@ class InteractiveEditorWidget { this._historyStore.add(statusToolbar); // preview editors - this._previewDiffEditor = this._store.add(_instantiationService.createInstance(EmbeddedDiffEditorWidget, this._elements.previewDiff, _previewEditorEditorOptions, parentEditor)); + this._previewDiffEditor = this._store.add(_instantiationService.createInstance(EmbeddedDiffEditorWidget, this._elements.previewDiff, _previewEditorEditorOptions, { modifiedEditor: codeEditorWidgetOptions, originalEditor: codeEditorWidgetOptions }, parentEditor)); this._previewCreateTitle = this._store.add(_instantiationService.createInstance(ResourceLabel, this._elements.previewCreateTitle, { supportIcons: true })); this._previewCreateEditor = this._store.add(_instantiationService.createInstance(EmbeddedCodeEditorWidget, this._elements.previewCreate, _previewEditorEditorOptions, codeEditorWidgetOptions, parentEditor)); diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 56fdbf0a4accd..cd294eafb7d23 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -412,7 +412,7 @@ class DirtyDiffWidget extends PeekViewWidget { stickyScroll: { enabled: false } }; - this.diffEditor = this.instantiationService.createInstance(EmbeddedDiffEditorWidget, container, options, this.editor); + this.diffEditor = this.instantiationService.createInstance(EmbeddedDiffEditorWidget, container, options, {}, this.editor); this._disposables.add(this.diffEditor); } diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index d9d0e4c3f28a0..b9c103d96c989 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -1044,6 +1044,7 @@ class DiffContentProvider extends Disposable implements IPeekOutputRenderer { EmbeddedDiffEditorWidget, this.container, diffEditorOptions, + {}, this.editor, ) : this.instantiationService.createInstance( DiffEditorWidget,