Path: blob/main/src/vs/editor/common/viewModelEventDispatcher.ts
3292 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { ViewEventHandler } from './viewEventHandler.js';6import { ViewEvent } from './viewEvents.js';7import { IContentSizeChangedEvent } from './editorCommon.js';8import { Emitter } from '../../base/common/event.js';9import { Selection } from './core/selection.js';10import { Disposable } from '../../base/common/lifecycle.js';11import { CursorChangeReason } from './cursorEvents.js';12import { ModelLineHeightChangedEvent as OriginalModelLineHeightChangedEvent, ModelFontChangedEvent as OriginalModelFontChangedEvent, IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent, IModelTokensChangedEvent } from './textModelEvents.js';1314export class ViewModelEventDispatcher extends Disposable {1516private readonly _onEvent = this._register(new Emitter<OutgoingViewModelEvent>());17public readonly onEvent = this._onEvent.event;1819private readonly _eventHandlers: ViewEventHandler[];20private _viewEventQueue: ViewEvent[] | null;21private _isConsumingViewEventQueue: boolean;22private _collector: ViewModelEventsCollector | null;23private _collectorCnt: number;24private _outgoingEvents: OutgoingViewModelEvent[];2526constructor() {27super();28this._eventHandlers = [];29this._viewEventQueue = null;30this._isConsumingViewEventQueue = false;31this._collector = null;32this._collectorCnt = 0;33this._outgoingEvents = [];34}3536public emitOutgoingEvent(e: OutgoingViewModelEvent): void {37this._addOutgoingEvent(e);38this._emitOutgoingEvents();39}4041private _addOutgoingEvent(e: OutgoingViewModelEvent): void {42for (let i = 0, len = this._outgoingEvents.length; i < len; i++) {43const mergeResult = (this._outgoingEvents[i].kind === e.kind ? this._outgoingEvents[i].attemptToMerge(e) : null);44if (mergeResult) {45this._outgoingEvents[i] = mergeResult;46return;47}48}49// not merged50this._outgoingEvents.push(e);51}5253private _emitOutgoingEvents(): void {54while (this._outgoingEvents.length > 0) {55if (this._collector || this._isConsumingViewEventQueue) {56// right now collecting or emitting view events, so let's postpone emitting57return;58}59const event = this._outgoingEvents.shift()!;60if (event.isNoOp()) {61continue;62}63this._onEvent.fire(event);64}65}6667public addViewEventHandler(eventHandler: ViewEventHandler): void {68for (let i = 0, len = this._eventHandlers.length; i < len; i++) {69if (this._eventHandlers[i] === eventHandler) {70console.warn('Detected duplicate listener in ViewEventDispatcher', eventHandler);71}72}73this._eventHandlers.push(eventHandler);74}7576public removeViewEventHandler(eventHandler: ViewEventHandler): void {77for (let i = 0; i < this._eventHandlers.length; i++) {78if (this._eventHandlers[i] === eventHandler) {79this._eventHandlers.splice(i, 1);80break;81}82}83}8485public beginEmitViewEvents(): ViewModelEventsCollector {86this._collectorCnt++;87if (this._collectorCnt === 1) {88this._collector = new ViewModelEventsCollector();89}90return this._collector!;91}9293public endEmitViewEvents(): void {94this._collectorCnt--;95if (this._collectorCnt === 0) {96const outgoingEvents = this._collector!.outgoingEvents;97const viewEvents = this._collector!.viewEvents;98this._collector = null;99100for (const outgoingEvent of outgoingEvents) {101this._addOutgoingEvent(outgoingEvent);102}103104if (viewEvents.length > 0) {105this._emitMany(viewEvents);106}107}108this._emitOutgoingEvents();109}110111public emitSingleViewEvent(event: ViewEvent): void {112try {113const eventsCollector = this.beginEmitViewEvents();114eventsCollector.emitViewEvent(event);115} finally {116this.endEmitViewEvents();117}118}119120private _emitMany(events: ViewEvent[]): void {121if (this._viewEventQueue) {122this._viewEventQueue = this._viewEventQueue.concat(events);123} else {124this._viewEventQueue = events;125}126127if (!this._isConsumingViewEventQueue) {128this._consumeViewEventQueue();129}130}131132private _consumeViewEventQueue(): void {133try {134this._isConsumingViewEventQueue = true;135this._doConsumeQueue();136} finally {137this._isConsumingViewEventQueue = false;138}139}140141private _doConsumeQueue(): void {142while (this._viewEventQueue) {143// Empty event queue, as events might come in while sending these off144const events = this._viewEventQueue;145this._viewEventQueue = null;146147// Use a clone of the event handlers list, as they might remove themselves148const eventHandlers = this._eventHandlers.slice(0);149for (const eventHandler of eventHandlers) {150eventHandler.handleEvents(events);151}152}153}154}155156export class ViewModelEventsCollector {157158public readonly viewEvents: ViewEvent[];159public readonly outgoingEvents: OutgoingViewModelEvent[];160161constructor() {162this.viewEvents = [];163this.outgoingEvents = [];164}165166public emitViewEvent(event: ViewEvent) {167this.viewEvents.push(event);168}169170public emitOutgoingEvent(e: OutgoingViewModelEvent): void {171this.outgoingEvents.push(e);172}173}174175export type OutgoingViewModelEvent = (176ContentSizeChangedEvent177| FocusChangedEvent178| WidgetFocusChangedEvent179| ScrollChangedEvent180| ViewZonesChangedEvent181| HiddenAreasChangedEvent182| ReadOnlyEditAttemptEvent183| CursorStateChangedEvent184| ModelDecorationsChangedEvent185| ModelLanguageChangedEvent186| ModelLanguageConfigurationChangedEvent187| ModelContentChangedEvent188| ModelOptionsChangedEvent189| ModelTokensChangedEvent190| ModelLineHeightChangedEvent191| ModelFontChangedEvent192);193194export const enum OutgoingViewModelEventKind {195ContentSizeChanged,196FocusChanged,197WidgetFocusChanged,198ScrollChanged,199ViewZonesChanged,200HiddenAreasChanged,201ReadOnlyEditAttempt,202CursorStateChanged,203ModelDecorationsChanged,204ModelLanguageChanged,205ModelLanguageConfigurationChanged,206ModelContentChanged,207ModelOptionsChanged,208ModelTokensChanged,209ModelLineHeightChanged,210ModelFontChangedEvent211}212213export class ContentSizeChangedEvent implements IContentSizeChangedEvent {214215public readonly kind = OutgoingViewModelEventKind.ContentSizeChanged;216217private readonly _oldContentWidth: number;218private readonly _oldContentHeight: number;219220readonly contentWidth: number;221readonly contentHeight: number;222readonly contentWidthChanged: boolean;223readonly contentHeightChanged: boolean;224225constructor(oldContentWidth: number, oldContentHeight: number, contentWidth: number, contentHeight: number) {226this._oldContentWidth = oldContentWidth;227this._oldContentHeight = oldContentHeight;228this.contentWidth = contentWidth;229this.contentHeight = contentHeight;230this.contentWidthChanged = (this._oldContentWidth !== this.contentWidth);231this.contentHeightChanged = (this._oldContentHeight !== this.contentHeight);232}233234public isNoOp(): boolean {235return (!this.contentWidthChanged && !this.contentHeightChanged);236}237238public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {239if (other.kind !== this.kind) {240return null;241}242return new ContentSizeChangedEvent(this._oldContentWidth, this._oldContentHeight, other.contentWidth, other.contentHeight);243}244}245246export class FocusChangedEvent {247248public readonly kind = OutgoingViewModelEventKind.FocusChanged;249250readonly oldHasFocus: boolean;251readonly hasFocus: boolean;252253constructor(oldHasFocus: boolean, hasFocus: boolean) {254this.oldHasFocus = oldHasFocus;255this.hasFocus = hasFocus;256}257258public isNoOp(): boolean {259return (this.oldHasFocus === this.hasFocus);260}261262public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {263if (other.kind !== this.kind) {264return null;265}266return new FocusChangedEvent(this.oldHasFocus, other.hasFocus);267}268}269270export class WidgetFocusChangedEvent {271272public readonly kind = OutgoingViewModelEventKind.WidgetFocusChanged;273274readonly oldHasFocus: boolean;275readonly hasFocus: boolean;276277constructor(oldHasFocus: boolean, hasFocus: boolean) {278this.oldHasFocus = oldHasFocus;279this.hasFocus = hasFocus;280}281282public isNoOp(): boolean {283return (this.oldHasFocus === this.hasFocus);284}285286public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {287if (other.kind !== this.kind) {288return null;289}290return new FocusChangedEvent(this.oldHasFocus, other.hasFocus);291}292}293294export class ScrollChangedEvent {295296public readonly kind = OutgoingViewModelEventKind.ScrollChanged;297298private readonly _oldScrollWidth: number;299private readonly _oldScrollLeft: number;300private readonly _oldScrollHeight: number;301private readonly _oldScrollTop: number;302303public readonly scrollWidth: number;304public readonly scrollLeft: number;305public readonly scrollHeight: number;306public readonly scrollTop: number;307308public readonly scrollWidthChanged: boolean;309public readonly scrollLeftChanged: boolean;310public readonly scrollHeightChanged: boolean;311public readonly scrollTopChanged: boolean;312313constructor(314oldScrollWidth: number, oldScrollLeft: number, oldScrollHeight: number, oldScrollTop: number,315scrollWidth: number, scrollLeft: number, scrollHeight: number, scrollTop: number,316) {317this._oldScrollWidth = oldScrollWidth;318this._oldScrollLeft = oldScrollLeft;319this._oldScrollHeight = oldScrollHeight;320this._oldScrollTop = oldScrollTop;321322this.scrollWidth = scrollWidth;323this.scrollLeft = scrollLeft;324this.scrollHeight = scrollHeight;325this.scrollTop = scrollTop;326327this.scrollWidthChanged = (this._oldScrollWidth !== this.scrollWidth);328this.scrollLeftChanged = (this._oldScrollLeft !== this.scrollLeft);329this.scrollHeightChanged = (this._oldScrollHeight !== this.scrollHeight);330this.scrollTopChanged = (this._oldScrollTop !== this.scrollTop);331}332333public isNoOp(): boolean {334return (!this.scrollWidthChanged && !this.scrollLeftChanged && !this.scrollHeightChanged && !this.scrollTopChanged);335}336337public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {338if (other.kind !== this.kind) {339return null;340}341return new ScrollChangedEvent(342this._oldScrollWidth, this._oldScrollLeft, this._oldScrollHeight, this._oldScrollTop,343other.scrollWidth, other.scrollLeft, other.scrollHeight, other.scrollTop344);345}346}347348export class ViewZonesChangedEvent {349350public readonly kind = OutgoingViewModelEventKind.ViewZonesChanged;351352constructor() {353}354355public isNoOp(): boolean {356return false;357}358359public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {360if (other.kind !== this.kind) {361return null;362}363return this;364}365}366367export class HiddenAreasChangedEvent {368369public readonly kind = OutgoingViewModelEventKind.HiddenAreasChanged;370371constructor() {372}373374public isNoOp(): boolean {375return false;376}377378public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {379if (other.kind !== this.kind) {380return null;381}382return this;383}384}385386export class CursorStateChangedEvent {387388public readonly kind = OutgoingViewModelEventKind.CursorStateChanged;389390public readonly oldSelections: Selection[] | null;391public readonly selections: Selection[];392public readonly oldModelVersionId: number;393public readonly modelVersionId: number;394public readonly source: string;395public readonly reason: CursorChangeReason;396public readonly reachedMaxCursorCount: boolean;397398constructor(oldSelections: Selection[] | null, selections: Selection[], oldModelVersionId: number, modelVersionId: number, source: string, reason: CursorChangeReason, reachedMaxCursorCount: boolean) {399this.oldSelections = oldSelections;400this.selections = selections;401this.oldModelVersionId = oldModelVersionId;402this.modelVersionId = modelVersionId;403this.source = source;404this.reason = reason;405this.reachedMaxCursorCount = reachedMaxCursorCount;406}407408private static _selectionsAreEqual(a: Selection[] | null, b: Selection[] | null): boolean {409if (!a && !b) {410return true;411}412if (!a || !b) {413return false;414}415const aLen = a.length;416const bLen = b.length;417if (aLen !== bLen) {418return false;419}420for (let i = 0; i < aLen; i++) {421if (!a[i].equalsSelection(b[i])) {422return false;423}424}425return true;426}427428public isNoOp(): boolean {429return (430CursorStateChangedEvent._selectionsAreEqual(this.oldSelections, this.selections)431&& this.oldModelVersionId === this.modelVersionId432);433}434435public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {436if (other.kind !== this.kind) {437return null;438}439return new CursorStateChangedEvent(440this.oldSelections, other.selections, this.oldModelVersionId, other.modelVersionId, other.source, other.reason, this.reachedMaxCursorCount || other.reachedMaxCursorCount441);442}443}444445export class ReadOnlyEditAttemptEvent {446447public readonly kind = OutgoingViewModelEventKind.ReadOnlyEditAttempt;448449constructor() {450}451452public isNoOp(): boolean {453return false;454}455456public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {457if (other.kind !== this.kind) {458return null;459}460return this;461}462}463464export class ModelDecorationsChangedEvent {465public readonly kind = OutgoingViewModelEventKind.ModelDecorationsChanged;466467constructor(468public readonly event: IModelDecorationsChangedEvent469) { }470471public isNoOp(): boolean {472return false;473}474475public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {476return null;477}478}479480export class ModelLanguageChangedEvent {481public readonly kind = OutgoingViewModelEventKind.ModelLanguageChanged;482483constructor(484public readonly event: IModelLanguageChangedEvent485) { }486487public isNoOp(): boolean {488return false;489}490491public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {492return null;493}494}495496export class ModelLanguageConfigurationChangedEvent {497public readonly kind = OutgoingViewModelEventKind.ModelLanguageConfigurationChanged;498499constructor(500public readonly event: IModelLanguageConfigurationChangedEvent501) { }502503public isNoOp(): boolean {504return false;505}506507public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {508return null;509}510}511512export class ModelContentChangedEvent {513public readonly kind = OutgoingViewModelEventKind.ModelContentChanged;514515constructor(516public readonly event: IModelContentChangedEvent517) { }518519public isNoOp(): boolean {520return false;521}522523public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {524return null;525}526}527528export class ModelOptionsChangedEvent {529public readonly kind = OutgoingViewModelEventKind.ModelOptionsChanged;530531constructor(532public readonly event: IModelOptionsChangedEvent533) { }534535public isNoOp(): boolean {536return false;537}538539public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {540return null;541}542}543544export class ModelTokensChangedEvent {545public readonly kind = OutgoingViewModelEventKind.ModelTokensChanged;546547constructor(548public readonly event: IModelTokensChangedEvent549) { }550551public isNoOp(): boolean {552return false;553}554555public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {556return null;557}558}559560export class ModelLineHeightChangedEvent {561public readonly kind = OutgoingViewModelEventKind.ModelLineHeightChanged;562563constructor(564public readonly event: OriginalModelLineHeightChangedEvent565) { }566567public isNoOp(): boolean {568return false;569}570571public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {572return null;573}574}575576export class ModelFontChangedEvent {577public readonly kind = OutgoingViewModelEventKind.ModelFontChangedEvent;578579constructor(580public readonly event: OriginalModelFontChangedEvent581) { }582583public isNoOp(): boolean {584return false;585}586587public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {588return null;589}590}591592593