Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/browser/controller/editContext/native/debugEditContext.ts
3296 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 { EditContext } from './editContextFactory.js';
7
8
const COLOR_FOR_CONTROL_BOUNDS = 'blue';
9
const COLOR_FOR_SELECTION_BOUNDS = 'red';
10
const COLOR_FOR_CHARACTER_BOUNDS = 'green';
11
12
export class DebugEditContext {
13
private _isDebugging = true;
14
private _controlBounds: DOMRect | null = null;
15
private _selectionBounds: DOMRect | null = null;
16
private _characterBounds: { rangeStart: number; characterBounds: DOMRect[] } | null = null;
17
18
private _editContext: EditContext;
19
20
constructor(window: Window, options?: EditContextInit | undefined) {
21
this._editContext = EditContext.create(window, options);
22
}
23
24
get text(): DOMString {
25
return this._editContext.text;
26
}
27
28
get selectionStart(): number {
29
return this._editContext.selectionStart;
30
}
31
32
get selectionEnd(): number {
33
return this._editContext.selectionEnd;
34
}
35
36
get characterBoundsRangeStart(): number {
37
return this._editContext.characterBoundsRangeStart;
38
}
39
40
updateText(rangeStart: number, rangeEnd: number, text: string): void {
41
this._editContext.updateText(rangeStart, rangeEnd, text);
42
this.renderDebug();
43
}
44
updateSelection(start: number, end: number): void {
45
this._editContext.updateSelection(start, end);
46
this.renderDebug();
47
}
48
updateControlBounds(controlBounds: DOMRect): void {
49
this._editContext.updateControlBounds(controlBounds);
50
this._controlBounds = controlBounds;
51
this.renderDebug();
52
}
53
updateSelectionBounds(selectionBounds: DOMRect): void {
54
this._editContext.updateSelectionBounds(selectionBounds);
55
this._selectionBounds = selectionBounds;
56
this.renderDebug();
57
}
58
updateCharacterBounds(rangeStart: number, characterBounds: DOMRect[]): void {
59
this._editContext.updateCharacterBounds(rangeStart, characterBounds);
60
this._characterBounds = { rangeStart, characterBounds };
61
this.renderDebug();
62
}
63
attachedElements(): HTMLElement[] {
64
return this._editContext.attachedElements();
65
}
66
67
characterBounds(): DOMRect[] {
68
return this._editContext.characterBounds();
69
}
70
71
private readonly _ontextupdateWrapper = new EventListenerWrapper('textupdate', this);
72
private readonly _ontextformatupdateWrapper = new EventListenerWrapper('textformatupdate', this);
73
private readonly _oncharacterboundsupdateWrapper = new EventListenerWrapper('characterboundsupdate', this);
74
private readonly _oncompositionstartWrapper = new EventListenerWrapper('compositionstart', this);
75
private readonly _oncompositionendWrapper = new EventListenerWrapper('compositionend', this);
76
77
get ontextupdate(): EventHandler | null { return this._ontextupdateWrapper.eventHandler; }
78
set ontextupdate(value: EventHandler | null) { this._ontextupdateWrapper.eventHandler = value; }
79
get ontextformatupdate(): EventHandler | null { return this._ontextformatupdateWrapper.eventHandler; }
80
set ontextformatupdate(value: EventHandler | null) { this._ontextformatupdateWrapper.eventHandler = value; }
81
get oncharacterboundsupdate(): EventHandler | null { return this._oncharacterboundsupdateWrapper.eventHandler; }
82
set oncharacterboundsupdate(value: EventHandler | null) { this._oncharacterboundsupdateWrapper.eventHandler = value; }
83
get oncompositionstart(): EventHandler | null { return this._oncompositionstartWrapper.eventHandler; }
84
set oncompositionstart(value: EventHandler | null) { this._oncompositionstartWrapper.eventHandler = value; }
85
get oncompositionend(): EventHandler | null { return this._oncompositionendWrapper.eventHandler; }
86
set oncompositionend(value: EventHandler | null) { this._oncompositionendWrapper.eventHandler = value; }
87
88
89
private readonly _listenerMap = new Map<EventListenerOrEventListenerObject, EventListenerOrEventListenerObject>();
90
91
addEventListener<K extends keyof EditContextEventHandlersEventMap>(type: K, listener: (this: GlobalEventHandlers, ev: EditContextEventHandlersEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
92
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void {
93
if (!listener) { return; }
94
95
const debugListener = (event: Event) => {
96
if (this._isDebugging) {
97
this.renderDebug();
98
console.log(`DebugEditContex.on_${type}`, event);
99
}
100
if (typeof listener === 'function') {
101
listener.call(this, event);
102
} else if (typeof listener === 'object' && 'handleEvent' in listener) {
103
listener.handleEvent(event);
104
}
105
};
106
this._listenerMap.set(listener, debugListener);
107
this._editContext.addEventListener(type, debugListener, options);
108
this.renderDebug();
109
}
110
111
removeEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | EventListenerOptions | undefined): void {
112
if (!listener) { return; }
113
const debugListener = this._listenerMap.get(listener);
114
if (debugListener) {
115
this._editContext.removeEventListener(type, debugListener, options);
116
this._listenerMap.delete(listener);
117
}
118
this.renderDebug();
119
}
120
121
dispatchEvent(event: Event): boolean {
122
return this._editContext.dispatchEvent(event);
123
}
124
125
public startDebugging() {
126
this._isDebugging = true;
127
this.renderDebug();
128
}
129
130
public endDebugging() {
131
this._isDebugging = false;
132
this.renderDebug();
133
}
134
135
private _disposables: { dispose(): void }[] = [];
136
137
public renderDebug() {
138
this._disposables.forEach(d => d.dispose());
139
this._disposables = [];
140
if (!this._isDebugging || this._listenerMap.size === 0) {
141
return;
142
}
143
if (this._controlBounds) {
144
this._disposables.push(createRect(this._controlBounds, COLOR_FOR_CONTROL_BOUNDS));
145
}
146
if (this._selectionBounds) {
147
this._disposables.push(createRect(this._selectionBounds, COLOR_FOR_SELECTION_BOUNDS));
148
}
149
if (this._characterBounds) {
150
for (const rect of this._characterBounds.characterBounds) {
151
this._disposables.push(createRect(rect, COLOR_FOR_CHARACTER_BOUNDS));
152
}
153
}
154
this._disposables.push(createDiv(this._editContext.text, this._editContext.selectionStart, this._editContext.selectionEnd));
155
}
156
}
157
158
function createDiv(text: string, selectionStart: number, selectionEnd: number) {
159
const ret = document.createElement('div');
160
ret.className = 'debug-rect-marker';
161
ret.style.position = 'absolute';
162
ret.style.zIndex = '999999999';
163
ret.style.bottom = '50px';
164
ret.style.left = '60px';
165
ret.style.backgroundColor = 'white';
166
ret.style.border = '1px solid black';
167
ret.style.padding = '5px';
168
ret.style.whiteSpace = 'pre';
169
ret.style.font = '12px monospace';
170
ret.style.pointerEvents = 'none';
171
172
const before = text.substring(0, selectionStart);
173
const selected = text.substring(selectionStart, selectionEnd) || '|';
174
const after = text.substring(selectionEnd) + ' ';
175
176
const beforeNode = document.createTextNode(before);
177
ret.appendChild(beforeNode);
178
179
const selectedNode = document.createElement('span');
180
selectedNode.style.backgroundColor = 'yellow';
181
selectedNode.appendChild(document.createTextNode(selected));
182
183
selectedNode.style.minWidth = '2px';
184
selectedNode.style.minHeight = '16px';
185
ret.appendChild(selectedNode);
186
187
const afterNode = document.createTextNode(after);
188
ret.appendChild(afterNode);
189
190
// eslint-disable-next-line no-restricted-syntax
191
document.body.appendChild(ret);
192
193
return {
194
dispose: () => {
195
ret.remove();
196
}
197
};
198
}
199
200
function createRect(rect: DOMRect, color: 'green' | 'blue' | 'red') {
201
const ret = document.createElement('div');
202
ret.className = 'debug-rect-marker';
203
ret.style.position = 'absolute';
204
ret.style.zIndex = '999999999';
205
ret.style.outline = `2px solid ${color}`;
206
ret.style.pointerEvents = 'none';
207
208
ret.style.top = rect.top + 'px';
209
ret.style.left = rect.left + 'px';
210
ret.style.width = rect.width + 'px';
211
ret.style.height = rect.height + 'px';
212
213
// eslint-disable-next-line no-restricted-syntax
214
document.body.appendChild(ret);
215
216
return {
217
dispose: () => {
218
ret.remove();
219
}
220
};
221
}
222
223
class EventListenerWrapper {
224
private _eventHandler: EventHandler | null = null;
225
226
constructor(
227
private readonly _eventType: string,
228
private readonly _target: EventTarget,
229
) {
230
}
231
232
get eventHandler(): EventHandler | null {
233
return this._eventHandler;
234
}
235
236
set eventHandler(value: EventHandler | null) {
237
if (this._eventHandler) {
238
this._target.removeEventListener(this._eventType, this._eventHandler);
239
}
240
this._eventHandler = value;
241
if (value) {
242
this._target.addEventListener(this._eventType, value);
243
}
244
}
245
}
246
247