diff --git a/src/vs/base/common/eventEmitter.ts b/src/vs/base/common/eventEmitter.ts index d7198968f41..60ac5b9ebe3 100644 --- a/src/vs/base/common/eventEmitter.ts +++ b/src/vs/base/common/eventEmitter.ts @@ -22,41 +22,26 @@ export interface ListenerCallback { (value: any): void; } -export interface IEventEmitter extends IDisposable { - addListener(eventType: string, listener: ListenerCallback): IDisposable; - addOneTimeListener(eventType: string, listener: ListenerCallback): IDisposable; -} - export interface IListenersMap { [key: string]: ListenerCallback[]; } -export class EventEmitter implements IEventEmitter { +export class EventEmitter implements IDisposable { protected _listeners: IListenersMap; private _collectedEvents: EmitterEvent[]; private _deferredCnt: number; - private _allowedEventTypes: { [eventType: string]: boolean; }; - constructor(allowedEventTypes: string[] = null) { + constructor() { this._listeners = {}; this._collectedEvents = []; this._deferredCnt = 0; - if (allowedEventTypes) { - this._allowedEventTypes = {}; - for (let i = 0; i < allowedEventTypes.length; i++) { - this._allowedEventTypes[allowedEventTypes[i]] = true; - } - } else { - this._allowedEventTypes = null; - } } public dispose(): void { this._listeners = {}; this._collectedEvents = []; this._deferredCnt = 0; - this._allowedEventTypes = null; } public addListener(eventType: string, listener: ListenerCallback): IDisposable { @@ -64,10 +49,6 @@ export class EventEmitter implements IEventEmitter { throw new Error('Use addBulkListener(listener) to register your listener!'); } - if (this._allowedEventTypes && !this._allowedEventTypes.hasOwnProperty(eventType)) { - throw new Error('This object will never emit this event type!'); - } - if (this._listeners.hasOwnProperty(eventType)) { this._listeners[eventType].push(listener); } else { @@ -130,9 +111,6 @@ export class EventEmitter implements IEventEmitter { } public emit(eventType: string, data: any = {}): void { - if (this._allowedEventTypes && !this._allowedEventTypes.hasOwnProperty(eventType)) { - throw new Error('Cannot emit this event type because it wasn\'t listed!'); - } // Early return if no listeners would get this if (!this._listeners.hasOwnProperty(eventType)) { return; @@ -159,16 +137,6 @@ export class EventEmitter implements IEventEmitter { } } - public deferredEmit(callback: () => T): T { - this.beginDeferredEmit(); - - let result: T = safeInvokeNoArg(callback); - - this.endDeferredEmit(); - - return result; - } - private _emitCollected(): void { if (this._collectedEvents.length === 0) { return; @@ -198,7 +166,7 @@ export class OrderGuaranteeEventEmitter extends EventEmitter { private _emitQueue: EmitQueueElement[]; constructor() { - super(null); + super(); this._emitQueue = []; } @@ -221,15 +189,6 @@ export class OrderGuaranteeEventEmitter extends EventEmitter { } } -function safeInvokeNoArg(func: Function): T { - try { - return func(); - } catch (e) { - Errors.onUnexpectedError(e); - } - return undefined; -} - function safeInvoke1Arg(func: Function, arg1: any): any { try { return func(arg1); diff --git a/src/vs/editor/common/commonCodeEditor.ts b/src/vs/editor/common/commonCodeEditor.ts index eaa01e1d459..35d93bc40f2 100644 --- a/src/vs/editor/common/commonCodeEditor.ts +++ b/src/vs/editor/common/commonCodeEditor.ts @@ -20,10 +20,7 @@ import { Selection, ISelection } from 'vs/editor/common/core/selection'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { hash } from 'vs/base/common/hash'; -import { - IModelContentChangedEvent, IModelDecorationsChangedEvent, - IModelLanguageChangedEvent, IModelOptionsChangedEvent, IModelLanguageConfigurationChangedEvent -} from 'vs/editor/common/model/textModelEvents'; +import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelOptionsChangedEvent, IModelLanguageConfigurationChangedEvent } from 'vs/editor/common/model/textModelEvents'; import * as editorOptions from 'vs/editor/common/config/editorOptions'; import { ICursorPositionChangedEvent, ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; diff --git a/src/vs/editor/common/model/editableTextModel.ts b/src/vs/editor/common/model/editableTextModel.ts index b5c7d58cee2..3008c7e8778 100644 --- a/src/vs/editor/common/model/editableTextModel.ts +++ b/src/vs/editor/common/model/editableTextModel.ts @@ -16,7 +16,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { LanguageIdentifier } from 'vs/editor/common/modes'; import { ITextSource, IRawTextSource, RawTextSource } from 'vs/editor/common/model/textSource'; import { TextModel } from 'vs/editor/common/model/textModel'; -import * as textModelEvents from 'vs/editor/common/model/textModelEvents'; +import { ModelRawContentChangedEvent, TextModelEventType, IModelContentChangedEvent, ModelRawChange, IModelContentChange, ModelRawLineChanged, ModelRawLinesDeleted, ModelRawLinesInserted } from 'vs/editor/common/model/textModelEvents'; export interface IValidatedEditOperation { sortIndex: number; @@ -39,11 +39,11 @@ export class EditableTextModel extends TextModelWithDecorations implements edito return new EditableTextModel(RawTextSource.fromString(text), options, languageIdentifier); } - public onDidChangeRawContent(listener: (e: textModelEvents.ModelRawContentChangedEvent) => void): IDisposable { - return this._eventEmitter.addListener(textModelEvents.TextModelEventType.ModelRawContentChanged2, listener); + public onDidChangeRawContent(listener: (e: ModelRawContentChangedEvent) => void): IDisposable { + return this._eventEmitter.addListener(TextModelEventType.ModelRawContentChanged2, listener); } - public onDidChangeContent(listener: (e: textModelEvents.IModelContentChangedEvent) => void): IDisposable { - return this._eventEmitter.addListener(textModelEvents.TextModelEventType.ModelContentChanged, listener); + public onDidChangeContent(listener: (e: IModelContentChangedEvent) => void): IDisposable { + return this._eventEmitter.addListener(TextModelEventType.ModelContentChanged, listener); } private _commandManager: EditStack; @@ -93,8 +93,10 @@ export class EditableTextModel extends TextModelWithDecorations implements edito public pushEditOperations(beforeCursorState: Selection[], editOperations: editorCommon.IIdentifiedSingleEditOperation[], cursorStateComputer: editorCommon.ICursorStateComputer): Selection[] { try { this._eventEmitter.beginDeferredEmit(); + this._onDidChangeDecorations.beginDeferredEmit(); return this._pushEditOperations(beforeCursorState, editOperations, cursorStateComputer); } finally { + this._onDidChangeDecorations.endDeferredEmit(); this._eventEmitter.endDeferredEmit(); } } @@ -276,10 +278,10 @@ export class EditableTextModel extends TextModelWithDecorations implements edito public applyEdits(rawOperations: editorCommon.IIdentifiedSingleEditOperation[]): editorCommon.IIdentifiedSingleEditOperation[] { try { this._eventEmitter.beginDeferredEmit(); - this._acquireDecorationsTracker(); + this._onDidChangeDecorations.beginDeferredEmit(); return this._applyEdits(rawOperations); } finally { - this._releaseDecorationsTracker(); + this._onDidChangeDecorations.endDeferredEmit(); this._eventEmitter.endDeferredEmit(); } } @@ -472,8 +474,8 @@ export class EditableTextModel extends TextModelWithDecorations implements edito // Sort operations descending operations.sort(EditableTextModel._sortOpsDescending); - let rawContentChanges: textModelEvents.ModelRawChange[] = []; - let contentChanges: textModelEvents.IModelContentChange[] = []; + let rawContentChanges: ModelRawChange[] = []; + let contentChanges: IModelContentChange[] = []; let lineEditsQueue: IIdentifiedLineEdit[] = []; const queueLineEdit = (lineEdit: IIdentifiedLineEdit) => { @@ -506,7 +508,7 @@ export class EditableTextModel extends TextModelWithDecorations implements edito this._lines[currentLineNumber - 1].applyEdits(lineEditsQueue.slice(currentLineNumberStart, i)); this._lineStarts.changeValue(currentLineNumber - 1, this._lines[currentLineNumber - 1].text.length + this._EOL.length); rawContentChanges.push( - new textModelEvents.ModelRawLineChanged(currentLineNumber, this._lines[currentLineNumber - 1].text) + new ModelRawLineChanged(currentLineNumber, this._lines[currentLineNumber - 1].text) ); currentLineNumber = lineNumber; @@ -517,7 +519,7 @@ export class EditableTextModel extends TextModelWithDecorations implements edito this._lines[currentLineNumber - 1].applyEdits(lineEditsQueue.slice(currentLineNumberStart, lineEditsQueue.length)); this._lineStarts.changeValue(currentLineNumber - 1, this._lines[currentLineNumber - 1].text.length + this._EOL.length); rawContentChanges.push( - new textModelEvents.ModelRawLineChanged(currentLineNumber, this._lines[currentLineNumber - 1].text) + new ModelRawLineChanged(currentLineNumber, this._lines[currentLineNumber - 1].text) ); lineEditsQueue = []; @@ -580,11 +582,11 @@ export class EditableTextModel extends TextModelWithDecorations implements edito this._lineStarts.changeValue(spliceStartLineNumber - 1, this._lines[spliceStartLineNumber - 1].text.length + this._EOL.length); rawContentChanges.push( - new textModelEvents.ModelRawLineChanged(spliceStartLineNumber, this._lines[spliceStartLineNumber - 1].text) + new ModelRawLineChanged(spliceStartLineNumber, this._lines[spliceStartLineNumber - 1].text) ); rawContentChanges.push( - new textModelEvents.ModelRawLinesDeleted(spliceStartLineNumber + 1, spliceStartLineNumber + spliceCnt) + new ModelRawLinesDeleted(spliceStartLineNumber + 1, spliceStartLineNumber + spliceCnt) ); } @@ -604,7 +606,7 @@ export class EditableTextModel extends TextModelWithDecorations implements edito let leftoverLine = this._lines[spliceLineNumber - 1].split(spliceColumn); this._lineStarts.changeValue(spliceLineNumber - 1, this._lines[spliceLineNumber - 1].text.length + this._EOL.length); rawContentChanges.push( - new textModelEvents.ModelRawLineChanged(spliceLineNumber, this._lines[spliceLineNumber - 1].text) + new ModelRawLineChanged(spliceLineNumber, this._lines[spliceLineNumber - 1].text) ); this._invalidateLine(spliceLineNumber - 1); @@ -625,7 +627,7 @@ export class EditableTextModel extends TextModelWithDecorations implements edito this._lines[startLineNumber + insertingLinesCnt - 1].append(leftoverLine); this._lineStarts.changeValue(startLineNumber + insertingLinesCnt - 1, this._lines[startLineNumber + insertingLinesCnt - 1].text.length + this._EOL.length); rawContentChanges.push( - new textModelEvents.ModelRawLinesInserted(spliceLineNumber + 1, startLineNumber + insertingLinesCnt, newLinesContent.join('\n')) + new ModelRawLinesInserted(spliceLineNumber + 1, startLineNumber + insertingLinesCnt, newLinesContent.join('\n')) ); } @@ -647,14 +649,14 @@ export class EditableTextModel extends TextModelWithDecorations implements edito if (rawContentChanges.length !== 0 || contentChanges.length !== 0) { this._increaseVersionId(); - this._emitModelRawContentChangedEvent(new textModelEvents.ModelRawContentChangedEvent( + this._emitModelRawContentChangedEvent(new ModelRawContentChangedEvent( rawContentChanges, this.getVersionId(), this._isUndoing, this._isRedoing )); - const e: textModelEvents.IModelContentChangedEvent = { + const e: IModelContentChangedEvent = { changes: contentChanges, eol: this._EOL, versionId: this.getVersionId(), @@ -662,7 +664,7 @@ export class EditableTextModel extends TextModelWithDecorations implements edito isRedoing: this._isRedoing, isFlush: false }; - this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelContentChanged, e); + this._eventEmitter.emit(TextModelEventType.ModelContentChanged, e); } } @@ -683,10 +685,10 @@ export class EditableTextModel extends TextModelWithDecorations implements edito public undo(): Selection[] { try { this._eventEmitter.beginDeferredEmit(); - this._acquireDecorationsTracker(); + this._onDidChangeDecorations.beginDeferredEmit(); return this._undo(); } finally { - this._releaseDecorationsTracker(); + this._onDidChangeDecorations.endDeferredEmit(); this._eventEmitter.endDeferredEmit(); } } @@ -708,10 +710,10 @@ export class EditableTextModel extends TextModelWithDecorations implements edito public redo(): Selection[] { try { this._eventEmitter.beginDeferredEmit(); - this._acquireDecorationsTracker(); + this._onDidChangeDecorations.beginDeferredEmit(); return this._redo(); } finally { - this._releaseDecorationsTracker(); + this._onDidChangeDecorations.endDeferredEmit(); this._eventEmitter.endDeferredEmit(); } } diff --git a/src/vs/editor/common/model/model.ts b/src/vs/editor/common/model/model.ts index 9aa42b70992..c562e49f7fe 100644 --- a/src/vs/editor/common/model/model.ts +++ b/src/vs/editor/common/model/model.ts @@ -5,15 +5,12 @@ 'use strict'; import URI from 'vs/base/common/uri'; -import { - IModel, ITextModelCreationOptions -} from 'vs/editor/common/editorCommon'; +import Event, { Emitter } from 'vs/base/common/event'; +import { IModel, ITextModelCreationOptions } from 'vs/editor/common/editorCommon'; import { EditableTextModel } from 'vs/editor/common/model/editableTextModel'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { IDisposable } from 'vs/base/common/lifecycle'; import { LanguageIdentifier } from 'vs/editor/common/modes'; import { IRawTextSource, RawTextSource } from 'vs/editor/common/model/textSource'; -import * as textModelEvents from 'vs/editor/common/model/textModelEvents'; // The hierarchy is: // Model -> EditableTextModel -> TextModelWithDecorations -> TextModelWithTokens -> TextModel @@ -22,24 +19,9 @@ var MODEL_ID = 0; export class Model extends EditableTextModel implements IModel { - public onDidChangeDecorations(listener: (e: textModelEvents.IModelDecorationsChangedEvent) => void): IDisposable { - return this._eventEmitter.addListener(textModelEvents.TextModelEventType.ModelDecorationsChanged, listener); - } - public onDidChangeOptions(listener: (e: textModelEvents.IModelOptionsChangedEvent) => void): IDisposable { - return this._eventEmitter.addListener(textModelEvents.TextModelEventType.ModelOptionsChanged, listener); - } - public onWillDispose(listener: () => void): IDisposable { - return this._eventEmitter.addListener(textModelEvents.TextModelEventType.ModelDispose, listener); - } - public onDidChangeLanguage(listener: (e: textModelEvents.IModelLanguageChangedEvent) => void): IDisposable { - return this._eventEmitter.addListener(textModelEvents.TextModelEventType.ModelLanguageChanged, listener); - } - public onDidChangeLanguageConfiguration(listener: (e: textModelEvents.IModelLanguageConfigurationChangedEvent) => void): IDisposable { - return this._eventEmitter.addListener(textModelEvents.TextModelEventType.ModelLanguageConfigurationChanged, listener); - } - public onDidChangeTokens(listener: (e: textModelEvents.IModelTokensChangedEvent) => void): IDisposable { - return this._eventEmitter.addListener(textModelEvents.TextModelEventType.ModelTokensChanged, listener); - } + private readonly _onWillDispose: Emitter = this._register(new Emitter()); + public readonly onWillDispose: Event = this._onWillDispose.event; + public static createFromString(text: string, options: ITextModelCreationOptions = TextModel.DEFAULT_CREATION_OPTIONS, languageIdentifier: LanguageIdentifier = null, uri: URI = null): Model { return new Model(RawTextSource.fromString(text), options, languageIdentifier, uri); } @@ -71,7 +53,7 @@ export class Model extends EditableTextModel implements IModel { public dispose(): void { this._isDisposing = true; - this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelDispose); + this._onWillDispose.fire(); super.dispose(); this._isDisposing = false; } diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 141468ed7c2..43245642410 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -6,6 +6,7 @@ import { OrderGuaranteeEventEmitter } from 'vs/base/common/eventEmitter'; import * as strings from 'vs/base/common/strings'; +import Event, { Emitter } from 'vs/base/common/event'; import { Position, IPosition } from 'vs/editor/common/core/position'; import { Range, IRange } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; @@ -16,7 +17,8 @@ import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/config/editorOptions'; import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; import { TextModelSearch, SearchParams } from 'vs/editor/common/model/textModelSearch'; import { TextSource, ITextSource, IRawTextSource, RawTextSource } from 'vs/editor/common/model/textSource'; -import * as textModelEvents from 'vs/editor/common/model/textModelEvents'; +import { TextModelEventType, IModelContentChangedEvent, ModelRawContentChangedEvent, ModelRawFlush, ModelRawEOLChanged, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents'; +import { Disposable } from 'vs/base/common/lifecycle'; const LIMIT_FIND_COUNT = 999; export const LONG_LINE_BOUNDARY = 10000; @@ -26,7 +28,7 @@ export interface ITextModelCreationData { readonly options: editorCommon.TextModelResolvedOptions; } -export class TextModel implements editorCommon.ITextModel { +export class TextModel extends Disposable implements editorCommon.ITextModel { private static MODEL_SYNC_LIMIT = 50 * 1024 * 1024; // 50 MB private static MODEL_TOKENIZATION_LIMIT = 20 * 1024 * 1024; // 20 MB private static MANY_MANY_LINES = 300 * 1000; // 300K lines @@ -70,6 +72,9 @@ export class TextModel implements editorCommon.ITextModel { }; } + private readonly _onDidChangeOptions: Emitter = this._register(new Emitter()); + public readonly onDidChangeOptions: Event = this._onDidChangeOptions.event; + protected readonly _eventEmitter: OrderGuaranteeEventEmitter; /*protected*/ _lines: IModelLine[]; @@ -92,6 +97,7 @@ export class TextModel implements editorCommon.ITextModel { protected readonly _isTooLargeForTokenization: boolean; constructor(rawTextSource: IRawTextSource, creationOptions: editorCommon.ITextModelCreationOptions) { + super(); this._eventEmitter = new OrderGuaranteeEventEmitter(); const textModelData = TextModel.resolveCreationData(rawTextSource, creationOptions); @@ -162,7 +168,7 @@ export class TextModel implements editorCommon.ITextModel { let e = this._options.createChangeEvent(newOpts); this._options = newOpts; - this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelOptionsChanged, e); + this._onDidChangeOptions.fire(e); } public detectIndentation(defaultInsertSpaces: boolean, defaultTabSize: number): void { @@ -292,10 +298,12 @@ export class TextModel implements editorCommon.ITextModel { this._BOM = null; this._eventEmitter.dispose(); + + super.dispose(); } private _emitContentChanged2(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, rangeLength: number, text: string, isUndoing: boolean, isRedoing: boolean, isFlush: boolean): void { - const e: textModelEvents.IModelContentChangedEvent = { + const e: IModelContentChangedEvent = { changes: [{ range: new Range(startLineNumber, startColumn, endLineNumber, endColumn), rangeLength: rangeLength, @@ -308,7 +316,7 @@ export class TextModel implements editorCommon.ITextModel { isFlush: isFlush }; if (!this._isDisposing) { - this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelContentChanged, e); + this._eventEmitter.emit(TextModelEventType.ModelContentChanged, e); } } @@ -360,9 +368,9 @@ export class TextModel implements editorCommon.ITextModel { this._resetValue(newValue); this._emitModelRawContentChangedEvent( - new textModelEvents.ModelRawContentChangedEvent( + new ModelRawContentChangedEvent( [ - new textModelEvents.ModelRawFlush() + new ModelRawFlush() ], this._versionId, false, @@ -515,9 +523,9 @@ export class TextModel implements editorCommon.ITextModel { this._onAfterEOLChange(); this._emitModelRawContentChangedEvent( - new textModelEvents.ModelRawContentChangedEvent( + new ModelRawContentChangedEvent( [ - new textModelEvents.ModelRawEOLChanged() + new ModelRawEOLChanged() ], this._versionId, false, @@ -734,12 +742,12 @@ export class TextModel implements editorCommon.ITextModel { return new Range(1, 1, lineCount, this.getLineMaxColumn(lineCount)); } - protected _emitModelRawContentChangedEvent(e: textModelEvents.ModelRawContentChangedEvent): void { + protected _emitModelRawContentChangedEvent(e: ModelRawContentChangedEvent): void { if (this._isDisposing) { // Do not confuse listeners by emitting any event after disposing return; } - this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelRawContentChanged2, e); + this._eventEmitter.emit(TextModelEventType.ModelRawContentChanged2, e); } private _constructLines(textSource: ITextSource): void { diff --git a/src/vs/editor/common/model/textModelEvents.ts b/src/vs/editor/common/model/textModelEvents.ts index aaa66c7beab..b7bd5a8e535 100644 --- a/src/vs/editor/common/model/textModelEvents.ts +++ b/src/vs/editor/common/model/textModelEvents.ts @@ -11,14 +11,8 @@ import { IRange } from 'vs/editor/common/core/range'; * @internal */ export const TextModelEventType = { - ModelDispose: 'modelDispose', - ModelTokensChanged: 'modelTokensChanged', - ModelLanguageChanged: 'modelLanguageChanged', - ModelOptionsChanged: 'modelOptionsChanged', ModelContentChanged: 'contentChanged', ModelRawContentChanged2: 'rawContentChanged2', - ModelDecorationsChanged: 'decorationsChanged', - ModelLanguageConfigurationChanged: 'modelLanguageConfigurationChanged' }; /** diff --git a/src/vs/editor/common/model/textModelWithDecorations.ts b/src/vs/editor/common/model/textModelWithDecorations.ts index 3497dfaf1ec..c98f12c1ede 100644 --- a/src/vs/editor/common/model/textModelWithDecorations.ts +++ b/src/vs/editor/common/model/textModelWithDecorations.ts @@ -8,14 +8,16 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import * as strings from 'vs/base/common/strings'; import { CharCode } from 'vs/base/common/charCode'; +import Event, { Emitter } from 'vs/base/common/event'; import { Range, IRange } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { TextModelWithTokens } from 'vs/editor/common/model/textModelWithTokens'; import { LanguageIdentifier } from 'vs/editor/common/modes'; import { ITextSource, IRawTextSource } from 'vs/editor/common/model/textSource'; -import * as textModelEvents from 'vs/editor/common/model/textModelEvents'; +import { IModelDecorationsChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; import { IntervalNode, IntervalTree, recomputeMaxEnd, getNodeIsInOverviewRuler } from 'vs/editor/common/model/intervalTree'; +import { Disposable } from 'vs/base/common/lifecycle'; let _INSTANCE_COUNT = 0; /** @@ -36,14 +38,15 @@ function nextInstanceId(): string { export class TextModelWithDecorations extends TextModelWithTokens implements editorCommon.ITextModelWithDecorations { + protected readonly _onDidChangeDecorations: DidChangeDecorationsEmitter = this._register(new DidChangeDecorationsEmitter()); + public readonly onDidChangeDecorations: Event = this._onDidChangeDecorations.event; + /** * Used to workaround broken clients that might attempt using a decoration id generated by a different model. * It is not globally unique in order to limit it to one character. */ private readonly _instanceId: string; private _lastDecorationId: number; - private _currentDecorationsTrackerCnt: number; - private _currentDecorationsTrackerDidChange: boolean; private _decorations: { [decorationId: string]: IntervalNode; }; private _decorationsTree: DecorationsTrees; @@ -52,8 +55,6 @@ export class TextModelWithDecorations extends TextModelWithTokens implements edi this._instanceId = nextInstanceId(); this._lastDecorationId = 0; - this._currentDecorationsTrackerCnt = 0; - this._currentDecorationsTrackerDidChange = false; this._decorations = Object.create(null); this._decorationsTree = new DecorationsTrees(); } @@ -79,24 +80,8 @@ export class TextModelWithDecorations extends TextModelWithTokens implements edi // --- END TrackedRanges - protected _acquireDecorationsTracker(): void { - if (this._currentDecorationsTrackerCnt === 0) { - this._currentDecorationsTrackerDidChange = false; - } - this._currentDecorationsTrackerCnt++; - } - - protected _releaseDecorationsTracker(): void { - this._currentDecorationsTrackerCnt--; - if (this._currentDecorationsTrackerCnt === 0) { - if (this._currentDecorationsTrackerDidChange) { - this._emitModelDecorationsChangedEvent(); - } - } - } - protected _adjustDecorationsForEdit(offset: number, length: number, textLength: number, forceMoveMarkers: boolean): void { - this._currentDecorationsTrackerDidChange = true; + this._onDidChangeDecorations.fire(); this._decorationsTree.acceptReplace(offset, length, textLength, forceMoveMarkers); } @@ -138,31 +123,29 @@ export class TextModelWithDecorations extends TextModelWithTokens implements edi this._assertNotDisposed(); try { - this._eventEmitter.beginDeferredEmit(); - this._acquireDecorationsTracker(); + this._onDidChangeDecorations.beginDeferredEmit(); return this._changeDecorations(ownerId, callback); } finally { - this._releaseDecorationsTracker(); - this._eventEmitter.endDeferredEmit(); + this._onDidChangeDecorations.endDeferredEmit(); } } private _changeDecorations(ownerId: number, callback: (changeAccessor: editorCommon.IModelDecorationsChangeAccessor) => T): T { let changeAccessor: editorCommon.IModelDecorationsChangeAccessor = { addDecoration: (range: IRange, options: editorCommon.IModelDecorationOptions): string => { - this._currentDecorationsTrackerDidChange = true; + this._onDidChangeDecorations.fire(); return this._deltaDecorationsImpl(ownerId, [], [{ range: range, options: options }])[0]; }, changeDecoration: (id: string, newRange: IRange): void => { - this._currentDecorationsTrackerDidChange = true; + this._onDidChangeDecorations.fire(); this._changeDecorationImpl(id, newRange); }, changeDecorationOptions: (id: string, options: editorCommon.IModelDecorationOptions) => { - this._currentDecorationsTrackerDidChange = true; + this._onDidChangeDecorations.fire(); this._changeDecorationOptionsImpl(id, _normalizeOptions(options)); }, removeDecoration: (id: string): void => { - this._currentDecorationsTrackerDidChange = true; + this._onDidChangeDecorations.fire(); this._deltaDecorationsImpl(ownerId, [id], []); }, deltaDecorations: (oldDecorations: string[], newDecorations: editorCommon.IModelDeltaDecoration[]): string[] => { @@ -170,7 +153,7 @@ export class TextModelWithDecorations extends TextModelWithTokens implements edi // nothing to do return []; } - this._currentDecorationsTrackerDidChange = true; + this._onDidChangeDecorations.fire(); return this._deltaDecorationsImpl(ownerId, oldDecorations, newDecorations); } }; @@ -199,13 +182,11 @@ export class TextModelWithDecorations extends TextModelWithTokens implements edi } try { - this._eventEmitter.beginDeferredEmit(); - this._acquireDecorationsTracker(); - this._currentDecorationsTrackerDidChange = true; + this._onDidChangeDecorations.beginDeferredEmit(); + this._onDidChangeDecorations.fire(); return this._deltaDecorationsImpl(ownerId, oldDecorations, newDecorations); } finally { - this._releaseDecorationsTracker(); - this._eventEmitter.endDeferredEmit(); + this._onDidChangeDecorations.endDeferredEmit(); } } @@ -312,13 +293,6 @@ export class TextModelWithDecorations extends TextModelWithTokens implements edi return this._ensureNodesHaveRanges(result); } - private _emitModelDecorationsChangedEvent(): void { - if (!this._isDisposing) { - let e: textModelEvents.IModelDecorationsChangedEvent = {}; - this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelDecorationsChanged, e); - } - } - private _getDecorationsInRange(filterRange: Range, filterOwnerId: number, filterOutValidation: boolean): IntervalNode[] { const startOffset = this._lineStarts.getAccumulatedValue(filterRange.startLineNumber - 2) + filterRange.startColumn - 1; const endOffset = this._lineStarts.getAccumulatedValue(filterRange.endLineNumber - 2) + filterRange.endColumn - 1; @@ -625,3 +599,36 @@ function _normalizeOptions(options: editorCommon.IModelDecorationOptions): Model } return ModelDecorationOptions.createDynamic(options); } + +export class DidChangeDecorationsEmitter extends Disposable { + + private readonly _actual: Emitter = this._register(new Emitter()); + public readonly event: Event = this._actual.event; + + private _deferredCnt: number; + private _shouldFire: boolean; + + constructor() { + super(); + this._deferredCnt = 0; + this._shouldFire = false; + } + + public beginDeferredEmit(): void { + this._deferredCnt++; + } + + public endDeferredEmit(): void { + this._deferredCnt--; + if (this._deferredCnt === 0) { + if (this._shouldFire) { + this._shouldFire = false; + this._actual.fire({}); + } + } + } + + public fire(): void { + this._shouldFire = true; + } +} diff --git a/src/vs/editor/common/model/textModelWithTokens.ts b/src/vs/editor/common/model/textModelWithTokens.ts index 6088aeadf78..a1ad4ea52b4 100644 --- a/src/vs/editor/common/model/textModelWithTokens.ts +++ b/src/vs/editor/common/model/textModelWithTokens.ts @@ -8,6 +8,7 @@ import * as nls from 'vs/nls'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable } from 'vs/base/common/lifecycle'; import { StopWatch } from 'vs/base/common/stopwatch'; +import Event, { Emitter } from 'vs/base/common/event'; import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { TextModel } from 'vs/editor/common/model/textModel'; @@ -21,7 +22,7 @@ import { LineTokens, LineToken } from 'vs/editor/common/core/lineTokens'; import { getWordAtText } from 'vs/editor/common/model/wordHelper'; import { TokenizationResult2 } from 'vs/editor/common/core/token'; import { ITextSource, IRawTextSource } from 'vs/editor/common/model/textSource'; -import * as textModelEvents from 'vs/editor/common/model/textModelEvents'; +import { IModelTokensChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { computeIndentLevel } from 'vs/editor/common/model/modelLine'; class ModelTokensChangedEventBuilder { @@ -49,7 +50,7 @@ class ModelTokensChangedEventBuilder { } } - public build(): textModelEvents.IModelTokensChangedEvent { + public build(): IModelTokensChangedEvent { if (this._ranges.length === 0) { return null; } @@ -63,6 +64,15 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke private static MODE_TOKENIZATION_FAILED_MSG = nls.localize('mode.tokenizationSupportFailed', "The mode has failed while tokenizing the input."); + private readonly _onDidChangeLanguage: Emitter = this._register(new Emitter()); + public readonly onDidChangeLanguage: Event = this._onDidChangeLanguage.event; + + private readonly _onDidChangeLanguageConfiguration: Emitter = this._register(new Emitter()); + public readonly onDidChangeLanguageConfiguration: Event = this._onDidChangeLanguageConfiguration.event; + + private readonly _onDidChangeTokens: Emitter = this._register(new Emitter()); + public readonly onDidChangeTokens: Event = this._onDidChangeTokens.event; + private _languageIdentifier: LanguageIdentifier; private _tokenizationListener: IDisposable; private _tokenizationSupport: ITokenizationSupport; @@ -100,7 +110,7 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke this._languageRegistryListener = LanguageConfigurationRegistry.onDidChange((e) => { if (e.languageIdentifier.id === this._languageIdentifier.id) { - this._emitModelLanguageConfigurationEvent({}); + this._onDidChangeLanguageConfiguration.fire({}); } }); @@ -164,29 +174,19 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke } } - private _withModelTokensChangedEventBuilder(callback: (eventBuilder: ModelTokensChangedEventBuilder) => T): T { - let eventBuilder = new ModelTokensChangedEventBuilder(); - - let result = callback(eventBuilder); - - if (!this._isDisposing) { - let e = eventBuilder.build(); - if (e) { - this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelTokensChanged, e); - } - } - - return result; - } - public forceTokenization(lineNumber: number): void { if (lineNumber < 1 || lineNumber > this.getLineCount()) { throw new Error('Illegal value ' + lineNumber + ' for `lineNumber`'); } - this._withModelTokensChangedEventBuilder((eventBuilder) => { - this._updateTokensUntilLine(eventBuilder, lineNumber); - }); + const eventBuilder = new ModelTokensChangedEventBuilder(); + + this._updateTokensUntilLine(eventBuilder, lineNumber); + + const e = eventBuilder.build(); + if (e) { + this._onDidChangeTokens.fire(e); + } } public isCheapToTokenize(lineNumber: number): boolean { @@ -226,7 +226,7 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke return; } - let e: textModelEvents.IModelLanguageChangedEvent = { + let e: IModelLanguageChangedEvent = { oldLanguage: this._languageIdentifier.language, newLanguage: languageIdentifier.language }; @@ -242,8 +242,8 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke toLineNumber: this.getLineCount() }] }); - this._emitModelModeChangedEvent(e); - this._emitModelLanguageConfigurationEvent({}); + this._onDidChangeLanguage.fire(e); + this._onDidChangeLanguageConfiguration.fire({}); } public getLanguageIdAtPosition(_lineNumber: number, _column: number): LanguageId { @@ -289,56 +289,60 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke private _revalidateTokensNow(toLineNumber: number = this._invalidLineStartIndex + 1000000): void { - this._withModelTokensChangedEventBuilder((eventBuilder) => { + const eventBuilder = new ModelTokensChangedEventBuilder(); - toLineNumber = Math.min(this._lines.length, toLineNumber); + toLineNumber = Math.min(this._lines.length, toLineNumber); - var MAX_ALLOWED_TIME = 20, - fromLineNumber = this._invalidLineStartIndex + 1, - tokenizedChars = 0, - currentCharsToTokenize = 0, - currentEstimatedTimeToTokenize = 0, - sw = StopWatch.create(false), - elapsedTime: number; + var MAX_ALLOWED_TIME = 20, + fromLineNumber = this._invalidLineStartIndex + 1, + tokenizedChars = 0, + currentCharsToTokenize = 0, + currentEstimatedTimeToTokenize = 0, + sw = StopWatch.create(false), + elapsedTime: number; - // Tokenize at most 1000 lines. Estimate the tokenization speed per character and stop when: - // - MAX_ALLOWED_TIME is reached - // - tokenizing the next line would go above MAX_ALLOWED_TIME + // Tokenize at most 1000 lines. Estimate the tokenization speed per character and stop when: + // - MAX_ALLOWED_TIME is reached + // - tokenizing the next line would go above MAX_ALLOWED_TIME - for (var lineNumber = fromLineNumber; lineNumber <= toLineNumber; lineNumber++) { - elapsedTime = sw.elapsed(); - if (elapsedTime > MAX_ALLOWED_TIME) { - // Stop if MAX_ALLOWED_TIME is reached + for (var lineNumber = fromLineNumber; lineNumber <= toLineNumber; lineNumber++) { + elapsedTime = sw.elapsed(); + if (elapsedTime > MAX_ALLOWED_TIME) { + // Stop if MAX_ALLOWED_TIME is reached + toLineNumber = lineNumber - 1; + break; + } + + // Compute how many characters will be tokenized for this line + currentCharsToTokenize = this._lines[lineNumber - 1].text.length; + + if (tokenizedChars > 0) { + // If we have enough history, estimate how long tokenizing this line would take + currentEstimatedTimeToTokenize = (elapsedTime / tokenizedChars) * currentCharsToTokenize; + if (elapsedTime + currentEstimatedTimeToTokenize > MAX_ALLOWED_TIME) { + // Tokenizing this line will go above MAX_ALLOWED_TIME toLineNumber = lineNumber - 1; break; } - - // Compute how many characters will be tokenized for this line - currentCharsToTokenize = this._lines[lineNumber - 1].text.length; - - if (tokenizedChars > 0) { - // If we have enough history, estimate how long tokenizing this line would take - currentEstimatedTimeToTokenize = (elapsedTime / tokenizedChars) * currentCharsToTokenize; - if (elapsedTime + currentEstimatedTimeToTokenize > MAX_ALLOWED_TIME) { - // Tokenizing this line will go above MAX_ALLOWED_TIME - toLineNumber = lineNumber - 1; - break; - } - } - - this._updateTokensUntilLine(eventBuilder, lineNumber); - tokenizedChars += currentCharsToTokenize; - - // Skip the lines that got tokenized - lineNumber = Math.max(lineNumber, this._invalidLineStartIndex + 1); } - elapsedTime = sw.elapsed(); + this._updateTokensUntilLine(eventBuilder, lineNumber); + tokenizedChars += currentCharsToTokenize; - if (this._invalidLineStartIndex < this._lines.length) { - this._beginBackgroundTokenization(); - } - }); + // Skip the lines that got tokenized + lineNumber = Math.max(lineNumber, this._invalidLineStartIndex + 1); + } + + elapsedTime = sw.elapsed(); + + if (this._invalidLineStartIndex < this._lines.length) { + this._beginBackgroundTokenization(); + } + + const e = eventBuilder.build(); + if (e) { + this._onDidChangeTokens.fire(e); + } } private _updateTokensUntilLine(eventBuilder: ModelTokensChangedEventBuilder, lineNumber: number): void { @@ -403,21 +407,9 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke this._invalidLineStartIndex = Math.max(this._invalidLineStartIndex, endLineIndex + 1); } - private emitModelTokensChangedEvent(e: textModelEvents.IModelTokensChangedEvent): void { + private emitModelTokensChangedEvent(e: IModelTokensChangedEvent): void { if (!this._isDisposing) { - this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelTokensChanged, e); - } - } - - private _emitModelLanguageConfigurationEvent(e: textModelEvents.IModelLanguageConfigurationChangedEvent): void { - if (!this._isDisposing) { - this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelLanguageConfigurationChanged, e); - } - } - - private _emitModelModeChangedEvent(e: textModelEvents.IModelLanguageChangedEvent): void { - if (!this._isDisposing) { - this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelLanguageChanged, e); + this._onDidChangeTokens.fire(e); } } diff --git a/src/vs/editor/contrib/find/findState.ts b/src/vs/editor/contrib/find/findState.ts index 6f7efa6d987..0c6533d0374 100644 --- a/src/vs/editor/contrib/find/findState.ts +++ b/src/vs/editor/contrib/find/findState.ts @@ -71,7 +71,6 @@ export class FindReplaceState implements IDisposable { private _matchesCount: number; private _currentMatch: Range; private _onFindReplaceStateChange: Emitter; - // private _eventEmitter: EventEmitter; public get searchString(): string { return this._searchString; } public get replaceString(): string { return this._replaceString; }