From b4bccd9d464301eef4fbeffabb76b5878d785843 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 21 Apr 2023 10:16:32 +0200 Subject: [PATCH 1/4] show commands only when having reply, proper type for different edit modes --- .../browser/interactiveEditorActions.ts | 20 ++++++++++-------- .../browser/interactiveEditorController.ts | 21 ++++++++++++------- .../common/interactiveEditor.ts | 13 +++++++++--- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts index 9e02688cf7a10..b075a674d67dc 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts @@ -10,7 +10,7 @@ import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { InteractiveEditorController, InteractiveEditorRunOptions, 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'; +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, EditMode } from 'vs/workbench/contrib/interactiveEditor/common/interactiveEditor'; import { localize } from 'vs/nls'; import { IAction2Options } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -342,7 +342,8 @@ export class FeebackHelpfulCommand extends AbstractInteractiveEditorAction { toggled: CTX_INTERACTIVE_EDITOR_LAST_FEEDBACK.isEqualTo('helpful'), menu: { id: MENU_INTERACTIVE_EDITOR_WIDGET_STATUS, - group: '1_feedback', + when: CTX_INTERACTIVE_EDITOR_HAS_RESPONSE, + group: '2_feedback', order: 1 } }); @@ -363,7 +364,8 @@ export class FeebackUnhelpfulCommand extends AbstractInteractiveEditorAction { toggled: CTX_INTERACTIVE_EDITOR_LAST_FEEDBACK.isEqualTo('unhelpful'), menu: { id: MENU_INTERACTIVE_EDITOR_WIDGET_STATUS, - group: '1_feedback', + when: CTX_INTERACTIVE_EDITOR_HAS_RESPONSE, + group: '2_feedback', order: 2 } }); @@ -385,9 +387,9 @@ export class ToggleInlineDiff extends AbstractInteractiveEditorAction { toggled: CTX_INTERACTIVE_EDITOR_INLNE_DIFF, menu: { id: MENU_INTERACTIVE_EDITOR_WIDGET_STATUS, - when: CTX_INTERACTIVE_EDITOR_EDIT_MODE.isEqualTo('direct'), - group: '0_main', - order: 10 + when: CTX_INTERACTIVE_EDITOR_EDIT_MODE.isEqualTo(EditMode.Live), + group: '1_main', + order: 1 } }); } @@ -404,14 +406,14 @@ export class ApplyPreviewEdits extends AbstractInteractiveEditorAction { id: 'interactiveEditor.applyEdits', title: localize('applyEdits', 'Apply Changes'), icon: Codicon.check, - precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_VISIBLE), + precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_VISIBLE, CTX_INTERACTIVE_EDITOR_HAS_RESPONSE), 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_HAS_RESPONSE, group: '0_main', order: 0 } @@ -447,7 +449,7 @@ export class CancelSessionAction extends AbstractInteractiveEditorAction { }, menu: { id: MENU_INTERACTIVE_EDITOR_WIDGET_STATUS, - // when: CTX_INTERACTIVE_EDITOR_EDIT_MODE.isEqualTo('preview'), + when: CTX_INTERACTIVE_EDITOR_HAS_RESPONSE, group: '0_main', order: 1 } diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts index 29132705cb08a..feab8148e6f52 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts @@ -40,7 +40,7 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { InteractiveEditorDiffWidget } from 'vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget'; import { InteractiveEditorZoneWidget } from 'vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget'; -import { CTX_INTERACTIVE_EDITOR_HAS_ACTIVE_REQUEST, CTX_INTERACTIVE_EDITOR_HAS_RESPONSE, CTX_INTERACTIVE_EDITOR_INLNE_DIFF, CTX_INTERACTIVE_EDITOR_LAST_EDIT_TYPE as CTX_INTERACTIVE_EDITOR_LAST_EDIT_KIND, CTX_INTERACTIVE_EDITOR_LAST_FEEDBACK as CTX_INTERACTIVE_EDITOR_LAST_FEEDBACK_KIND, IInteractiveEditorBulkEditResponse, IInteractiveEditorEditResponse, IInteractiveEditorRequest, IInteractiveEditorResponse, IInteractiveEditorService, IInteractiveEditorSession, IInteractiveEditorSessionProvider, IInteractiveEditorSlashCommand, INTERACTIVE_EDITOR_ID, InteractiveEditorResponseFeedbackKind } from 'vs/workbench/contrib/interactiveEditor/common/interactiveEditor'; +import { CTX_INTERACTIVE_EDITOR_HAS_ACTIVE_REQUEST, CTX_INTERACTIVE_EDITOR_HAS_RESPONSE, CTX_INTERACTIVE_EDITOR_INLNE_DIFF, CTX_INTERACTIVE_EDITOR_LAST_EDIT_TYPE as CTX_INTERACTIVE_EDITOR_LAST_EDIT_KIND, CTX_INTERACTIVE_EDITOR_LAST_FEEDBACK as CTX_INTERACTIVE_EDITOR_LAST_FEEDBACK_KIND, IInteractiveEditorBulkEditResponse, IInteractiveEditorEditResponse, IInteractiveEditorRequest, IInteractiveEditorResponse, IInteractiveEditorService, IInteractiveEditorSession, IInteractiveEditorSessionProvider, IInteractiveEditorSlashCommand, INTERACTIVE_EDITOR_ID, EditMode, InteractiveEditorResponseFeedbackKind } from 'vs/workbench/contrib/interactiveEditor/common/interactiveEditor'; import { IInteractiveSessionWidgetService } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionWidget'; import { IInteractiveSessionService } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService'; @@ -223,7 +223,6 @@ class LastEditorState { ) { } } -type EditMode = 'live' | 'livePreview' | 'preview'; export interface InteractiveEditorRunOptions { initialRange?: IRange; @@ -345,7 +344,7 @@ export class InteractiveEditorController implements IEditorContribution { }; this._strategy = this._instaService.createInstance( - editMode === 'preview' ? PreviewStrategy : editMode === 'livePreview' ? LivePreviewStrategy : LiveStrategy, + EditModeStrategy.ctor(editMode), textModel, textModel.getAlternativeVersionId() ); @@ -380,7 +379,7 @@ export class InteractiveEditorController implements IEditorContribution { // CANCEL when input changes this._editor.onDidChangeModel(this.cancelSession, this, store); - if (editMode === 'live') { + if (editMode === EditMode.Live) { // REposition the zone widget whenever the block decoration changes let lastPost: Position | undefined; @@ -460,7 +459,7 @@ export class InteractiveEditorController implements IEditorContribution { this._historyOffset = -1; const inputPromise = this._zone.getInput(wholeRange.getEndPosition(), placeholder, value, this._ctsRequest.token); - if (textModel0Changes && editMode === 'livePreview') { + if (textModel0Changes && editMode === EditMode.LivePreview) { const diffPosition = diffZone.getEndPositionForChanges(wholeRange, textModel0Changes); if (diffPosition) { const newInputPosition = diffPosition.delta(0, 1); @@ -574,7 +573,7 @@ export class InteractiveEditorController implements IEditorContribution { }]); } - if (editMode === 'preview') { + if (editMode === EditMode.Preview) { // only preview changes if (editResponse.localEdits.length > 0) { this._zone.widget.showEditsPreview(textModel, editResponse.localEdits); @@ -619,7 +618,7 @@ export class InteractiveEditorController implements IEditorContribution { ignoreModelChanges = false; } - if (editMode === 'live') { + if (editMode === EditMode.Live) { inlineDiffDecorations.update(); } @@ -767,6 +766,14 @@ export class InteractiveEditorController implements IEditorContribution { abstract class EditModeStrategy { + static ctor(mode: EditMode) { + switch (mode) { + case EditMode.Live: return LiveStrategy; + case EditMode.LivePreview: return LivePreviewStrategy; + case EditMode.Preview: return PreviewStrategy; + } + } + abstract update(response: EditResponse): boolean; abstract apply(): Promise; diff --git a/src/vs/workbench/contrib/interactiveEditor/common/interactiveEditor.ts b/src/vs/workbench/contrib/interactiveEditor/common/interactiveEditor.ts index ea8bddf30bd75..2c88a46debf81 100644 --- a/src/vs/workbench/contrib/interactiveEditor/common/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactiveEditor/common/interactiveEditor.ts @@ -94,6 +94,7 @@ export interface IInteractiveEditorService { export const INTERACTIVE_EDITOR_ID = 'interactiveEditor'; + export const CTX_INTERACTIVE_EDITOR_HAS_PROVIDER = new RawContextKey('interactiveEditorHasProvider', false, localize('interactiveEditorHasProvider', "Whether a provider for interactive editors exists")); export const CTX_INTERACTIVE_EDITOR_VISIBLE = new RawContextKey('interactiveEditorVisible', false, localize('interactiveEditorVisible', "Whether the interactive editor input is visible")); export const CTX_INTERACTIVE_EDITOR_FOCUSED = new RawContextKey('interactiveEditorFocused', false, localize('interactiveEditorFocused', "Whether the interactive editor input is focused")); @@ -106,7 +107,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_INTERACTIVE_EDITOR_EDIT_MODE = new RawContextKey<'live' | 'livePreview' | 'preview'>('config.interactiveEditor.editMode', 'live'); +export const CTX_INTERACTIVE_EDITOR_EDIT_MODE = new RawContextKey('config.interactiveEditor.editMode', EditMode.Live); // --- menus @@ -137,14 +138,20 @@ export const interactiveEditorDiffRemoved = registerColor('interactiveEditorDiff // settings +export const enum EditMode { + Live = 'live', + LivePreview = 'livePreview', + Preview = 'preview' +} + Registry.as(Extensions.Configuration).registerConfiguration({ id: 'editor', properties: { 'interactiveEditor.editMode': { description: localize('editMode', "Configure if changes crafted in the interactive editor are applied directly or previewed first"), - default: 'live', + default: EditMode.Live, type: 'string', - enum: ['live', 'livePreview', 'preview'] + enum: [EditMode.Live, EditMode.LivePreview, EditMode.Preview] } } }); From 6bf3a52a13195c718f2d260a28a0fa686e9ba699 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 21 Apr 2023 11:09:34 +0200 Subject: [PATCH 2/4] [debt] extract Session object which holds useful state for commands --- .../browser/interactiveEditorActions.ts | 4 +- .../browser/interactiveEditorController.ts | 166 +++++++++--------- 2 files changed, 88 insertions(+), 82 deletions(-) diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts index b075a674d67dc..93e08dc7d9b22 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts @@ -456,8 +456,8 @@ export class CancelSessionAction extends AbstractInteractiveEditorAction { }); } - runInteractiveEditorCommand(_accessor: ServicesAccessor, ctrl: InteractiveEditorController, _editor: ICodeEditor, ..._args: any[]): void { - ctrl.cancelSession(); + async runInteractiveEditorCommand(_accessor: ServicesAccessor, ctrl: InteractiveEditorController, _editor: ICodeEditor, ..._args: any[]): Promise { + await ctrl.cancelSession(); } } diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts index feab8148e6f52..0e55e16ef917c 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorController.ts @@ -212,17 +212,40 @@ export class EditResponse { } } -class LastEditorState { +class Session { + + private readonly _responses: EditResponse[] = []; + + readonly teldata: TelemetryData; constructor( - readonly model: ITextModel, - readonly modelVersionId: number, + readonly editMode: EditMode, + readonly model0: ITextModel, + readonly modelN: ITextModel, readonly provider: IInteractiveEditorSessionProvider, readonly session: IInteractiveEditorSession, - readonly response: EditResponse, - ) { } -} + ) { + this.teldata = { + extension: provider.debugName, + startTime: new Date().toISOString(), + endTime: new Date().toISOString(), + edits: false, + terminalEdits: false, + rounds: '', + undos: '', + editMode + }; + } + + addResponse(response: EditResponse): void { + const newLen = this._responses.push(response); + this.teldata.rounds += `${newLen}|`; + } + get lastResponse(): EditResponse | undefined { + return this._responses[this._responses.length - 1]; + } +} export interface InteractiveEditorRunOptions { initialRange?: IRange; @@ -262,11 +285,12 @@ export class InteractiveEditorController implements IEditorContribution { private readonly _ctxLastEditKind: IContextKey<'' | 'simple'>; private readonly _ctxLastFeedbackKind: IContextKey<'helpful' | 'unhelpful' | ''>; - private _lastEditState?: LastEditorState; private _strategy?: EditModeStrategy; private _lastInlineDecorations?: InlineDiffDecorations; private _inlineDiffEnabled: boolean = false; + private _currentSession?: Session; + private _ctsSession: CancellationTokenSource = new CancellationTokenSource(); private _ctsRequest?: CancellationTokenSource; @@ -332,22 +356,20 @@ export class InteractiveEditorController implements IEditorContribution { this._recorder.add(session, textModel); this._logService.trace('[IE] NEW session', provider.debugName); - const data: TelemetryData = { - extension: provider.debugName, - startTime: new Date().toISOString(), - endTime: new Date().toISOString(), - edits: false, - terminalEdits: false, - rounds: '', - undos: '', - editMode - }; + const store = new DisposableStore(); + - this._strategy = this._instaService.createInstance( - EditModeStrategy.ctor(editMode), - textModel, textModel.getAlternativeVersionId() + let textModel0Changes: LineRangeMapping[] | undefined; + const textModel0 = this._modelService.createModel( + createTextBufferFactoryFromSnapshot(textModel.createSnapshot()), + { languageId: textModel.getLanguageId(), onDidChange: Event.None }, + undefined, true ); + store.add(textModel0); + this._currentSession = new Session(editMode, textModel0, textModel, provider, session); + this._strategy = this._instaService.createInstance(EditModeStrategy.ctor(editMode), this._currentSession); + this._inlineDiffEnabled = this._storageService.getBoolean(InteractiveEditorController._inlineDiffStorageKey, StorageScope.PROFILE, false); const inlineDiffDecorations = new InlineDiffDecorations(this._editor, this._inlineDiffEnabled); this._lastInlineDecorations = inlineDiffDecorations; @@ -368,7 +390,6 @@ export class InteractiveEditorController implements IEditorContribution { let placeholder = session.placeholder ?? ''; let value = options?.message ?? ''; - const store = new DisposableStore(); if (session.slashCommands) { store.add(this._instaService.invokeFunction(installSlashCommandSupport, this._zone.widget.inputEditor as IActiveCodeEditor, session.slashCommands)); @@ -400,7 +421,7 @@ export class InteractiveEditorController implements IEditorContribution { inlineDiffDecorations.clear(); // note when "other" edits happen - data.edits = true; + this._currentSession!.teldata.edits = true; // CANCEL if the document has changed outside the current range const wholeRange = wholeRangeDecoration.getRange(0); @@ -413,7 +434,7 @@ export class InteractiveEditorController implements IEditorContribution { if (!Range.areIntersectingOrTouching(wholeRange, change.range)) { this._ctsSession.cancel(); this._logService.trace('[IE] CANCEL because of model change OUTSIDE range'); - data.terminalEdits = true; + this._currentSession!.teldata.terminalEdits = true; break; } } @@ -421,24 +442,13 @@ export class InteractiveEditorController implements IEditorContribution { }, undefined, store); - let round = 0; const roundStore = new DisposableStore(); store.add(roundStore); - let textModel0Changes: LineRangeMapping[] | undefined; - const textModel0 = this._modelService.createModel( - createTextBufferFactoryFromSnapshot(textModel.createSnapshot()), - { languageId: textModel.getLanguageId(), onDidChange: Event.None }, - undefined, true - ); - - store.add(textModel0); const diffZone = this._instaService.createInstance(InteractiveEditorDiffWidget, this._editor, textModel0); do { - round += 1; - const wholeRange = wholeRangeDecoration.getRange(0); if (!wholeRange) { // nuked whole file contents? @@ -475,7 +485,7 @@ export class InteractiveEditorController implements IEditorContribution { this._ctxLastFeedbackKind.reset(); // reveal the line after the whole range to ensure that the input box is visible this._editor.revealPosition({ lineNumber: wholeRange.endLineNumber + 1, column: 1 }, ScrollType.Smooth); - if (options?.autoSend && round === 1) { + if (options?.autoSend && !this._currentSession.lastResponse) { this.accept(); } const input = await inputPromise; @@ -552,6 +562,7 @@ export class InteractiveEditorController implements IEditorContribution { } const editResponse = new EditResponse(textModel.uri, reply); + this._currentSession.addResponse(editResponse); const canContinue = this._strategy.update(editResponse); if (!canContinue) { @@ -563,7 +574,6 @@ export class InteractiveEditorController implements IEditorContribution { // inline diff inlineDiffDecorations.clear(); - this._lastEditState = new LastEditorState(textModel, textModel.getAlternativeVersionId(), provider, session, editResponse); // use whole range from reply if (reply.wholeRange) { @@ -649,17 +659,21 @@ export class InteractiveEditorController implements IEditorContribution { placeholder = reply.placeholder ?? session.placeholder ?? ''; - data.rounds += round + '|'; - } while (!thisSession.token.isCancellationRequested); this._inlineDiffEnabled = inlineDiffDecorations.visible; this._storageService.store(InteractiveEditorController._inlineDiffStorageKey, this._inlineDiffEnabled, StorageScope.PROFILE, StorageTarget.USER); + + this._logService.trace('[IE] session DONE', provider.debugName); + this._currentSession.teldata.endTime = new Date().toISOString(); + this._telemetryService.publicLog2('interactiveEditor/session', this._currentSession.teldata); + + // done, cleanup + diffZone.hide(); diffZone.dispose(); - // done, cleanup wholeRangeDecoration.clear(); blockDecoration.clear(); inlineDiffDecorations.clear(); @@ -670,16 +684,11 @@ export class InteractiveEditorController implements IEditorContribution { this._ctxLastEditKind.reset(); this._ctxHasResponse.reset(); this._ctxLastFeedbackKind.reset(); - this._lastEditState = undefined; + this._currentSession = undefined; this._lastInlineDecorations = undefined; this._zone.hide(); this._editor.focus(); - - this._logService.trace('[IE] session DONE', provider.debugName); - data.endTime = new Date().toISOString(); - - this._telemetryService.publicLog2('interactiveEditor/session', data); } accept(): void { @@ -729,38 +738,33 @@ export class InteractiveEditorController implements IEditorContribution { } undoLast(): string | void { - if (this._lastEditState) { - const { model, modelVersionId } = this._lastEditState; - while (model.getAlternativeVersionId() !== modelVersionId) { - model.undo(); - } - this._lastEditState.provider.handleInteractiveEditorResponseFeedback?.(this._lastEditState.session, this._lastEditState.response.raw, InteractiveEditorResponseFeedbackKind.Undone); - return this._lastEditState.response.localEdits[0].text; + if (this._currentSession?.lastResponse) { + this._currentSession.modelN.undo(); + return this._currentSession.lastResponse.localEdits[0].text; } } feedbackLast(helpful: boolean) { - if (this._lastEditState) { + if (this._currentSession?.lastResponse) { const kind = helpful ? InteractiveEditorResponseFeedbackKind.Helpful : InteractiveEditorResponseFeedbackKind.Unhelpful; - this._lastEditState.provider.handleInteractiveEditorResponseFeedback?.(this._lastEditState.session, this._lastEditState.response.raw, kind); + this._currentSession.provider.handleInteractiveEditorResponseFeedback?.(this._currentSession.session, this._currentSession.lastResponse.raw, kind); this._ctxLastFeedbackKind.set(helpful ? 'helpful' : 'unhelpful'); this._zone.widget.updateMessage('Thank you for your feedback!', { resetAfter: 1250 }); } } - async applyChanges() { - if (!this._lastEditState) { - return undefined; + async applyChanges(): Promise { + if (this._currentSession?.lastResponse) { + const { lastResponse } = this._currentSession; + await this._strategy?.apply(); + this._ctsSession.cancel(); + return lastResponse; } - const { response } = this._lastEditState; - await this._strategy?.apply(); - this._ctsSession.cancel(); - return response; } - cancelSession() { + async cancelSession() { + await this._strategy?.cancel(); this._ctsSession.cancel(); - this._strategy?.cancel(); } } @@ -778,23 +782,19 @@ abstract class EditModeStrategy { abstract apply(): Promise; - abstract cancel(): void; + abstract cancel(): Promise; } class PreviewStrategy extends EditModeStrategy { - private _lastResponse?: EditResponse; - constructor( - private readonly _model: ITextModel, - private readonly _versionId: number, + protected readonly _session: Session, @IBulkEditService private readonly _bulkEditService: IBulkEditService, ) { super(); } update(response: EditResponse): boolean { - this._lastResponse = response; if (!response.workspaceEdits || response.singleCreateFileEdit) { // preview stategy can handle simple workspace edit (single file create) return true; @@ -805,7 +805,7 @@ class PreviewStrategy extends EditModeStrategy { async apply() { - const response = this._lastResponse; + const response = this._session.lastResponse; if (!response) { return; } @@ -814,16 +814,19 @@ class PreviewStrategy extends EditModeStrategy { await this._bulkEditService.apply(response.workspaceEdits); } else if (!response.workspaceEditsIncludeLocalEdits) { - if (this._model.getAlternativeVersionId() === this._versionId) { - this._model.pushStackElement(); + + const { modelN } = this._session; + + if (modelN.equalsTextBuffer(this._session.model0.getTextBuffer())) { + modelN.pushStackElement(); const edits = response.localEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)); - this._model.pushEditOperations(null, edits, () => null); - this._model.pushStackElement(); + modelN.pushEditOperations(null, edits, () => null); + modelN.pushStackElement(); } } } - cancel(): void { + async cancel(): Promise { // nothing to do } } @@ -831,9 +834,9 @@ class PreviewStrategy extends EditModeStrategy { class LiveStrategy extends EditModeStrategy { constructor( - protected readonly _model: ITextModel, - protected readonly _versionId: number, + protected readonly _session: Session, @IBulkEditService protected readonly _bulkEditService: IBulkEditService, + @IEditorWorkerService protected readonly _editorWorkerService: IEditorWorkerService ) { super(); } @@ -850,9 +853,12 @@ class LiveStrategy extends EditModeStrategy { // nothing to do } - cancel(): void { - while (this._model.getAlternativeVersionId() !== this._versionId && this._model.canUndo()) { - this._model.undo(); + async cancel() { + const { modelN, model0 } = this._session; + const edits = await this._editorWorkerService.computeMoreMinimalEdits(modelN.uri, [{ range: modelN.getFullModelRange(), text: model0.getValue() }]); + if (edits) { + const operations = edits.map(e => EditOperation.replace(Range.lift(e.range), e.text)); + modelN.pushEditOperations(null, operations, () => null); } } } From f7eb567525dff9d3363b52ed4d6b0e02c1cd87c3 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 21 Apr 2023 11:34:52 +0200 Subject: [PATCH 3/4] allow for default sash ratio in diff editor (side-by-side) and use it for inline diff'ing --- .../editor/browser/widget/diffEditorWidget.ts | 27 +++++++++++-------- src/vs/editor/common/config/editorOptions.ts | 16 +++++++++++ src/vs/monaco.d.ts | 5 ++++ .../browser/interactiveEditorDiffWidget.ts | 3 ++- 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 9efe95bcb584f..24cf5b188b03f 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -29,7 +29,7 @@ import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/wi import { DiffReview } from 'vs/editor/browser/widget/diffReview'; import { IDiffLinesChange, InlineDiffMargin } from 'vs/editor/browser/widget/inlineDiffMargin'; import { WorkerBasedDocumentDiffProvider } from 'vs/editor/browser/widget/workerBasedDocumentDiffProvider'; -import { boolean as validateBooleanOption, clampedInt, EditorFontLigatures, EditorLayoutInfo, EditorOption, EditorOptions, IDiffEditorOptions, stringSet as validateStringSetOption, ValidDiffEditorBaseOptions } from 'vs/editor/common/config/editorOptions'; +import { boolean as validateBooleanOption, clampedInt, EditorFontLigatures, EditorLayoutInfo, EditorOption, EditorOptions, IDiffEditorOptions, stringSet as validateStringSetOption, ValidDiffEditorBaseOptions, clampedFloat } from 'vs/editor/common/config/editorOptions'; import { FontInfo } from 'vs/editor/common/config/fontInfo'; import { IDimension } from 'vs/editor/common/core/dimension'; import { IPosition, Position } from 'vs/editor/common/core/position'; @@ -273,6 +273,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._options = validateDiffEditorOptions(options, { enableSplitViewResizing: true, + splitViewDefaultRatio: 0.5, renderSideBySide: true, renderMarginRevertIcon: true, maxComputationTime: 5000, @@ -364,7 +365,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._containerDomElement.appendChild(this._reviewPane.actionBarContainer.domNode); if (this._options.renderSideBySide) { - this._setStrategy(new DiffEditorWidgetSideBySide(this._createDataSource(), this._options.enableSplitViewResizing)); + this._setStrategy(new DiffEditorWidgetSideBySide(this._createDataSource(), this._options.enableSplitViewResizing, this._options.splitViewDefaultRatio)); } else { this._setStrategy(new DiffEditorWidgetInline(this._createDataSource(), this._options.enableSplitViewResizing)); } @@ -788,12 +789,12 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._originalEditor.updateOptions(this._adjustOptionsForLeftHandSide(_newOptions)); // enableSplitViewResizing - this._strategy.setEnableSplitViewResizing(this._options.enableSplitViewResizing); + this._strategy.setEnableSplitViewResizing(this._options.enableSplitViewResizing, this._options.splitViewDefaultRatio); // renderSideBySide if (changed.renderSideBySide) { if (this._options.renderSideBySide) { - this._setStrategy(new DiffEditorWidgetSideBySide(this._createDataSource(), this._options.enableSplitViewResizing)); + this._setStrategy(new DiffEditorWidgetSideBySide(this._createDataSource(), this._options.enableSplitViewResizing, this._options.splitViewDefaultRatio)); } else { this._setStrategy(new DiffEditorWidgetInline(this._createDataSource(), this._options.enableSplitViewResizing)); } @@ -1584,7 +1585,7 @@ abstract class DiffEditorWidgetStyle extends Disposable { protected abstract _getOriginalEditorDecorations(zones: IEditorsZones, lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean): IEditorDiffDecorations; protected abstract _getModifiedEditorDecorations(zones: IEditorsZones, lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, renderMarginRevertIcon: boolean): IEditorDiffDecorations; - public abstract setEnableSplitViewResizing(enableSplitViewResizing: boolean): void; + public abstract setEnableSplitViewResizing(enableSplitViewResizing: boolean, defaultRatio: number): void; public abstract layout(): number; setBoundarySashes(_sashes: IBoundarySashes): void { @@ -1974,14 +1975,16 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IVerti private _disableSash: boolean; private readonly _sash: Sash; + private _defaultRatio: number; private _sashRatio: number | null; private _sashPosition: number | null; private _startSashPosition: number | null; - constructor(dataSource: IDataSource, enableSplitViewResizing: boolean) { + constructor(dataSource: IDataSource, enableSplitViewResizing: boolean, defaultSashRatio: number) { super(dataSource); this._disableSash = (enableSplitViewResizing === false); + this._defaultRatio = defaultSashRatio; this._sashRatio = null; this._sashPosition = null; this._startSashPosition = null; @@ -1997,7 +2000,8 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IVerti this._sash.onDidReset(() => this._onSashReset()); } - public setEnableSplitViewResizing(enableSplitViewResizing: boolean): void { + public setEnableSplitViewResizing(enableSplitViewResizing: boolean, defaultRatio: number): void { + this._defaultRatio = defaultRatio; const newDisableSash = (enableSplitViewResizing === false); if (this._disableSash !== newDisableSash) { this._disableSash = newDisableSash; @@ -2005,12 +2009,12 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IVerti } } - public layout(sashRatio: number | null = this._sashRatio): number { + public layout(sashRatio: number | null = this._sashRatio || this._defaultRatio): number { const w = this._dataSource.getWidth(); const contentWidth = w - (this._dataSource.getOptions().renderOverviewRuler ? DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH : 0); - let sashPosition = Math.floor((sashRatio || 0.5) * contentWidth); - const midPoint = Math.floor(0.5 * contentWidth); + let sashPosition = Math.floor((sashRatio || this._defaultRatio) * contentWidth); + const midPoint = Math.floor(this._defaultRatio * contentWidth); sashPosition = this._disableSash ? midPoint : sashPosition || midPoint; @@ -2053,7 +2057,7 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IVerti } private _onSashReset(): void { - this._sashRatio = 0.5; + this._sashRatio = this._defaultRatio; this._dataSource.relayoutEditors(); this._sash.layout(); } @@ -2749,6 +2753,7 @@ function getViewRange(model: ITextModel, viewModel: IViewModel, startLineNumber: function validateDiffEditorOptions(options: Readonly, defaults: ValidDiffEditorBaseOptions): ValidDiffEditorBaseOptions { return { enableSplitViewResizing: validateBooleanOption(options.enableSplitViewResizing, defaults.enableSplitViewResizing), + splitViewDefaultRatio: clampedFloat(options.splitViewDefaultRatio, 0.5, 0.1, 0.9), renderSideBySide: validateBooleanOption(options.renderSideBySide, defaults.renderSideBySide), renderMarginRevertIcon: validateBooleanOption(options.renderMarginRevertIcon, defaults.renderMarginRevertIcon), maxComputationTime: clampedInt(options.maxComputationTime, defaults.maxComputationTime, 0, Constants.MAX_SAFE_SMALL_INTEGER), diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 52f6dcd515324..7ed880cdcda84 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -725,6 +725,12 @@ export interface IDiffEditorBaseOptions { * Defaults to true. */ enableSplitViewResizing?: boolean; + /** + * The default ratio when rendering side-by-side editors. + * Must be a number between 0 and 1, min sizes apply. + * Defaults to 0.5 + */ + splitViewDefaultRatio?: number; /** * Render the differences in two side-by-side editors. * Defaults to true. @@ -1068,6 +1074,16 @@ class EditorIntOption extends SimpleEditorOption(value: any, defaultValue: T, minimum: number, maximum: number): number | T { + if (typeof value === 'undefined') { + return defaultValue; + } + const r = EditorFloatOption.float(value, defaultValue); + return EditorFloatOption.clamp(r, minimum, maximum); +} class EditorFloatOption extends SimpleEditorOption { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index fa0ca5ee7b4d9..e087556ef3867 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3777,6 +3777,11 @@ declare namespace monaco.editor { * Defaults to true. */ enableSplitViewResizing?: boolean; + /** + * The default ratio when rendering side-by-side editors. + * Must be a number between 0.1 and 0.9, defaults to 0.5 + */ + splitViewDefaultRatio?: number; /** * Render the differences in two side-by-side editors. * Defaults to true. diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts index 597e03c6ffc08..ca1a3b7602acb 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorDiffWidget.ts @@ -55,7 +55,8 @@ export class InteractiveEditorDiffWidget extends ZoneWidget { scrollBeyondLastLine: false, stickyScroll: { enabled: false }, renderOverviewRuler: false, - diffAlgorithm: 'advanced' + diffAlgorithm: 'advanced', + splitViewDefaultRatio: 0.35 }, { originalEditor: { contributions: diffContributions }, modifiedEditor: { contributions: diffContributions } From 5490a498d427c29b34440351088c25a45e53de43 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 21 Apr 2023 14:06:36 +0200 Subject: [PATCH 4/4] add missing monaco.d.ts file --- src/vs/monaco.d.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index e087556ef3867..705065c18005e 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3779,7 +3779,8 @@ declare namespace monaco.editor { enableSplitViewResizing?: boolean; /** * The default ratio when rendering side-by-side editors. - * Must be a number between 0.1 and 0.9, defaults to 0.5 + * Must be a number between 0 and 1, min sizes apply. + * Defaults to 0.5 */ splitViewDefaultRatio?: number; /**