Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/gitpod-protocol/src/util/event.ts
2500 views
1
/*
2
* Copyright (C) 2017 TypeFox and others.
3
*
4
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
5
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
*/
7
8
import { Disposable } from "./disposable";
9
import { log } from "./logging";
10
11
/**
12
* Represents a typed event.
13
*/
14
export interface Event<T> {
15
/**
16
*
17
* @param listener The listener function will be call when the event happens.
18
* @param thisArgs The 'this' which will be used when calling the event listener.
19
* @param disposables An array to which a {{IDisposable}} will be added.
20
* @return a disposable to remove the listener again.
21
*/
22
(listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]): Disposable;
23
}
24
25
export namespace Event {
26
const _disposable = { dispose() {} };
27
export const None: Event<any> = function () {
28
return _disposable;
29
};
30
}
31
32
class CallbackList {
33
private _callbacks: Function[] | undefined;
34
private _contexts: any[] | undefined;
35
36
public add(callback: Function, context: any = null, bucket?: Disposable[]): void {
37
if (!this._callbacks) {
38
this._callbacks = [];
39
this._contexts = [];
40
}
41
this._callbacks.push(callback);
42
this._contexts!.push(context);
43
44
if (Array.isArray(bucket)) {
45
bucket.push({ dispose: () => this.remove(callback, context) });
46
}
47
}
48
49
public remove(callback: Function, context: any = null): void {
50
if (!this._callbacks) {
51
return;
52
}
53
54
let foundCallbackWithDifferentContext = false;
55
for (let i = 0, len = this._callbacks.length; i < len; i++) {
56
if (this._callbacks[i] === callback) {
57
if (this._contexts![i] === context) {
58
// callback & context match => remove it
59
this._callbacks.splice(i, 1);
60
this._contexts!.splice(i, 1);
61
return;
62
} else {
63
foundCallbackWithDifferentContext = true;
64
}
65
}
66
}
67
68
if (foundCallbackWithDifferentContext) {
69
throw new Error("When adding a listener with a context, you should remove it with the same context");
70
}
71
}
72
73
public invoke(...args: any[]): any[] {
74
if (!this._callbacks) {
75
return [];
76
}
77
78
const ret: any[] = [];
79
const callbacks = this._callbacks.slice(0);
80
const contexts = this._contexts!.slice(0);
81
82
for (let i = 0, len = callbacks.length; i < len; i++) {
83
try {
84
ret.push(callbacks[i].apply(contexts[i], args));
85
} catch (e) {
86
log.error(e);
87
}
88
}
89
return ret;
90
}
91
92
public isEmpty(): boolean {
93
return !this._callbacks || this._callbacks.length === 0;
94
}
95
96
public dispose(): void {
97
this._callbacks = undefined;
98
this._contexts = undefined;
99
}
100
}
101
102
export interface EmitterOptions {
103
onFirstListenerAdd?: Function;
104
onLastListenerRemove?: Function;
105
}
106
107
export class Emitter<T> {
108
private static _noop = function () {};
109
110
private _event: Event<T>;
111
private _callbacks: CallbackList | undefined;
112
113
constructor(private _options?: EmitterOptions) {}
114
115
/**
116
* For the public to allow to subscribe
117
* to events from this Emitter
118
*/
119
get event(): Event<T> {
120
if (!this._event) {
121
this._event = (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => {
122
if (!this._callbacks) {
123
this._callbacks = new CallbackList();
124
}
125
if (this._options && this._options.onFirstListenerAdd && this._callbacks.isEmpty()) {
126
this._options.onFirstListenerAdd(this);
127
}
128
this._callbacks.add(listener, thisArgs);
129
130
const result = {
131
dispose: () => {
132
this._callbacks!.remove(listener, thisArgs);
133
result.dispose = Emitter._noop;
134
if (this._options && this._options.onLastListenerRemove && this._callbacks!.isEmpty()) {
135
this._options.onLastListenerRemove(this);
136
}
137
},
138
};
139
if (Array.isArray(disposables)) {
140
disposables.push(result);
141
}
142
143
return result;
144
};
145
}
146
return this._event;
147
}
148
149
/**
150
* To be kept private to fire an event to
151
* subscribers
152
*/
153
fire(event: T): any {
154
if (this._callbacks) {
155
this._callbacks.invoke.call(this._callbacks, event);
156
}
157
}
158
159
dispose() {
160
if (this._callbacks) {
161
this._callbacks.dispose();
162
this._callbacks = undefined;
163
}
164
}
165
}
166
167