Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/viewModelEventDispatcher.ts
3292 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { ViewEventHandler } from './viewEventHandler.js';
7
import { ViewEvent } from './viewEvents.js';
8
import { IContentSizeChangedEvent } from './editorCommon.js';
9
import { Emitter } from '../../base/common/event.js';
10
import { Selection } from './core/selection.js';
11
import { Disposable } from '../../base/common/lifecycle.js';
12
import { CursorChangeReason } from './cursorEvents.js';
13
import { ModelLineHeightChangedEvent as OriginalModelLineHeightChangedEvent, ModelFontChangedEvent as OriginalModelFontChangedEvent, IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent, IModelTokensChangedEvent } from './textModelEvents.js';
14
15
export class ViewModelEventDispatcher extends Disposable {
16
17
private readonly _onEvent = this._register(new Emitter<OutgoingViewModelEvent>());
18
public readonly onEvent = this._onEvent.event;
19
20
private readonly _eventHandlers: ViewEventHandler[];
21
private _viewEventQueue: ViewEvent[] | null;
22
private _isConsumingViewEventQueue: boolean;
23
private _collector: ViewModelEventsCollector | null;
24
private _collectorCnt: number;
25
private _outgoingEvents: OutgoingViewModelEvent[];
26
27
constructor() {
28
super();
29
this._eventHandlers = [];
30
this._viewEventQueue = null;
31
this._isConsumingViewEventQueue = false;
32
this._collector = null;
33
this._collectorCnt = 0;
34
this._outgoingEvents = [];
35
}
36
37
public emitOutgoingEvent(e: OutgoingViewModelEvent): void {
38
this._addOutgoingEvent(e);
39
this._emitOutgoingEvents();
40
}
41
42
private _addOutgoingEvent(e: OutgoingViewModelEvent): void {
43
for (let i = 0, len = this._outgoingEvents.length; i < len; i++) {
44
const mergeResult = (this._outgoingEvents[i].kind === e.kind ? this._outgoingEvents[i].attemptToMerge(e) : null);
45
if (mergeResult) {
46
this._outgoingEvents[i] = mergeResult;
47
return;
48
}
49
}
50
// not merged
51
this._outgoingEvents.push(e);
52
}
53
54
private _emitOutgoingEvents(): void {
55
while (this._outgoingEvents.length > 0) {
56
if (this._collector || this._isConsumingViewEventQueue) {
57
// right now collecting or emitting view events, so let's postpone emitting
58
return;
59
}
60
const event = this._outgoingEvents.shift()!;
61
if (event.isNoOp()) {
62
continue;
63
}
64
this._onEvent.fire(event);
65
}
66
}
67
68
public addViewEventHandler(eventHandler: ViewEventHandler): void {
69
for (let i = 0, len = this._eventHandlers.length; i < len; i++) {
70
if (this._eventHandlers[i] === eventHandler) {
71
console.warn('Detected duplicate listener in ViewEventDispatcher', eventHandler);
72
}
73
}
74
this._eventHandlers.push(eventHandler);
75
}
76
77
public removeViewEventHandler(eventHandler: ViewEventHandler): void {
78
for (let i = 0; i < this._eventHandlers.length; i++) {
79
if (this._eventHandlers[i] === eventHandler) {
80
this._eventHandlers.splice(i, 1);
81
break;
82
}
83
}
84
}
85
86
public beginEmitViewEvents(): ViewModelEventsCollector {
87
this._collectorCnt++;
88
if (this._collectorCnt === 1) {
89
this._collector = new ViewModelEventsCollector();
90
}
91
return this._collector!;
92
}
93
94
public endEmitViewEvents(): void {
95
this._collectorCnt--;
96
if (this._collectorCnt === 0) {
97
const outgoingEvents = this._collector!.outgoingEvents;
98
const viewEvents = this._collector!.viewEvents;
99
this._collector = null;
100
101
for (const outgoingEvent of outgoingEvents) {
102
this._addOutgoingEvent(outgoingEvent);
103
}
104
105
if (viewEvents.length > 0) {
106
this._emitMany(viewEvents);
107
}
108
}
109
this._emitOutgoingEvents();
110
}
111
112
public emitSingleViewEvent(event: ViewEvent): void {
113
try {
114
const eventsCollector = this.beginEmitViewEvents();
115
eventsCollector.emitViewEvent(event);
116
} finally {
117
this.endEmitViewEvents();
118
}
119
}
120
121
private _emitMany(events: ViewEvent[]): void {
122
if (this._viewEventQueue) {
123
this._viewEventQueue = this._viewEventQueue.concat(events);
124
} else {
125
this._viewEventQueue = events;
126
}
127
128
if (!this._isConsumingViewEventQueue) {
129
this._consumeViewEventQueue();
130
}
131
}
132
133
private _consumeViewEventQueue(): void {
134
try {
135
this._isConsumingViewEventQueue = true;
136
this._doConsumeQueue();
137
} finally {
138
this._isConsumingViewEventQueue = false;
139
}
140
}
141
142
private _doConsumeQueue(): void {
143
while (this._viewEventQueue) {
144
// Empty event queue, as events might come in while sending these off
145
const events = this._viewEventQueue;
146
this._viewEventQueue = null;
147
148
// Use a clone of the event handlers list, as they might remove themselves
149
const eventHandlers = this._eventHandlers.slice(0);
150
for (const eventHandler of eventHandlers) {
151
eventHandler.handleEvents(events);
152
}
153
}
154
}
155
}
156
157
export class ViewModelEventsCollector {
158
159
public readonly viewEvents: ViewEvent[];
160
public readonly outgoingEvents: OutgoingViewModelEvent[];
161
162
constructor() {
163
this.viewEvents = [];
164
this.outgoingEvents = [];
165
}
166
167
public emitViewEvent(event: ViewEvent) {
168
this.viewEvents.push(event);
169
}
170
171
public emitOutgoingEvent(e: OutgoingViewModelEvent): void {
172
this.outgoingEvents.push(e);
173
}
174
}
175
176
export type OutgoingViewModelEvent = (
177
ContentSizeChangedEvent
178
| FocusChangedEvent
179
| WidgetFocusChangedEvent
180
| ScrollChangedEvent
181
| ViewZonesChangedEvent
182
| HiddenAreasChangedEvent
183
| ReadOnlyEditAttemptEvent
184
| CursorStateChangedEvent
185
| ModelDecorationsChangedEvent
186
| ModelLanguageChangedEvent
187
| ModelLanguageConfigurationChangedEvent
188
| ModelContentChangedEvent
189
| ModelOptionsChangedEvent
190
| ModelTokensChangedEvent
191
| ModelLineHeightChangedEvent
192
| ModelFontChangedEvent
193
);
194
195
export const enum OutgoingViewModelEventKind {
196
ContentSizeChanged,
197
FocusChanged,
198
WidgetFocusChanged,
199
ScrollChanged,
200
ViewZonesChanged,
201
HiddenAreasChanged,
202
ReadOnlyEditAttempt,
203
CursorStateChanged,
204
ModelDecorationsChanged,
205
ModelLanguageChanged,
206
ModelLanguageConfigurationChanged,
207
ModelContentChanged,
208
ModelOptionsChanged,
209
ModelTokensChanged,
210
ModelLineHeightChanged,
211
ModelFontChangedEvent
212
}
213
214
export class ContentSizeChangedEvent implements IContentSizeChangedEvent {
215
216
public readonly kind = OutgoingViewModelEventKind.ContentSizeChanged;
217
218
private readonly _oldContentWidth: number;
219
private readonly _oldContentHeight: number;
220
221
readonly contentWidth: number;
222
readonly contentHeight: number;
223
readonly contentWidthChanged: boolean;
224
readonly contentHeightChanged: boolean;
225
226
constructor(oldContentWidth: number, oldContentHeight: number, contentWidth: number, contentHeight: number) {
227
this._oldContentWidth = oldContentWidth;
228
this._oldContentHeight = oldContentHeight;
229
this.contentWidth = contentWidth;
230
this.contentHeight = contentHeight;
231
this.contentWidthChanged = (this._oldContentWidth !== this.contentWidth);
232
this.contentHeightChanged = (this._oldContentHeight !== this.contentHeight);
233
}
234
235
public isNoOp(): boolean {
236
return (!this.contentWidthChanged && !this.contentHeightChanged);
237
}
238
239
public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {
240
if (other.kind !== this.kind) {
241
return null;
242
}
243
return new ContentSizeChangedEvent(this._oldContentWidth, this._oldContentHeight, other.contentWidth, other.contentHeight);
244
}
245
}
246
247
export class FocusChangedEvent {
248
249
public readonly kind = OutgoingViewModelEventKind.FocusChanged;
250
251
readonly oldHasFocus: boolean;
252
readonly hasFocus: boolean;
253
254
constructor(oldHasFocus: boolean, hasFocus: boolean) {
255
this.oldHasFocus = oldHasFocus;
256
this.hasFocus = hasFocus;
257
}
258
259
public isNoOp(): boolean {
260
return (this.oldHasFocus === this.hasFocus);
261
}
262
263
public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {
264
if (other.kind !== this.kind) {
265
return null;
266
}
267
return new FocusChangedEvent(this.oldHasFocus, other.hasFocus);
268
}
269
}
270
271
export class WidgetFocusChangedEvent {
272
273
public readonly kind = OutgoingViewModelEventKind.WidgetFocusChanged;
274
275
readonly oldHasFocus: boolean;
276
readonly hasFocus: boolean;
277
278
constructor(oldHasFocus: boolean, hasFocus: boolean) {
279
this.oldHasFocus = oldHasFocus;
280
this.hasFocus = hasFocus;
281
}
282
283
public isNoOp(): boolean {
284
return (this.oldHasFocus === this.hasFocus);
285
}
286
287
public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {
288
if (other.kind !== this.kind) {
289
return null;
290
}
291
return new FocusChangedEvent(this.oldHasFocus, other.hasFocus);
292
}
293
}
294
295
export class ScrollChangedEvent {
296
297
public readonly kind = OutgoingViewModelEventKind.ScrollChanged;
298
299
private readonly _oldScrollWidth: number;
300
private readonly _oldScrollLeft: number;
301
private readonly _oldScrollHeight: number;
302
private readonly _oldScrollTop: number;
303
304
public readonly scrollWidth: number;
305
public readonly scrollLeft: number;
306
public readonly scrollHeight: number;
307
public readonly scrollTop: number;
308
309
public readonly scrollWidthChanged: boolean;
310
public readonly scrollLeftChanged: boolean;
311
public readonly scrollHeightChanged: boolean;
312
public readonly scrollTopChanged: boolean;
313
314
constructor(
315
oldScrollWidth: number, oldScrollLeft: number, oldScrollHeight: number, oldScrollTop: number,
316
scrollWidth: number, scrollLeft: number, scrollHeight: number, scrollTop: number,
317
) {
318
this._oldScrollWidth = oldScrollWidth;
319
this._oldScrollLeft = oldScrollLeft;
320
this._oldScrollHeight = oldScrollHeight;
321
this._oldScrollTop = oldScrollTop;
322
323
this.scrollWidth = scrollWidth;
324
this.scrollLeft = scrollLeft;
325
this.scrollHeight = scrollHeight;
326
this.scrollTop = scrollTop;
327
328
this.scrollWidthChanged = (this._oldScrollWidth !== this.scrollWidth);
329
this.scrollLeftChanged = (this._oldScrollLeft !== this.scrollLeft);
330
this.scrollHeightChanged = (this._oldScrollHeight !== this.scrollHeight);
331
this.scrollTopChanged = (this._oldScrollTop !== this.scrollTop);
332
}
333
334
public isNoOp(): boolean {
335
return (!this.scrollWidthChanged && !this.scrollLeftChanged && !this.scrollHeightChanged && !this.scrollTopChanged);
336
}
337
338
public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {
339
if (other.kind !== this.kind) {
340
return null;
341
}
342
return new ScrollChangedEvent(
343
this._oldScrollWidth, this._oldScrollLeft, this._oldScrollHeight, this._oldScrollTop,
344
other.scrollWidth, other.scrollLeft, other.scrollHeight, other.scrollTop
345
);
346
}
347
}
348
349
export class ViewZonesChangedEvent {
350
351
public readonly kind = OutgoingViewModelEventKind.ViewZonesChanged;
352
353
constructor() {
354
}
355
356
public isNoOp(): boolean {
357
return false;
358
}
359
360
public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {
361
if (other.kind !== this.kind) {
362
return null;
363
}
364
return this;
365
}
366
}
367
368
export class HiddenAreasChangedEvent {
369
370
public readonly kind = OutgoingViewModelEventKind.HiddenAreasChanged;
371
372
constructor() {
373
}
374
375
public isNoOp(): boolean {
376
return false;
377
}
378
379
public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {
380
if (other.kind !== this.kind) {
381
return null;
382
}
383
return this;
384
}
385
}
386
387
export class CursorStateChangedEvent {
388
389
public readonly kind = OutgoingViewModelEventKind.CursorStateChanged;
390
391
public readonly oldSelections: Selection[] | null;
392
public readonly selections: Selection[];
393
public readonly oldModelVersionId: number;
394
public readonly modelVersionId: number;
395
public readonly source: string;
396
public readonly reason: CursorChangeReason;
397
public readonly reachedMaxCursorCount: boolean;
398
399
constructor(oldSelections: Selection[] | null, selections: Selection[], oldModelVersionId: number, modelVersionId: number, source: string, reason: CursorChangeReason, reachedMaxCursorCount: boolean) {
400
this.oldSelections = oldSelections;
401
this.selections = selections;
402
this.oldModelVersionId = oldModelVersionId;
403
this.modelVersionId = modelVersionId;
404
this.source = source;
405
this.reason = reason;
406
this.reachedMaxCursorCount = reachedMaxCursorCount;
407
}
408
409
private static _selectionsAreEqual(a: Selection[] | null, b: Selection[] | null): boolean {
410
if (!a && !b) {
411
return true;
412
}
413
if (!a || !b) {
414
return false;
415
}
416
const aLen = a.length;
417
const bLen = b.length;
418
if (aLen !== bLen) {
419
return false;
420
}
421
for (let i = 0; i < aLen; i++) {
422
if (!a[i].equalsSelection(b[i])) {
423
return false;
424
}
425
}
426
return true;
427
}
428
429
public isNoOp(): boolean {
430
return (
431
CursorStateChangedEvent._selectionsAreEqual(this.oldSelections, this.selections)
432
&& this.oldModelVersionId === this.modelVersionId
433
);
434
}
435
436
public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {
437
if (other.kind !== this.kind) {
438
return null;
439
}
440
return new CursorStateChangedEvent(
441
this.oldSelections, other.selections, this.oldModelVersionId, other.modelVersionId, other.source, other.reason, this.reachedMaxCursorCount || other.reachedMaxCursorCount
442
);
443
}
444
}
445
446
export class ReadOnlyEditAttemptEvent {
447
448
public readonly kind = OutgoingViewModelEventKind.ReadOnlyEditAttempt;
449
450
constructor() {
451
}
452
453
public isNoOp(): boolean {
454
return false;
455
}
456
457
public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {
458
if (other.kind !== this.kind) {
459
return null;
460
}
461
return this;
462
}
463
}
464
465
export class ModelDecorationsChangedEvent {
466
public readonly kind = OutgoingViewModelEventKind.ModelDecorationsChanged;
467
468
constructor(
469
public readonly event: IModelDecorationsChangedEvent
470
) { }
471
472
public isNoOp(): boolean {
473
return false;
474
}
475
476
public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {
477
return null;
478
}
479
}
480
481
export class ModelLanguageChangedEvent {
482
public readonly kind = OutgoingViewModelEventKind.ModelLanguageChanged;
483
484
constructor(
485
public readonly event: IModelLanguageChangedEvent
486
) { }
487
488
public isNoOp(): boolean {
489
return false;
490
}
491
492
public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {
493
return null;
494
}
495
}
496
497
export class ModelLanguageConfigurationChangedEvent {
498
public readonly kind = OutgoingViewModelEventKind.ModelLanguageConfigurationChanged;
499
500
constructor(
501
public readonly event: IModelLanguageConfigurationChangedEvent
502
) { }
503
504
public isNoOp(): boolean {
505
return false;
506
}
507
508
public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {
509
return null;
510
}
511
}
512
513
export class ModelContentChangedEvent {
514
public readonly kind = OutgoingViewModelEventKind.ModelContentChanged;
515
516
constructor(
517
public readonly event: IModelContentChangedEvent
518
) { }
519
520
public isNoOp(): boolean {
521
return false;
522
}
523
524
public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {
525
return null;
526
}
527
}
528
529
export class ModelOptionsChangedEvent {
530
public readonly kind = OutgoingViewModelEventKind.ModelOptionsChanged;
531
532
constructor(
533
public readonly event: IModelOptionsChangedEvent
534
) { }
535
536
public isNoOp(): boolean {
537
return false;
538
}
539
540
public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {
541
return null;
542
}
543
}
544
545
export class ModelTokensChangedEvent {
546
public readonly kind = OutgoingViewModelEventKind.ModelTokensChanged;
547
548
constructor(
549
public readonly event: IModelTokensChangedEvent
550
) { }
551
552
public isNoOp(): boolean {
553
return false;
554
}
555
556
public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {
557
return null;
558
}
559
}
560
561
export class ModelLineHeightChangedEvent {
562
public readonly kind = OutgoingViewModelEventKind.ModelLineHeightChanged;
563
564
constructor(
565
public readonly event: OriginalModelLineHeightChangedEvent
566
) { }
567
568
public isNoOp(): boolean {
569
return false;
570
}
571
572
public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {
573
return null;
574
}
575
}
576
577
export class ModelFontChangedEvent {
578
public readonly kind = OutgoingViewModelEventKind.ModelFontChangedEvent;
579
580
constructor(
581
public readonly event: OriginalModelFontChangedEvent
582
) { }
583
584
public isNoOp(): boolean {
585
return false;
586
}
587
588
public attemptToMerge(other: OutgoingViewModelEvent): OutgoingViewModelEvent | null {
589
return null;
590
}
591
}
592
593