From 95355e457d0abc852e4a6fdb310eb853de041814 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 17 Aug 2021 13:25:10 +0200 Subject: [PATCH] When injected text changes, only call setCursors when the cursor position also changed. This fixes #130982. --- src/vs/editor/common/controller/cursor.ts | 32 ++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/common/controller/cursor.ts b/src/vs/editor/common/controller/cursor.ts index a14ba69259f..d4e43cd3a00 100644 --- a/src/vs/editor/common/controller/cursor.ts +++ b/src/vs/editor/common/controller/cursor.ts @@ -332,7 +332,15 @@ export class CursorsController extends Disposable { if (e instanceof ModelInjectedTextChangedEvent) { // If injected texts change, the view positions of all cursors need to be updated. const selectionsFromMarkers = this._cursors.readSelectionFromMarkers(); - this.setStates(eventsCollector, 'modelChange', CursorChangeReason.RecoverFromMarkers, CursorState.fromModelSelections(selectionsFromMarkers)); + const newState = CursorState.fromModelSelections(selectionsFromMarkers); + + if (didStateChange(this.getCursorStates(), newState || [])) { + // setStates might remove markers, which could trigger a decoration change. + // If there are injected text decorations for that line, `onModelContentChanged` is emitted again + // and an endless recursion happens. + // This is why we only call setStates if we really need to (this fixes recursion). + this.setStates(eventsCollector, 'modelChange', CursorChangeReason.RecoverFromMarkers, newState); + } } else { this._knownModelVersionId = e.versionId; if (this._isHandling) { @@ -719,6 +727,28 @@ export class CursorsController extends Disposable { } } +function didStateChange(currentStates: CursorState[], newStates: PartialCursorState[]): boolean { + if (currentStates.length !== newStates.length) { + return true; + } + + for (let i = 0; i < currentStates.length; i++) { + const curState = currentStates[i]; + const newState = newStates[i]; + if (newState.modelState) { + if (!newState.modelState.equals(curState.modelState)) { + return true; + } + } + if (newState.viewState) { + if (!newState.viewState.equals(curState.viewState)) { + return true; + } + } + } + return false; +} + interface IExecContext { readonly model: ITextModel; readonly selectionsBefore: Selection[];