Path: blob/main/src/vs/editor/browser/controller/editContext/native/debugEditContext.ts
3296 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 { EditContext } from './editContextFactory.js';67const COLOR_FOR_CONTROL_BOUNDS = 'blue';8const COLOR_FOR_SELECTION_BOUNDS = 'red';9const COLOR_FOR_CHARACTER_BOUNDS = 'green';1011export class DebugEditContext {12private _isDebugging = true;13private _controlBounds: DOMRect | null = null;14private _selectionBounds: DOMRect | null = null;15private _characterBounds: { rangeStart: number; characterBounds: DOMRect[] } | null = null;1617private _editContext: EditContext;1819constructor(window: Window, options?: EditContextInit | undefined) {20this._editContext = EditContext.create(window, options);21}2223get text(): DOMString {24return this._editContext.text;25}2627get selectionStart(): number {28return this._editContext.selectionStart;29}3031get selectionEnd(): number {32return this._editContext.selectionEnd;33}3435get characterBoundsRangeStart(): number {36return this._editContext.characterBoundsRangeStart;37}3839updateText(rangeStart: number, rangeEnd: number, text: string): void {40this._editContext.updateText(rangeStart, rangeEnd, text);41this.renderDebug();42}43updateSelection(start: number, end: number): void {44this._editContext.updateSelection(start, end);45this.renderDebug();46}47updateControlBounds(controlBounds: DOMRect): void {48this._editContext.updateControlBounds(controlBounds);49this._controlBounds = controlBounds;50this.renderDebug();51}52updateSelectionBounds(selectionBounds: DOMRect): void {53this._editContext.updateSelectionBounds(selectionBounds);54this._selectionBounds = selectionBounds;55this.renderDebug();56}57updateCharacterBounds(rangeStart: number, characterBounds: DOMRect[]): void {58this._editContext.updateCharacterBounds(rangeStart, characterBounds);59this._characterBounds = { rangeStart, characterBounds };60this.renderDebug();61}62attachedElements(): HTMLElement[] {63return this._editContext.attachedElements();64}6566characterBounds(): DOMRect[] {67return this._editContext.characterBounds();68}6970private readonly _ontextupdateWrapper = new EventListenerWrapper('textupdate', this);71private readonly _ontextformatupdateWrapper = new EventListenerWrapper('textformatupdate', this);72private readonly _oncharacterboundsupdateWrapper = new EventListenerWrapper('characterboundsupdate', this);73private readonly _oncompositionstartWrapper = new EventListenerWrapper('compositionstart', this);74private readonly _oncompositionendWrapper = new EventListenerWrapper('compositionend', this);7576get ontextupdate(): EventHandler | null { return this._ontextupdateWrapper.eventHandler; }77set ontextupdate(value: EventHandler | null) { this._ontextupdateWrapper.eventHandler = value; }78get ontextformatupdate(): EventHandler | null { return this._ontextformatupdateWrapper.eventHandler; }79set ontextformatupdate(value: EventHandler | null) { this._ontextformatupdateWrapper.eventHandler = value; }80get oncharacterboundsupdate(): EventHandler | null { return this._oncharacterboundsupdateWrapper.eventHandler; }81set oncharacterboundsupdate(value: EventHandler | null) { this._oncharacterboundsupdateWrapper.eventHandler = value; }82get oncompositionstart(): EventHandler | null { return this._oncompositionstartWrapper.eventHandler; }83set oncompositionstart(value: EventHandler | null) { this._oncompositionstartWrapper.eventHandler = value; }84get oncompositionend(): EventHandler | null { return this._oncompositionendWrapper.eventHandler; }85set oncompositionend(value: EventHandler | null) { this._oncompositionendWrapper.eventHandler = value; }868788private readonly _listenerMap = new Map<EventListenerOrEventListenerObject, EventListenerOrEventListenerObject>();8990addEventListener<K extends keyof EditContextEventHandlersEventMap>(type: K, listener: (this: GlobalEventHandlers, ev: EditContextEventHandlersEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;91addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void {92if (!listener) { return; }9394const debugListener = (event: Event) => {95if (this._isDebugging) {96this.renderDebug();97console.log(`DebugEditContex.on_${type}`, event);98}99if (typeof listener === 'function') {100listener.call(this, event);101} else if (typeof listener === 'object' && 'handleEvent' in listener) {102listener.handleEvent(event);103}104};105this._listenerMap.set(listener, debugListener);106this._editContext.addEventListener(type, debugListener, options);107this.renderDebug();108}109110removeEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | EventListenerOptions | undefined): void {111if (!listener) { return; }112const debugListener = this._listenerMap.get(listener);113if (debugListener) {114this._editContext.removeEventListener(type, debugListener, options);115this._listenerMap.delete(listener);116}117this.renderDebug();118}119120dispatchEvent(event: Event): boolean {121return this._editContext.dispatchEvent(event);122}123124public startDebugging() {125this._isDebugging = true;126this.renderDebug();127}128129public endDebugging() {130this._isDebugging = false;131this.renderDebug();132}133134private _disposables: { dispose(): void }[] = [];135136public renderDebug() {137this._disposables.forEach(d => d.dispose());138this._disposables = [];139if (!this._isDebugging || this._listenerMap.size === 0) {140return;141}142if (this._controlBounds) {143this._disposables.push(createRect(this._controlBounds, COLOR_FOR_CONTROL_BOUNDS));144}145if (this._selectionBounds) {146this._disposables.push(createRect(this._selectionBounds, COLOR_FOR_SELECTION_BOUNDS));147}148if (this._characterBounds) {149for (const rect of this._characterBounds.characterBounds) {150this._disposables.push(createRect(rect, COLOR_FOR_CHARACTER_BOUNDS));151}152}153this._disposables.push(createDiv(this._editContext.text, this._editContext.selectionStart, this._editContext.selectionEnd));154}155}156157function createDiv(text: string, selectionStart: number, selectionEnd: number) {158const ret = document.createElement('div');159ret.className = 'debug-rect-marker';160ret.style.position = 'absolute';161ret.style.zIndex = '999999999';162ret.style.bottom = '50px';163ret.style.left = '60px';164ret.style.backgroundColor = 'white';165ret.style.border = '1px solid black';166ret.style.padding = '5px';167ret.style.whiteSpace = 'pre';168ret.style.font = '12px monospace';169ret.style.pointerEvents = 'none';170171const before = text.substring(0, selectionStart);172const selected = text.substring(selectionStart, selectionEnd) || '|';173const after = text.substring(selectionEnd) + ' ';174175const beforeNode = document.createTextNode(before);176ret.appendChild(beforeNode);177178const selectedNode = document.createElement('span');179selectedNode.style.backgroundColor = 'yellow';180selectedNode.appendChild(document.createTextNode(selected));181182selectedNode.style.minWidth = '2px';183selectedNode.style.minHeight = '16px';184ret.appendChild(selectedNode);185186const afterNode = document.createTextNode(after);187ret.appendChild(afterNode);188189// eslint-disable-next-line no-restricted-syntax190document.body.appendChild(ret);191192return {193dispose: () => {194ret.remove();195}196};197}198199function createRect(rect: DOMRect, color: 'green' | 'blue' | 'red') {200const ret = document.createElement('div');201ret.className = 'debug-rect-marker';202ret.style.position = 'absolute';203ret.style.zIndex = '999999999';204ret.style.outline = `2px solid ${color}`;205ret.style.pointerEvents = 'none';206207ret.style.top = rect.top + 'px';208ret.style.left = rect.left + 'px';209ret.style.width = rect.width + 'px';210ret.style.height = rect.height + 'px';211212// eslint-disable-next-line no-restricted-syntax213document.body.appendChild(ret);214215return {216dispose: () => {217ret.remove();218}219};220}221222class EventListenerWrapper {223private _eventHandler: EventHandler | null = null;224225constructor(226private readonly _eventType: string,227private readonly _target: EventTarget,228) {229}230231get eventHandler(): EventHandler | null {232return this._eventHandler;233}234235set eventHandler(value: EventHandler | null) {236if (this._eventHandler) {237this._target.removeEventListener(this._eventType, this._eventHandler);238}239this._eventHandler = value;240if (value) {241this._target.addEventListener(this._eventType, value);242}243}244}245246247