From ae0cfd5bf223979e3df202aaadf34ad3e65cc768 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 5 Jul 2024 15:12:27 +0800 Subject: [PATCH] fix: improve live inline diff prioritization and fix discard functionality (#3837) * fix: discard * fix: content widget handle * chore: improve prioritize those closest to the head * chore: improve layout code * fix: removed widget position * feat: support setValue * fix: inlineContentWidget --- .../widget/inline-chat/inline-chat.handler.ts | 26 +--- .../inline-diff/inline-diff-previewer.ts | 67 +++++---- .../inline-stream-diff.handler.tsx | 141 ++++++++++++------ .../live-preview.decoration.tsx | 136 ++++++++++------- 4 files changed, 220 insertions(+), 150 deletions(-) diff --git a/packages/ai-native/src/browser/widget/inline-chat/inline-chat.handler.ts b/packages/ai-native/src/browser/widget/inline-chat/inline-chat.handler.ts index 5ff6be6aff..3cb008fd66 100644 --- a/packages/ai-native/src/browser/widget/inline-chat/inline-chat.handler.ts +++ b/packages/ai-native/src/browser/widget/inline-chat/inline-chat.handler.ts @@ -336,23 +336,13 @@ export class InlineChatHandler extends Disposable { this.diffPreviewer = this.injector.get(LiveInlineDiffPreviewer, [monacoEditor, crossSelection]); } + this.diffPreviewer.mount(this.aiInlineContentWidget); + this.diffPreviewer.show( crossSelection.startLineNumber - 1, crossSelection.endLineNumber - crossSelection.startLineNumber + 2, ); - const doLayoutContentWidget = () => { - if (inlineDiffMode === EInlineDifPreviewMode.sideBySide) { - this.aiInlineContentWidget.setPositionPreference([ContentWidgetPositionPreference.BELOW]); - } else { - this.aiInlineContentWidget.setPositionPreference([ContentWidgetPositionPreference.EXACT]); - } - this.aiInlineContentWidget?.setOptions({ - position: this.diffPreviewer.getPosition(), - }); - this.aiInlineContentWidget?.layoutContentWidget(); - }; - if (InlineChatController.is(chatResponse)) { const controller = chatResponse as InlineChatController; @@ -374,7 +364,7 @@ export class InlineChatHandler extends Disposable { isRetry, }); this.diffPreviewer.onError(error); - doLayoutContentWidget(); + this.diffPreviewer.layout(); }), controller.onAbort(() => { this.convertInlineChatStatus(EInlineChatStatus.READY, { @@ -385,7 +375,7 @@ export class InlineChatHandler extends Disposable { isStop: true, }); this.diffPreviewer.onAbort(); - doLayoutContentWidget(); + this.diffPreviewer.layout(); }), controller.onEnd(() => { this.convertInlineChatStatus(EInlineChatStatus.DONE, { @@ -395,7 +385,7 @@ export class InlineChatHandler extends Disposable { isRetry, }); this.diffPreviewer.onEnd(); - doLayoutContentWidget(); + this.diffPreviewer.layout(); }), ]); }), @@ -442,11 +432,7 @@ export class InlineChatHandler extends Disposable { ); } - this.aiInlineChatOperationDisposed.addDispose( - this.diffPreviewer.onLayout(() => { - doLayoutContentWidget(); - }), - ); + this.diffPreviewer.layout(); this.aiInlineChatOperationDisposed.addDispose( this.diffPreviewer.onDispose(() => { diff --git a/packages/ai-native/src/browser/widget/inline-diff/inline-diff-previewer.ts b/packages/ai-native/src/browser/widget/inline-diff/inline-diff-previewer.ts index a664fdc2fa..bc04563ffa 100644 --- a/packages/ai-native/src/browser/widget/inline-diff/inline-diff-previewer.ts +++ b/packages/ai-native/src/browser/widget/inline-diff/inline-diff-previewer.ts @@ -1,12 +1,13 @@ import { Autowired, INJECTOR_TOKEN, Injectable, Injector } from '@opensumi/di'; import { Disposable, ErrorResponse, ReplyResponse } from '@opensumi/ide-core-common'; import { EOL, ICodeEditor, IPosition, ITextModel, Position, Selection } from '@opensumi/ide-monaco'; -import { LineRange } from '@opensumi/monaco-editor-core/esm/vs/editor/common/core/lineRange'; +import { ContentWidgetPositionPreference } from '@opensumi/ide-monaco/lib/browser/monaco-exports/editor'; import { DefaultEndOfLine } from '@opensumi/monaco-editor-core/esm/vs/editor/common/model'; import { createTextBuffer } from '@opensumi/monaco-editor-core/esm/vs/editor/common/model/textModel'; import { ModelService } from '@opensumi/monaco-editor-core/esm/vs/editor/common/services/modelService'; import { EResultKind } from '../inline-chat/inline-chat.service'; +import { AIInlineContentWidget } from '../inline-chat/inline-content-widget'; import { EComputerMode, InlineStreamDiffHandler } from '../inline-stream-diff/inline-stream-diff.handler'; import { InlineDiffWidget } from './inline-diff-widget'; @@ -16,6 +17,8 @@ export abstract class BaseInlineDiffPreviewer extends Disposable { @Autowired(INJECTOR_TOKEN) protected readonly injector: Injector; + protected inlineContentWidget: AIInlineContentWidget | null = null; + constructor(protected readonly monacoEditor: ICodeEditor, protected readonly selection: Selection) { super(); this.node = this.createNode(); @@ -30,15 +33,20 @@ export abstract class BaseInlineDiffPreviewer extends Disposable { return this.node; } + public mount(contentWidget: AIInlineContentWidget): void { + this.inlineContentWidget = contentWidget; + } + + public layout(): void { + this.inlineContentWidget?.setOptions({ position: this.getPosition() }); + this.inlineContentWidget?.layoutContentWidget(); + } + abstract onReady(exec: () => void): Disposable; - abstract onLayout(exec: () => void): Disposable; abstract createNode(): N; abstract onData(data: ReplyResponse): void; abstract handleAction(action: EResultKind): void; - - getPosition(): IPosition | undefined { - return undefined; - } + abstract getPosition(): IPosition; show(line: number, heightInLines: number): void { // do nothing @@ -80,19 +88,15 @@ export class SideBySideInlineDiffWidget extends BaseInlineDiffPreviewer { - widget.dispose(); - }), - ); + this.addDispose(widget); return widget; } - getPosition(): IPosition | undefined { + getPosition(): IPosition { return Position.lift({ lineNumber: this.selection.endLineNumber + 1, column: 1 }); } - onLayout(exec: () => void): Disposable { - requestAnimationFrame(() => exec()); - return this; + layout(): void { + this.inlineContentWidget?.setPositionPreference([ContentWidgetPositionPreference.BELOW]); + super.layout(); } onReady(exec: () => void): Disposable { this.addDispose(this.node.onReady(exec.bind(this))); @@ -150,14 +154,27 @@ export class LiveInlineDiffPreviewer extends BaseInlineDiffPreviewer this.layout())); this.addDispose(node.onDispose(() => this.dispose())); this.addDispose(node); + + node.registerPartialEditWidgetHandle((widgets) => { + if (widgets.every((widget) => widget.isHidden)) { + this.dispose(); + this.inlineContentWidget?.dispose(); + } + }); return node; } - getPosition(): IPosition | undefined { + getPosition(): IPosition { const zone = this.node.getZone(); return Position.lift({ lineNumber: Math.max(0, zone.startLineNumber - 1), column: 1 }); } + setValue(content: string): void { + const diffModel = this.node.recompute(EComputerMode.legacy, content); + this.node.readyRender(diffModel); + } handleAction(action: EResultKind): void { switch (action) { case EResultKind.ACCEPT: @@ -167,29 +184,23 @@ export class LiveInlineDiffPreviewer extends BaseInlineDiffPreviewer void): Disposable { - this.node.onDidEditChange(exec.bind(this)); - return this; + layout(): void { + this.inlineContentWidget?.setPositionPreference([ContentWidgetPositionPreference.EXACT]); + super.layout(); } onData(data: ReplyResponse): void { const { message } = data; this.node.addLinesToDiff(message); } onEnd(): void { - const { changes } = this.node.recompute(EComputerMode.legacy); - const zone = this.node.getZone(); - const allAddRanges = changes.map((c) => { - const lineNumber = zone.startLineNumber + c.addedRange.startLineNumber - 1; - return new LineRange(lineNumber, lineNumber + 1); - }); - this.node.renderPartialEditWidgets(allAddRanges); - this.node.pushStackElement(); - this.monacoEditor.focus(); + const diffModel = this.node.recompute(EComputerMode.legacy); + this.node.readyRender(diffModel); } } diff --git a/packages/ai-native/src/browser/widget/inline-stream-diff/inline-stream-diff.handler.tsx b/packages/ai-native/src/browser/widget/inline-stream-diff/inline-stream-diff.handler.tsx index 30f288c5b6..0cf4f0a603 100644 --- a/packages/ai-native/src/browser/widget/inline-stream-diff/inline-stream-diff.handler.tsx +++ b/packages/ai-native/src/browser/widget/inline-stream-diff/inline-stream-diff.handler.tsx @@ -7,9 +7,10 @@ import { LineRange } from '@opensumi/monaco-editor-core/esm/vs/editor/common/cor import { linesDiffComputers } from '@opensumi/monaco-editor-core/esm/vs/editor/common/diff/linesDiffComputers'; import { DetailedLineRangeMapping } from '@opensumi/monaco-editor-core/esm/vs/editor/common/diff/rangeMapping'; import { IModelService } from '@opensumi/monaco-editor-core/esm/vs/editor/common/services/model'; +import { LineTokens } from '@opensumi/monaco-editor-core/esm/vs/editor/common/tokens/lineTokens'; import { UndoRedoGroup } from '@opensumi/monaco-editor-core/esm/vs/platform/undoRedo/common/undoRedo'; -import { LivePreviewDiffDecorationModel } from './live-preview.decoration'; +import { AcceptPartialEditWidget, LivePreviewDiffDecorationModel } from './live-preview.decoration'; interface IRangeChangeData { removedTextLines: string[]; @@ -42,12 +43,15 @@ export class InlineStreamDiffHandler extends Disposable { private virtualModel: ITextModel; private rawOriginalTextLines: string[]; + private rawOriginalTextLinesTokens: LineTokens[] = []; + private livePreviewDiffDecorationModel: LivePreviewDiffDecorationModel; private schedulerHandleEdits: RunOnceScheduler; private currentDiffModel: IComputeDiffData; private undoRedoGroup: UndoRedoGroup; + private partialEditWidgetHandle: (widgets: AcceptPartialEditWidget[]) => void; protected readonly _onDidEditChange = new Emitter(); public readonly onDidEditChange: Event = this._onDidEditChange.event; @@ -56,10 +60,6 @@ export class InlineStreamDiffHandler extends Disposable { super(); this.undoRedoGroup = new UndoRedoGroup(); - this.livePreviewDiffDecorationModel = this.injector.get(LivePreviewDiffDecorationModel, [ - this.monacoEditor, - this.selection, - ]); const modelService = StandaloneServices.get(IModelService); this.virtualModel = modelService.createModel('', null); @@ -71,7 +71,12 @@ export class InlineStreamDiffHandler extends Disposable { .getValueInRange(Range.fromPositions(startPosition, endPosition)) .split(eol); - this.livePreviewDiffDecorationModel.calcTextLinesTokens(this.rawOriginalTextLines); + this.rawOriginalTextLinesTokens = this.rawOriginalTextLines.map((_, index) => { + const lineNumber = startPosition.lineNumber + index; + this.originalModel.tokenization.forceTokenization(lineNumber); + const lineTokens = this.originalModel.tokenization.getLineTokens(lineNumber); + return lineTokens; + }); this.schedulerHandleEdits = new RunOnceScheduler(() => { if (this.currentDiffModel) { @@ -79,7 +84,24 @@ export class InlineStreamDiffHandler extends Disposable { } }, 16 * 12.5); + this.initializeDecorationModel(); + } + + private initializeDecorationModel(): void { + this.livePreviewDiffDecorationModel = this.injector.get(LivePreviewDiffDecorationModel, [ + this.monacoEditor, + this.selection, + ]); + this.addDispose(this.livePreviewDiffDecorationModel); + + this.addDispose( + this.livePreviewDiffDecorationModel.onPartialEditWidgetListChange((partialWidgets) => { + if (this.partialEditWidgetHandle) { + this.partialEditWidgetHandle(partialWidgets); + } + }), + ); } private get originalModel(): ITextModel { @@ -169,34 +191,63 @@ export class InlineStreamDiffHandler extends Disposable { }; } + public registerPartialEditWidgetHandle(exec: (widgets: AcceptPartialEditWidget[]) => void) { + this.partialEditWidgetHandle = exec; + } + public discard(): void { - const eol = this.originalModel.getEOL(); - const zone = this.getZone(); - this.originalModel.pushEditOperations( - null, - [ - { - range: zone.toInclusiveRange()!, - text: this.rawOriginalTextLines.join(eol), - }, - ], - () => null, - ); + this.livePreviewDiffDecorationModel.discardUnProcessed(); } public getZone(): LineRange { return this.livePreviewDiffDecorationModel.getZone(); } - public renderPartialEditWidgets(range: LineRange[]): void { - this.livePreviewDiffDecorationModel.touchPartialEditWidgets(range); + private renderPartialEditWidgets(diffModel: IComputeDiffData): void { + const { changes } = diffModel; + const zone = this.getZone(); + const allAddRanges = changes.map((c) => { + const lineNumber = zone.startLineNumber + c.addedRange.startLineNumber - 1; + return new LineRange(lineNumber, lineNumber + 1); + }); + + this.livePreviewDiffDecorationModel.touchPartialEditWidgets(allAddRanges); + } + + private renderAddedRangeDecoration(diffModel: IComputeDiffData): void { + const allAddRanges = diffModel.changes.map((c) => c.addedRange); + this.livePreviewDiffDecorationModel.touchAddedRange(allAddRanges); + } + + private renderRemovedRangeDecoration(diffModel: IComputeDiffData): void { + const { changes } = diffModel; + const zone = this.getZone(); + + let preRemovedLen: number = 0; + this.livePreviewDiffDecorationModel.clearRemovedWidgets(); + + for (const change of changes) { + const { removedTextLines, removedLinesOriginalRange, addedRange } = change; + + if (removedTextLines.length > 0) { + this.livePreviewDiffDecorationModel.showRemovedWidgetByLineNumber( + zone.startLineNumber + removedLinesOriginalRange.startLineNumber - 2 - preRemovedLen, + removedTextLines.map((text, index) => ({ + text, + lineTokens: this.rawOriginalTextLinesTokens[removedLinesOriginalRange.startLineNumber - 1 + index], + })), + ); + } + + preRemovedLen += removedLinesOriginalRange.length - addedRange.length; + } } /** * 令当前的 inline diff 在流式渲染过程当中使用 pushEditOperations 进行编辑的操作都放在同一组 undo/redo 堆栈里 * 一旦撤销到最顶层则关闭当前的 inline diff */ - public pushStackElement(): void { + private pushStackElement(): void { this.livePreviewDiffDecorationModel.pushUndoElement({ undo: () => this.dispose(), redo: () => { @@ -207,7 +258,7 @@ export class InlineStreamDiffHandler extends Disposable { } private handleEdits(diffModel: IComputeDiffData): void { - const { activeLine, changes, newFullRangeTextLines, pendingRange } = diffModel; + const { activeLine, newFullRangeTextLines, pendingRange } = diffModel; const eol = this.originalModel.getEOL(); const zone = this.getZone(); @@ -299,8 +350,7 @@ export class InlineStreamDiffHandler extends Disposable { /** * handle added range decoration */ - const allAddRanges = changes.map((c) => c.addedRange); - this.livePreviewDiffDecorationModel.touchAddedRange(allAddRanges); + this.renderAddedRangeDecoration(diffModel); /** * handle pending range decoration @@ -314,40 +364,37 @@ export class InlineStreamDiffHandler extends Disposable { /** * handle removed range */ - let preRemovedLen: number = 0; - this.livePreviewDiffDecorationModel.clearRemovedWidgets(); + this.renderRemovedRangeDecoration(diffModel); - for (const change of changes) { - const { removedTextLines, removedLinesOriginalRange, addedRange } = change; - - if (removedTextLines.length > 0) { - this.livePreviewDiffDecorationModel.showRemovedWidgetByLineNumber( - validZone.startLineNumber + removedLinesOriginalRange.startLineNumber - 2 - preRemovedLen, - removedLinesOriginalRange, - removedTextLines, - ); - } + this._onDidEditChange.fire(); + } - preRemovedLen += removedLinesOriginalRange.length - addedRange.length; + private doSchedulerEdits(): void { + if (!this.schedulerHandleEdits.isScheduled()) { + this.schedulerHandleEdits.schedule(); } + } - this._onDidEditChange.fire(); + public recompute(computerMode: EComputerMode, newContent?: string): IComputeDiffData { + if (newContent) { + this.virtualModel.setValue(newContent); + } + const newTextLines = this.virtualModel.getLinesContent(); + this.currentDiffModel = this.computeDiff(this.rawOriginalTextLines, newTextLines, computerMode); + return this.currentDiffModel; } public addLinesToDiff(newText: string, computerMode: EComputerMode = EComputerMode.default): void { this.virtualModel.setValue(newText); this.recompute(computerMode); + this.doSchedulerEdits(); } - public recompute(computerMode: EComputerMode): IComputeDiffData { - const newTextLines = this.virtualModel.getLinesContent(); - const diffModel = this.computeDiff(this.rawOriginalTextLines, newTextLines, computerMode); - this.currentDiffModel = diffModel; - - if (!this.schedulerHandleEdits.isScheduled()) { - this.schedulerHandleEdits.schedule(); - } + public readyRender(diffModel: IComputeDiffData): void { + this.doSchedulerEdits(); - return diffModel; + this.renderPartialEditWidgets(diffModel); + this.pushStackElement(); + this.monacoEditor.focus(); } } diff --git a/packages/ai-native/src/browser/widget/inline-stream-diff/live-preview.decoration.tsx b/packages/ai-native/src/browser/widget/inline-stream-diff/live-preview.decoration.tsx index 9a16dd7e2d..be8017fdd7 100644 --- a/packages/ai-native/src/browser/widget/inline-stream-diff/live-preview.decoration.tsx +++ b/packages/ai-native/src/browser/widget/inline-stream-diff/live-preview.decoration.tsx @@ -7,15 +7,7 @@ import { KeybindingRegistry, StackingLevel } from '@opensumi/ide-core-browser'; import { AI_INLINE_DIFF_PARTIAL_EDIT } from '@opensumi/ide-core-browser/lib/ai-native/command'; import { Disposable, Emitter, Event, isUndefined, uuid } from '@opensumi/ide-core-common'; import { ISingleEditOperation } from '@opensumi/ide-editor'; -import { - ICodeEditor, - IEditorDecorationsCollection, - IPosition, - IRange, - Position, - Range, - Selection, -} from '@opensumi/ide-monaco'; +import { ICodeEditor, IEditorDecorationsCollection, Position, Range, Selection } from '@opensumi/ide-monaco'; import { ReactInlineContentWidget } from '@opensumi/ide-monaco/lib/browser/ai-native/BaseInlineContentWidget'; import { StandaloneServices } from '@opensumi/ide-monaco/lib/browser/monaco-api/services'; import { ContentWidgetPositionPreference } from '@opensumi/ide-monaco/lib/browser/monaco-exports/editor'; @@ -54,8 +46,16 @@ enum EPartialEdit { discard = 'discard', } +interface ITextLinesTokens { + text: string; + lineTokens: LineTokens; +} + +/** + * @internal + */ @Injectable({ multiple: true }) -class AcceptPartialEditWidget extends ReactInlineContentWidget { +export class AcceptPartialEditWidget extends ReactInlineContentWidget { static ID = 'AcceptPartialEditWidgetID'; @Autowired(KeybindingRegistry) @@ -199,13 +199,16 @@ export class LivePreviewDiffDecorationModel extends Disposable { private addedRangeDec: EnhanceDecorationsCollection; private partialEditWidgetList: AcceptPartialEditWidget[] = []; private removedZoneWidgets: Array = []; - private rawOriginalTextLinesTokens: LineTokens[] = []; private undoRedoService: IUndoRedoService; private zone: LineRange; private aiNativeContextKey: AINativeContextKey; + protected readonly _onPartialEditWidgetListChange = new Emitter(); + public readonly onPartialEditWidgetListChange: Event = + this._onPartialEditWidgetListChange.event; + constructor(private readonly monacoEditor: ICodeEditor, private readonly selection: Selection) { super(); @@ -248,9 +251,25 @@ export class LivePreviewDiffDecorationModel extends Disposable { this.addDispose( this.inlineStreamDiffService.onAcceptDiscardPartialEdit((isAccept) => { - const firstWidget = this.partialEditWidgetList.filter((p) => !p.isHidden)[0]; - if (firstWidget) { - this.handlePartialEditAction(isAccept ? EPartialEdit.accept : EPartialEdit.discard, firstWidget); + const currentPosition = this.monacoEditor.getPosition()!; + + /** + * 找出离当前光标最近的操作点 + */ + const widget = this.partialEditWidgetList + .filter((p) => !p.isHidden) + .sort((pa, pb) => { + const paLineNumber = pa.getPosition()?.position?.lineNumber || 1; + const pbLineNumber = pb.getPosition()?.position?.lineNumber || 1; + + const distanceToPa = Math.abs(currentPosition.lineNumber - paLineNumber); + const distanceToPb = Math.abs(currentPosition.lineNumber - pbLineNumber); + + return distanceToPa - distanceToPb; + }); + + if (widget.length > 0) { + this.handlePartialEditAction(isAccept ? EPartialEdit.accept : EPartialEdit.discard, widget[0]); } }), ); @@ -268,32 +287,19 @@ export class LivePreviewDiffDecorationModel extends Disposable { ); } - public calcTextLinesTokens(rawOriginalTextLines: string[]): void { - this.rawOriginalTextLinesTokens = rawOriginalTextLines.map((_, index) => { - const model = this.monacoEditor.getModel()!; - const zone = this.getZone(); - const lineNumber = zone.startLineNumber + index; - - model.tokenization.forceTokenization(lineNumber); - const lineTokens = model.tokenization.getLineTokens(lineNumber); - - return lineTokens; - }); - } - - public showRemovedWidgetByLineNumber( - lineNumber: number, - removedLinesOriginalRange: LineRange, - texts: string[], - ): void { - const position = new Position(lineNumber, this.monacoEditor.getModel()!.getLineMaxColumn(lineNumber) || 1); + public showRemovedWidgetByLineNumber(lineNumber: number, texts: ITextLinesTokens[]): void { + const position = new Position(lineNumber, 1); const heightInLines = texts.length; - const widget = new RemovedZoneWidget(this.monacoEditor, texts, { - showInHiddenAreas: true, - showFrame: false, - showArrow: false, - }); + const widget = new RemovedZoneWidget( + this.monacoEditor, + texts.map((t) => t.text), + { + showInHiddenAreas: true, + showFrame: false, + showArrow: false, + }, + ); widget.create(); @@ -301,10 +307,10 @@ export class LivePreviewDiffDecorationModel extends Disposable { renderLines( dom, this.monacoEditor.getOption(EditorOption.tabIndex), - texts.map((content, index) => ({ + texts.map(({ text: content, lineTokens }) => ({ content, decorations: [], - lineTokens: this.rawOriginalTextLinesTokens[removedLinesOriginalRange.startLineNumber - 1 + index], + lineTokens, })), this.monacoEditor.getOptions(), ); @@ -362,7 +368,7 @@ export class LivePreviewDiffDecorationModel extends Disposable { ]); } - private doDiscard( + private doDiscardPartialWidget( partialWidget: AcceptPartialEditWidget, addedDec?: IEnhanceModelDeltaDecoration, removedWidget?: RemovedZoneWidget, @@ -405,7 +411,7 @@ export class LivePreviewDiffDecorationModel extends Disposable { return operation; } - private handlePartialEditAction(type: EPartialEdit, widget: AcceptPartialEditWidget) { + private handlePartialEditAction(type: EPartialEdit, widget: AcceptPartialEditWidget, isPushStack: boolean = true) { const position = widget.getPosition()!.position!; const model = this.monacoEditor.getModel()!; /** @@ -434,22 +440,26 @@ export class LivePreviewDiffDecorationModel extends Disposable { switch (type) { case EPartialEdit.accept: hide(); - this.pushUndoElement({ - undo: () => resume(), - redo: () => hide(), - group, - }); + if (isPushStack) { + this.pushUndoElement({ + undo: () => resume(), + redo: () => hide(), + group, + }); + } break; case EPartialEdit.discard: { - const operation = this.doDiscard(widget, findAddedDec, findRemovedWidget); + const operation = this.doDiscardPartialWidget(widget, findAddedDec, findRemovedWidget); if (operation) { - this.pushUndoElement({ - undo: () => resume(), - redo: () => hide(), - group, - }); + if (isPushStack) { + this.pushUndoElement({ + undo: () => resume(), + redo: () => hide(), + group, + }); + } model.pushEditOperations(null, [operation], () => null, group); } } @@ -460,6 +470,7 @@ export class LivePreviewDiffDecorationModel extends Disposable { } this.monacoEditor.focus(); + this._onPartialEditWidgetListChange.fire(this.partialEditWidgetList); } /** @@ -477,6 +488,13 @@ export class LivePreviewDiffDecorationModel extends Disposable { }); } + public discardUnProcessed(): void { + const otherWidgets = this.partialEditWidgetList.filter((widget) => !widget.isHidden); + otherWidgets.forEach((widget) => { + this.handlePartialEditAction(EPartialEdit.discard, widget, false); + }); + } + public pushUndoElement(data: { undo: () => void; redo: () => void; group?: UndoRedoGroup }): void { const resource = this.monacoEditor.getModel()!.uri; const group = data.group ?? new UndoRedoGroup(); @@ -486,8 +504,16 @@ export class LivePreviewDiffDecorationModel extends Disposable { type: UndoRedoElementType.Resource, resource, label: 'Live.Preview.UndoRedo', - undo: data.undo, - redo: data.redo, + undo: () => { + if (!this.disposed) { + data.undo(); + } + }, + redo: () => { + if (!this.disposed) { + data.redo(); + } + }, } as IResourceUndoRedoElement, group, );