diff --git a/src/vs/base/common/controlFlow.ts b/src/vs/base/common/controlFlow.ts new file mode 100644 index 00000000000..2c4d020dd99 --- /dev/null +++ b/src/vs/base/common/controlFlow.ts @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { BugIndicatingError } from 'vs/base/common/errors'; + +/* + * This file contains helper classes to manage control flow. +*/ + +/** + * Prevents code from being re-entrant. +*/ +export class ReentrancyBarrier { + private _isOccupied = false; + + /** + * Calls `runner` if the barrier is not occupied. + * During the call, the barrier becomes occupied. + */ + public runExclusivelyOrSkip(runner: () => void): void { + if (this._isOccupied) { + return; + } + this._isOccupied = true; + try { + runner(); + } finally { + this._isOccupied = false; + } + } + + /** + * Calls `runner`. If the barrier is occupied, throws an error. + * During the call, the barrier becomes active. + */ + public runExclusivelyOrThrow(runner: () => void): void { + if (this._isOccupied) { + throw new BugIndicatingError(`ReentrancyBarrier: reentrant call detected!`); + } + this._isOccupied = true; + try { + runner(); + } finally { + this._isOccupied = false; + } + } + + /** + * Indicates if some runner occupies this barrier. + */ + public get isOccupied() { + return this._isOccupied; + } + + public makeExclusiveOrSkip(fn: TFunction): TFunction { + return ((...args: any[]) => { + if (this._isOccupied) { + return; + } + this._isOccupied = true; + try { + return fn(...args); + } finally { + this._isOccupied = false; + } + }) as any; + } +} diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts index 2affffb8df3..9f888b4cec5 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts @@ -10,7 +10,7 @@ import { ITextModel } from 'vs/editor/common/model'; import { DetailedLineRangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; import { LineRangeEdit } from 'vs/workbench/contrib/mergeEditor/browser/model/editing'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; -import { ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/utils'; +import { ReentrancyBarrier } from '../../../../../base/common/controlFlow'; import { IMergeDiffComputer } from './diffComputer'; import { autorun, IObservable, IReader, ITransaction, observableSignal, observableValue, transaction } from 'vs/base/common/observable'; import { UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo'; @@ -24,7 +24,7 @@ export class TextModelDiffs extends Disposable { private _isDisposed = false; public get isApplyingChange() { - return this._barrier.isActive; + return this._barrier.isOccupied; } constructor( @@ -44,14 +44,14 @@ export class TextModelDiffs extends Disposable { this._register( baseTextModel.onDidChangeContent( - this._barrier.makeExclusive(() => { + this._barrier.makeExclusiveOrSkip(() => { recomputeSignal.trigger(undefined); }) ) ); this._register( textModel.onDidChangeContent( - this._barrier.makeExclusive(() => { + this._barrier.makeExclusiveOrSkip(() => { recomputeSignal.trigger(undefined); }) ) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts index b43d06358f8..c085272472c 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ArrayQueue, CompareResult } from 'vs/base/common/arrays'; -import { BugIndicatingError, onUnexpectedError } from 'vs/base/common/errors'; +import { onUnexpectedError } from 'vs/base/common/errors'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IObservable, autorunOpts, observableFromEvent } from 'vs/base/common/observable'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; @@ -12,52 +12,6 @@ import { IModelDeltaDecoration } from 'vs/editor/common/model'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -export class ReentrancyBarrier { - private _isActive = false; - - public get isActive() { - return this._isActive; - } - - public makeExclusive(fn: TFunction): TFunction { - return ((...args: any[]) => { - if (this._isActive) { - return; - } - this._isActive = true; - try { - return fn(...args); - } finally { - this._isActive = false; - } - }) as any; - } - - public runExclusively(fn: () => void): void { - if (this._isActive) { - return; - } - this._isActive = true; - try { - fn(); - } finally { - this._isActive = false; - } - } - - public runExclusivelyOrThrow(fn: () => void): void { - if (this._isActive) { - throw new BugIndicatingError(); - } - this._isActive = true; - try { - fn(); - } finally { - this._isActive = false; - } - } -} - export function setStyle( element: HTMLElement, style: { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/scrollSynchronizer.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/scrollSynchronizer.ts index 94857ee4726..79e0e769398 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/scrollSynchronizer.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/scrollSynchronizer.ts @@ -8,7 +8,7 @@ import { autorunWithStore, IObservable } from 'vs/base/common/observable'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { DocumentLineRangeMap } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; -import { ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/utils'; +import { ReentrancyBarrier } from '../../../../../base/common/controlFlow'; import { BaseCodeEditorView } from 'vs/workbench/contrib/mergeEditor/browser/view/editors/baseCodeEditorView'; import { IMergeEditorLayout } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor'; import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/view/viewModel'; @@ -62,7 +62,7 @@ export class ScrollSynchronizer extends Disposable { this._store.add( this.input1View.editor.onDidScrollChange( - this.reentrancyBarrier.makeExclusive((c) => { + this.reentrancyBarrier.makeExclusiveOrSkip((c) => { if (c.scrollTopChanged) { handleInput1OnScroll(); } @@ -77,7 +77,7 @@ export class ScrollSynchronizer extends Disposable { this._store.add( this.input2View.editor.onDidScrollChange( - this.reentrancyBarrier.makeExclusive((c) => { + this.reentrancyBarrier.makeExclusiveOrSkip((c) => { if (!this.model) { return; } @@ -112,7 +112,7 @@ export class ScrollSynchronizer extends Disposable { ); this._store.add( this.inputResultView.editor.onDidScrollChange( - this.reentrancyBarrier.makeExclusive((c) => { + this.reentrancyBarrier.makeExclusiveOrSkip((c) => { if (c.scrollTopChanged) { if (this.shouldAlignResult) { this.input1View.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); @@ -146,7 +146,7 @@ export class ScrollSynchronizer extends Disposable { const baseView = this.baseView.read(reader); if (baseView) { store.add(baseView.editor.onDidScrollChange( - this.reentrancyBarrier.makeExclusive((c) => { + this.reentrancyBarrier.makeExclusiveOrSkip((c) => { if (c.scrollTopChanged) { if (!this.model) { return; diff --git a/src/vs/workbench/contrib/scrollLocking/browser/scrollLocking.ts b/src/vs/workbench/contrib/scrollLocking/browser/scrollLocking.ts index 418197bc66c..17b3027251a 100644 --- a/src/vs/workbench/contrib/scrollLocking/browser/scrollLocking.ts +++ b/src/vs/workbench/contrib/scrollLocking/browser/scrollLocking.ts @@ -12,7 +12,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IEditorPane, IEditorPaneScrollPosition, isEditorPaneWithScrolling } from 'vs/workbench/common/editor'; -import { ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/utils'; +import { ReentrancyBarrier } from 'vs/base/common/controlFlow'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar'; @@ -75,7 +75,7 @@ export class SyncScroll extends Disposable implements IWorkbenchContribution { this.paneInitialScrollTop.set(pane, pane.getScrollPosition()); this.paneDisposables.add(pane.onDidChangeScroll(() => - this._reentrancyBarrier.runExclusively(() => { + this._reentrancyBarrier.runExclusivelyOrSkip(() => { this.onDidEditorPaneScroll(pane); }) ));