From e72d8d15b40f00d4a3751ee333806c94de7237bb Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 28 Mar 2018 15:33:56 +0200 Subject: [PATCH] Fixes #46314: Make sure model change events reach the view models first --- src/vs/editor/common/model.ts | 7 ++++ src/vs/editor/common/model/textModel.ts | 22 ++++++++---- .../editor/common/viewModel/viewModelImpl.ts | 2 +- .../test/browser/controller/cursor.test.ts | 34 ++++++++++++++++++- 4 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index a4b31bd392e..2f2a7433d4b 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -1002,6 +1002,13 @@ export interface ITextModel { */ redo(): Selection[]; + /** + * @deprecated Please use `onDidChangeContent` instead. + * An event emitted when the contents of the model have changed. + * @internal + * @event + */ + onDidChangeRawContentFast(listener: (e: ModelRawContentChangedEvent) => void): IDisposable; /** * @deprecated Please use `onDidChangeContent` instead. * An event emitted when the contents of the model have changed. diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 2fd6a637aa7..ac9a6b5e49b 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -211,11 +211,14 @@ export class TextModel extends Disposable implements model.ITextModel { public readonly onDidChangeOptions: Event = this._onDidChangeOptions.event; private readonly _eventEmitter: DidChangeContentEmitter = this._register(new DidChangeContentEmitter()); + public onDidChangeRawContentFast(listener: (e: ModelRawContentChangedEvent) => void): IDisposable { + return this._eventEmitter.fastEvent((e: InternalModelContentChangeEvent) => listener(e.rawContentChangedEvent)); + } public onDidChangeRawContent(listener: (e: ModelRawContentChangedEvent) => void): IDisposable { - return this._eventEmitter.event((e: InternalModelContentChangeEvent) => listener(e.rawContentChangedEvent)); + return this._eventEmitter.slowEvent((e: InternalModelContentChangeEvent) => listener(e.rawContentChangedEvent)); } public onDidChangeContent(listener: (e: IModelContentChangedEvent) => void): IDisposable { - return this._eventEmitter.event((e: InternalModelContentChangeEvent) => listener(e.contentChangedEvent)); + return this._eventEmitter.slowEvent((e: InternalModelContentChangeEvent) => listener(e.contentChangedEvent)); } //#endregion @@ -2600,8 +2603,13 @@ export class DidChangeDecorationsEmitter extends Disposable { export class DidChangeContentEmitter extends Disposable { - private readonly _actual: Emitter = this._register(new Emitter()); - public readonly event: Event = this._actual.event; + /** + * Both `fastEvent` and `slowEvent` work the same way and contain the same events, but first we invoke `fastEvent` and then `slowEvent`. + */ + private readonly _fastEmitter: Emitter = this._register(new Emitter()); + public readonly fastEvent: Event = this._fastEmitter.event; + private readonly _slowEmitter: Emitter = this._register(new Emitter()); + public readonly slowEvent: Event = this._slowEmitter.event; private _deferredCnt: number; private _deferredEvent: InternalModelContentChangeEvent; @@ -2622,7 +2630,8 @@ export class DidChangeContentEmitter extends Disposable { if (this._deferredEvent !== null) { const e = this._deferredEvent; this._deferredEvent = null; - this._actual.fire(e); + this._fastEmitter.fire(e); + this._slowEmitter.fire(e); } } } @@ -2636,6 +2645,7 @@ export class DidChangeContentEmitter extends Disposable { } return; } - this._actual.fire(e); + this._fastEmitter.fire(e); + this._slowEmitter.fire(e); } } diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index cdde76ac955..6f9f27e704e 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -173,7 +173,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel private _registerModelEvents(): void { - this._register(this.model.onDidChangeRawContent((e) => { + this._register(this.model.onDidChangeRawContentFast((e) => { try { const eventsCollector = this._beginEmit(); diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index 296051b8ef3..f6562068e8a 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -17,11 +17,14 @@ import { IndentAction, IndentationRule } from 'vs/editor/common/modes/languageCo import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; import { MockMode } from 'vs/editor/test/common/mocks/mockMode'; -import { LanguageIdentifier } from 'vs/editor/common/modes'; +import { LanguageIdentifier, ITokenizationSupport, IState, TokenizationRegistry } from 'vs/editor/common/modes'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { CoreNavigationCommands, CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; +import { NULL_STATE } from 'vs/editor/common/modes/nullMode'; +import { TokenizationResult2 } from 'vs/editor/common/core/token'; + let H = Handler; // --------- utils @@ -2048,6 +2051,35 @@ suite('Editor Controller - Regression tests', () => { model.dispose(); }); + + test('issue #46314: ViewModel is out of sync with Model!', () => { + + const tokenizationSupport: ITokenizationSupport = { + getInitialState: () => NULL_STATE, + tokenize: undefined, + tokenize2: (line: string, state: IState): TokenizationResult2 => { + return new TokenizationResult2(null, state); + } + }; + + const LANGUAGE_ID = 'modelModeTest1'; + const languageRegistration = TokenizationRegistry.register(LANGUAGE_ID, tokenizationSupport); + let model = TextModel.createFromString('Just text', undefined, new LanguageIdentifier(LANGUAGE_ID, 0)); + + withTestCodeEditor(null, { model: model }, (editor1, cursor1) => { + withTestCodeEditor(null, { model: model }, (editor2, cursor2) => { + + editor1.onDidChangeCursorPosition(() => { + model.tokenizeIfCheap(1); + }); + + model.applyEdits([{ range: new Range(1, 1, 1, 1), text: '-' }]); + }); + }); + + languageRegistration.dispose(); + model.dispose(); + }); }); suite('Editor Controller - Cursor Configuration', () => { -- GitLab