From f966c6ff8b7faa42dee09f850bbf3c2b767fc4ca Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 3 Mar 2023 17:39:09 +0100 Subject: [PATCH] joh/used ermine (#176067) * more logging, more undo, better wholeRange mangement * improve/fix position of zone widget, better undo logic * make getDomNode a getter * add debug command --- .../browser/interactiveEditor.contribution.ts | 1 + .../interactive/browser/interactiveEditor.css | 4 + .../browser/interactiveEditorActions.ts | 45 ++- .../browser/interactiveEditorWidget.ts | 335 ++++++++++-------- .../interactive/common/interactiveEditor.ts | 2 - 5 files changed, 238 insertions(+), 149 deletions(-) diff --git a/src/vs/editor/contrib/interactive/browser/interactiveEditor.contribution.ts b/src/vs/editor/contrib/interactive/browser/interactiveEditor.contribution.ts index 3e2347bbfb1..52484ac2920 100644 --- a/src/vs/editor/contrib/interactive/browser/interactiveEditor.contribution.ts +++ b/src/vs/editor/contrib/interactive/browser/interactiveEditor.contribution.ts @@ -28,3 +28,4 @@ registerAction2(interactiveEditorActions.FocusInteractiveEditor); registerAction2(interactiveEditorActions.PreviousFromHistory); registerAction2(interactiveEditorActions.NextFromHistory); registerAction2(interactiveEditorActions.UndoCommand); +registerAction2(interactiveEditorActions.CopyRecordings); diff --git a/src/vs/editor/contrib/interactive/browser/interactiveEditor.css b/src/vs/editor/contrib/interactive/browser/interactiveEditor.css index aa702a4c48f..a0c6204bdcf 100644 --- a/src/vs/editor/contrib/interactive/browser/interactiveEditor.css +++ b/src/vs/editor/contrib/interactive/browser/interactiveEditor.css @@ -37,6 +37,10 @@ padding: 2px } +.monaco-editor .interactive-editor .body .history .history-entry.hidden { + display: none; +} + .monaco-editor .interactive-editor .body .history .history-entry:hover { background-color: var(--vscode-list-hoverBackground); color: var(--vscode-list-hoverForeground); diff --git a/src/vs/editor/contrib/interactive/browser/interactiveEditorActions.ts b/src/vs/editor/contrib/interactive/browser/interactiveEditorActions.ts index 0ec58ab2455..d4292b08de7 100644 --- a/src/vs/editor/contrib/interactive/browser/interactiveEditorActions.ts +++ b/src/vs/editor/contrib/interactive/browser/interactiveEditorActions.ts @@ -9,13 +9,15 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { InteractiveEditorController } from 'vs/editor/contrib/interactive/browser/interactiveEditorWidget'; +import { InteractiveEditorController, Recording } from 'vs/editor/contrib/interactive/browser/interactiveEditorWidget'; 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_PREVIEW, CTX_INTERACTIVE_EDITOR_VISIBLE, MENU_INTERACTIVE_EDITOR_WIDGET, MENU_INTERACTIVE_EDITOR_WIDGET_LHS, CTX_INTERACTIVE_EDITOR_HISTORY_VISIBLE } from 'vs/editor/contrib/interactive/common/interactiveEditor'; import { localize } from 'vs/nls'; import { IAction2Options } from 'vs/platform/actions/common/actions'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; export class StartSessionAction extends EditorAction2 { @@ -326,3 +328,44 @@ export class ToggleHistory extends AbstractInteractiveEditorAction { ctrl.toggleHistory(); } } + +export class CopyRecordings extends AbstractInteractiveEditorAction { + + constructor() { + super({ + id: 'interactiveEditor.copyRecordings', + f1: true, + title: { + value: localize('copyRecordings', '(Developer) Write Exchange to Clipboard'), original: '(Developer) Write Exchange to Clipboard' + } + }); + } + + override async runInteractiveEditorCommand(accessor: ServicesAccessor, ctrl: InteractiveEditorController, _editor: ICodeEditor, ..._args: any[]): Promise { + + const clipboardService = accessor.get(IClipboardService); + const quickPickService = accessor.get(IQuickInputService); + + const picks: (IQuickPickItem & { rec: Recording })[] = ctrl.recordings().map(rec => { + return { + rec, + label: localize('label', "{0} messages, started {1}", rec.exchanges.length, rec.when.toLocaleTimeString()), + tooltip: rec.exchanges.map(ex => ex.req.prompt).join('\n'), + }; + }); + + if (picks.length === 0) { + return; + } + + let pick: typeof picks[number] | undefined; + if (picks.length === 1) { + pick = picks[0]; + } else { + pick = await quickPickService.pick(picks, { canPickMany: false }); + } + if (pick) { + clipboardService.writeText(JSON.stringify(pick.rec, undefined, 2)); + } + } +} diff --git a/src/vs/editor/contrib/interactive/browser/interactiveEditorWidget.ts b/src/vs/editor/contrib/interactive/browser/interactiveEditorWidget.ts index 96845172e40..21883104132 100644 --- a/src/vs/editor/contrib/interactive/browser/interactiveEditorWidget.ts +++ b/src/vs/editor/contrib/interactive/browser/interactiveEditorWidget.ts @@ -8,17 +8,17 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance import { DisposableStore, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions'; -import { IRange, Range } from 'vs/editor/common/core/range'; -import { IEditorContribution, IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; +import { Range } from 'vs/editor/common/core/range'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; import { assertType } from 'vs/base/common/types'; -import { IInteractiveEditorResponse, IInteractiveEditorService, CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_HAS_ACTIVE_REQUEST, 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_PREVIEW, CTX_INTERACTIVE_EDITOR_VISIBLE, MENU_INTERACTIVE_EDITOR_WIDGET, CTX_INTERACTIVE_EDITOR_HISTORY_VISIBLE, MENU_INTERACTIVE_EDITOR_WIDGET_LHS } from 'vs/editor/contrib/interactive/common/interactiveEditor'; +import { IInteractiveEditorResponse, IInteractiveEditorService, CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_HAS_ACTIVE_REQUEST, 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_PREVIEW, CTX_INTERACTIVE_EDITOR_VISIBLE, MENU_INTERACTIVE_EDITOR_WIDGET, CTX_INTERACTIVE_EDITOR_HISTORY_VISIBLE, MENU_INTERACTIVE_EDITOR_WIDGET_LHS, IInteractiveEditorRequest, IInteractiveEditorSession } from 'vs/editor/contrib/interactive/common/interactiveEditor'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Iterable } from 'vs/base/common/iterator'; -import { IModelDeltaDecoration, ITextModel, IValidEditOperation } from 'vs/editor/common/model'; +import { ICursorStateComputer, IModelDeltaDecoration, ITextModel, IValidEditOperation } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { Dimension, addDisposableListener, getTotalHeight, getTotalWidth, h, reset } from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; @@ -33,7 +33,7 @@ import { GhostTextController } from 'vs/editor/contrib/inlineCompletions/browser import { MenuWorkbenchToolBar, WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; -import { Position } from 'vs/editor/common/core/position'; +import { IPosition, Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; import { raceCancellationError } from 'vs/base/common/async'; import { isCancellationError } from 'vs/base/common/errors'; @@ -44,8 +44,14 @@ import { StopWatch } from 'vs/base/common/stopwatch'; import { Action, IAction } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; +import { LRUCache } from 'vs/base/common/map'; +interface IHistoryEntry { + updateVisibility(visible: boolean): void; + updateActions(actions: IAction[]): void; +} + class InteractiveEditorWidget { private static _noop = () => { }; @@ -201,7 +207,7 @@ class InteractiveEditorWidget { this._ctxHistoryVisible.reset(); } - getDomNode(): HTMLElement { + get domNode(): HTMLElement { return this._elements.root; } @@ -237,7 +243,9 @@ class InteractiveEditorWidget { this._elements.placeholder.innerText = placeholder; this._elements.placeholder.style.fontSize = `${this._inputEditor.getOption(EditorOption.fontSize)}px`; this._elements.placeholder.style.lineHeight = `${this._inputEditor.getOption(EditorOption.lineHeight)}px`; + this._inputModel.setValue(value); + this._inputEditor.setSelection(this._inputModel.getFullModelRange()); const disposeOnDone = new DisposableStore(); @@ -318,7 +326,7 @@ class InteractiveEditorWidget { this._onDidChangeHeight.fire(); } - createHistoryEntry(value: string) { + createHistoryEntry(value: string): IHistoryEntry { const { root, label, actions } = h('div.history-entry@item', [ h('div.label@label'), @@ -336,8 +344,8 @@ class InteractiveEditorWidget { } return { - dispose: () => { - root.remove(); + updateVisibility: (visible) => { + root.classList.toggle('hidden', !visible); if (this._isExpanded) { this._onDidChangeHeight.fire(); } @@ -412,7 +420,7 @@ export class InteractiveEditorZoneWidget extends ZoneWidget { } protected override _fillContainer(container: HTMLElement): void { - container.appendChild(this.widget.getDomNode()); + container.appendChild(this.widget.domNode); } protected override _getWidth(info: EditorLayoutInfo): number { @@ -438,8 +446,8 @@ export class InteractiveEditorZoneWidget extends ZoneWidget { const width = widthInPixel - (spaceLeft + spaceRight); this._dimension = new Dimension(width, heightInPixel); - this.widget.getDomNode().style.marginLeft = `${spaceLeft}px`; - this.widget.getDomNode().style.marginRight = `${spaceRight}px`; + this.widget.domNode.style.marginLeft = `${spaceLeft}px`; + this.widget.domNode.style.marginRight = `${spaceRight}px`; this.widget.layout(this._dimension); } @@ -453,7 +461,7 @@ export class InteractiveEditorZoneWidget extends ZoneWidget { super._relayout(this._computeHeightInLines()); } - async getInput(where: IRange, placeholder: string, value: string, token: CancellationToken): Promise<{ value: string; preview: boolean } | undefined> { + async getInput(where: IPosition, placeholder: string, value: string, token: CancellationToken): Promise<{ value: string; preview: boolean } | undefined> { assertType(this.editor.hasModel()); super.show(where, this._computeHeightInLines()); this._ctxVisible.set(true); @@ -463,13 +471,62 @@ export class InteractiveEditorZoneWidget extends ZoneWidget { return result; } + updatePosition(where: IPosition) { + super.show(where, this._computeHeightInLines()); + } + override hide(): void { this._ctxVisible.reset(); this._ctxCursorPosition.reset(); this.widget.reset(); super.hide(); } +} +class UndoStepAction extends Action { + + static all: UndoStepAction[] = []; + + static updateUndoSteps() { + UndoStepAction.all.forEach(action => { + const isMyAltId = action.myAlternativeVersionId === action.model.getAlternativeVersionId(); + action.enabled = isMyAltId; + }); + } + + readonly myAlternativeVersionId: number; + + constructor(readonly model: ITextModel) { + super(`undo@${model.getAlternativeVersionId()}`, localize('undoStep', "Undo This Step"), ThemeIcon.asClassName(Codicon.discard), false); + this.myAlternativeVersionId = model.getAlternativeVersionId(); + UndoStepAction.all.push(this); + UndoStepAction.updateUndoSteps(); + } + + override async run() { + this.model.undo(); + UndoStepAction.updateUndoSteps(); + } +} + +type Exchange = { req: IInteractiveEditorRequest; res: IInteractiveEditorResponse }; +export type Recording = { when: Date; session: IInteractiveEditorSession; value: string; exchanges: Exchange[] }; + +class SessionRecorder { + + private readonly _data = new LRUCache(3); + + add(session: IInteractiveEditorSession, model: ITextModel) { + this._data.set(session, { when: new Date(), session, value: model.getValue(), exchanges: [] }); + } + + addExchange(session: IInteractiveEditorSession, req: IInteractiveEditorRequest, res: IInteractiveEditorResponse) { + this._data.get(session)?.exchanges.push({ req, res }); + } + + getAll(): Recording[] { + return [...this._data.values()]; + } } export class InteractiveEditorController implements IEditorContribution { @@ -487,14 +544,11 @@ export class InteractiveEditorController implements IEditorContribution { blockPadding: [1, 0, 1, 4] }); - private static _decoEdits = ModelDecorationOptions.register({ - description: 'interactive-editor-edits' - }); - private static _promptHistory: string[] = []; private _historyOffset: number = -1; private readonly _store = new DisposableStore(); + private readonly _recorder = new SessionRecorder(); private readonly _zone: InteractiveEditorZoneWidget; private readonly _ctxShowPreview: IContextKey; private readonly _ctxHasActiveRequest: IContextKey; @@ -539,131 +593,110 @@ export class InteractiveEditorController implements IEditorContribution { return; } - this._ctsSession = new CancellationTokenSource(); - - const session = await provider.prepareInteractiveEditorSession(this._editor.getModel(), this._editor.getSelection(), this._ctsSession.token); + const textModel = this._editor.getModel(); + const session = await provider.prepareInteractiveEditorSession(textModel, this._editor.getSelection(), this._ctsSession.token); if (!session) { this._logService.trace('[IE] NO session', provider.debugName); return; } - + this._recorder.add(session, textModel); this._logService.trace('[IE] NEW session', provider.debugName); - const decoBorder = this._editor.createDecorationsCollection(); - const decoInlineDiff = this._editor.createDecorationsCollection(); + const blockDecoration = this._editor.createDecorationsCollection(); + const inlineDiffDecorations = this._editor.createDecorationsCollection(); - const decoEdits: [deco: IEditorDecorationsCollection, altId: number][] = []; - - const decoWholeRange = this._editor.createDecorationsCollection(); - decoWholeRange.set([{ - range: this._editor.getSelection(), + const wholeRangeDecoration = this._editor.createDecorationsCollection(); + let initialRange: Range = this._editor.getSelection(); + if (initialRange.isEmpty()) { + initialRange = new Range( + initialRange.startLineNumber, 1, + initialRange.startLineNumber, textModel.getLineMaxColumn(initialRange.startLineNumber) + ); + } + wholeRangeDecoration.set([{ + range: initialRange, options: { description: 'interactive-editor-marker' } }]); + let placeholder = session.placeholder ?? ''; let value = ''; const listener = new DisposableStore(); + + // CANCEL when input changes this._editor.onDidChangeModel(this._ctsSession.cancel, this._ctsSession, listener); - class UndoStepAction extends Action { - - static all: UndoStepAction[] = []; - - static updateUndoSteps() { - UndoStepAction.all.forEach(action => { - const isMyAltId = action.myAlternativeVersionId === action.model.getAlternativeVersionId(); - action.enabled = isMyAltId; - }); + // REposition the zone widget whenever the block decoration changes + let lastPost: Position | undefined; + wholeRangeDecoration.onDidChange(e => { + const range = wholeRangeDecoration.getRange(0); + if (range && (!lastPost || !lastPost.equals(range.getEndPosition()))) { + lastPost = range.getEndPosition(); + this._zone.updatePosition(lastPost); } + }, undefined, listener); - readonly myAlternativeVersionId: number; - - constructor(readonly model: ITextModel) { - super(`undo@${model.getAlternativeVersionId()}`, localize('undoStep', "Undo This Step"), ThemeIcon.asClassName(Codicon.discard), false); - this.myAlternativeVersionId = model.getAlternativeVersionId(); - UndoStepAction.all.push(this); - UndoStepAction.updateUndoSteps(); - } - - override async run() { - this.model.undo(); - UndoStepAction.updateUndoSteps(); - } - } - - - // CANCEL if the document has changed outside the current range + let ignoreModelChanges = false; this._editor.onDidChangeModelContent(e => { - let cancel = false; - const wholeRange = decoWholeRange.getRange(0); - if (!wholeRange) { - cancel = true; - } else { + // UPDATE undo actions based on alternative version id + UndoStepAction.updateUndoSteps(); + + // CANCEL if the document has changed outside the current range + if (!ignoreModelChanges) { + const wholeRange = wholeRangeDecoration.getRange(0); + if (!wholeRange) { + this._ctsSession.cancel(); + this._logService.trace('[IE] ABORT wholeRange seems gone/collapsed'); + return; + } for (const change of e.changes) { if (!Range.areIntersectingOrTouching(wholeRange, change.range)) { - cancel = true; + this._ctsSession.cancel(); + this._logService.trace('[IE] CANCEL because of model change OUTSIDE range'); break; } } } - if (cancel) { - this._ctsSession.cancel(); - this._logService.trace('[IE] CANCEL because of model change OUTSIDE range'); - return; - } - - // - UndoStepAction.updateUndoSteps(); - - }, undefined, listener); do { - - const wholeRange = decoWholeRange.getRange(0); + const wholeRange = wholeRangeDecoration.getRange(0); if (!wholeRange) { // nuked whole file contents? + this._logService.trace('[IE] ABORT wholeRange seems gone/collapsed'); break; } - const newDecorations: IModelDeltaDecoration[] = [{ + // visuals: add block decoration + blockDecoration.set([{ range: wholeRange, options: InteractiveEditorController._decoBlock - }]; - - decoBorder.set(newDecorations); + }]); this._historyOffset = -1; - const input = await this._zone.getInput(wholeRange.collapseToEnd(), placeholder, value, this._ctsSession.token); + const input = await this._zone.getInput(wholeRange.getEndPosition(), placeholder, value, this._ctsSession.token); if (!input || !input.value) { continue; } - const historyEntry = this._zone.widget.createHistoryEntry(input.value); - - historyEntry.updateActions([new Action('cancelRequest', localize('cancel', "Cancel Request"), ThemeIcon.asClassName(Codicon.debugStop), true, () => { - this._ctsRequest?.cancel(); - })]); - this._ctsRequest?.dispose(true); this._ctsRequest = new CancellationTokenSource(this._ctsSession.token); + const historyEntry = this._zone.widget.createHistoryEntry(input.value); + const sw = StopWatch.create(); - const task = provider.provideResponse( - session, - { - session, - prompt: input.value, - selection: this._editor.getSelection(), - wholeRange: wholeRange - }, - this._ctsRequest.token - ); + const request: IInteractiveEditorRequest = { + prompt: input.value, + selection: this._editor.getSelection(), + wholeRange + }; + const task = provider.provideResponse(session, request, this._ctsRequest.token); + this._logService.trace('[IE] request started', provider.debugName, session, request); let reply: IInteractiveEditorResponse | null | undefined; try { @@ -684,76 +717,63 @@ export class InteractiveEditorController implements IEditorContribution { this._logService.trace('[IE] request took', sw.elapsed(), provider.debugName); if (this._ctsRequest.token.isCancellationRequested) { + this._logService.trace('[IE] request CANCELED', provider.debugName); value = input.value; continue; } if (!reply || isFalsyOrEmpty(reply.edits)) { this._logService.trace('[IE] NO reply or edits', provider.debugName); - reply = { edits: [] }; - placeholder = ''; + value = input.value; continue; } // make edits more minimal - const moreMinimalEdits = (await this._editorWorkerService.computeMoreMinimalEdits(this._editor.getModel().uri, reply.edits, true)) ?? reply.edits; + const moreMinimalEdits = (await this._editorWorkerService.computeMoreMinimalEdits(textModel.uri, reply.edits, true)); + this._logService.trace('[IE] edits from PROVIDER and after making them MORE MINIMAL', provider.debugName, reply.edits, moreMinimalEdits); + this._recorder.addExchange(session, request, reply); - // clear old preview - decoInlineDiff.clear(); + // inline diff + inlineDiffDecorations.clear(); + const newInlineDiffDecorationsData: IModelDeltaDecoration[] = []; - const altIdNow = this._editor.getModel().getAlternativeVersionId(); - const undoEdits: IValidEditOperation[] = []; - this._editor.pushUndoStop(); - this._editor.executeEdits( - 'interactive-editor', - moreMinimalEdits.map(edit => { - // return EditOperation.replaceMove(Range.lift(edit.range), edit.text); ??? - return EditOperation.replace(Range.lift(edit.range), edit.text); - }), - _undoEdits => { + try { + ignoreModelChanges = true; + + const cursorStateComputerAndInlineDiffCollection: ICursorStateComputer = (undoEdits) => { let last: Position | null = null; - for (const undoEdit of _undoEdits) { - undoEdits.push(undoEdit); - last = !last || last.isBefore(undoEdit.range.getEndPosition()) ? undoEdit.range.getEndPosition() : last; + for (const edit of undoEdits) { + last = !last || last.isBefore(edit.range.getEndPosition()) ? edit.range.getEndPosition() : last; + newInlineDiffDecorationsData.push(InteractiveEditorController._asInlineDiffDecorationData(edit)); } return last && [Selection.fromPositions(last)]; - } - ); - this._editor.pushUndoStop(); + }; - if (input.preview) { - const decorations: IModelDeltaDecoration[] = []; - for (const edit of undoEdits) { + this._editor.pushUndoStop(); + this._editor.executeEdits( + 'interactive-editor', + (moreMinimalEdits ?? reply.edits).map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)), + cursorStateComputerAndInlineDiffCollection + ); + this._editor.pushUndoStop(); - let content = edit.text; - if (content.length > 12) { - content = content.substring(0, 12) + '…'; - } - decorations.push({ - range: edit.range, - options: { - description: 'interactive-editor-inline-diff', - className: 'interactive-editor-lines-inserted-range', - before: { - content, - inlineClassName: 'interactive-editor-lines-deleted-range-inline', - attachedData: edit - } - } - }); - } - decoInlineDiff.set(decorations); + } finally { + ignoreModelChanges = false; } - historyEntry.updateActions([new UndoStepAction(this._editor.getModel())]); + inlineDiffDecorations.set(input.preview ? newInlineDiffDecorationsData : []); + + historyEntry.updateActions([new class extends UndoStepAction { + constructor() { + super(textModel); + } + override async run() { + super.run(); + historyEntry.updateVisibility(false); + value = input.value; + } + }]); - // keep edits - decoEdits.push([this._editor.createDecorationsCollection(undoEdits.map(edit => { - return { - range: edit.range, - options: InteractiveEditorController._decoEdits - }; - })), altIdNow]); if (!InteractiveEditorController._promptHistory.includes(input.value)) { InteractiveEditorController._promptHistory.unshift(input.value); @@ -763,9 +783,9 @@ export class InteractiveEditorController implements IEditorContribution { } while (!this._ctsSession.token.isCancellationRequested); // done, cleanup - decoWholeRange.clear(); - decoBorder.clear(); - decoInlineDiff.clear(); + wholeRangeDecoration.clear(); + blockDecoration.clear(); + inlineDiffDecorations.clear(); listener.dispose(); session.dispose?.(); @@ -778,6 +798,25 @@ export class InteractiveEditorController implements IEditorContribution { this._logService.trace('[IE] session DONE', provider.debugName); } + private static _asInlineDiffDecorationData(edit: IValidEditOperation): IModelDeltaDecoration { + let content = edit.text; + if (content.length > 12) { + content = content.substring(0, 12) + '…'; + } + return { + range: edit.range, + options: { + description: 'interactive-editor-inline-diff', + className: 'interactive-editor-lines-inserted-range', + before: { + content, + inlineClassName: 'interactive-editor-lines-deleted-range-inline', + attachedData: edit + } + } + }; + } + accept(preview: boolean = this._preview): void { this._zone.widget.acceptInput(preview); } @@ -825,4 +864,8 @@ export class InteractiveEditorController implements IEditorContribution { toggleHistory(): void { this._zone.widget.toggleHistory(); } + + recordings() { + return this._recorder.getAll(); + } } diff --git a/src/vs/editor/contrib/interactive/common/interactiveEditor.ts b/src/vs/editor/contrib/interactive/common/interactiveEditor.ts index 06a15d64ce5..52c6130073e 100644 --- a/src/vs/editor/contrib/interactive/common/interactiveEditor.ts +++ b/src/vs/editor/contrib/interactive/common/interactiveEditor.ts @@ -21,9 +21,7 @@ export interface IInteractiveEditorSession { } export interface IInteractiveEditorRequest { - session: IInteractiveEditorSession; prompt: string; - // model: ITextModel; selection: ISelection; wholeRange: IRange; }