X-Git-Url: https://git.josue.xyz/?p=VSoRC%2F.git;a=blobdiff_plain;f=node_modules%2Fxterm%2Fsrc%2Fbrowser%2FMouseZoneManager.ts;fp=node_modules%2Fxterm%2Fsrc%2Fbrowser%2FMouseZoneManager.ts;h=7eb7c5f89b91fb38a519c8933a002436690ffda7;hp=0000000000000000000000000000000000000000;hb=4339da12467b75fb8b6ca831f4bf0081c485ed2c;hpb=af450fde25a9ccf4767b29254c463ffb8ef25237 diff --git a/node_modules/xterm/src/browser/MouseZoneManager.ts b/node_modules/xterm/src/browser/MouseZoneManager.ts new file mode 100644 index 0000000..7eb7c5f --- /dev/null +++ b/node_modules/xterm/src/browser/MouseZoneManager.ts @@ -0,0 +1,241 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { Disposable } from 'common/Lifecycle'; +import { addDisposableDomListener } from 'browser/Lifecycle'; +import { IMouseService, ISelectionService } from 'browser/services/Services'; +import { IMouseZoneManager, IMouseZone } from 'browser/Types'; +import { IBufferService } from 'common/services/Services'; + +const HOVER_DURATION = 500; + +/** + * The MouseZoneManager allows components to register zones within the terminal + * that trigger hover and click callbacks. + * + * This class was intentionally made not so robust initially as the only case it + * needed to support was single-line links which never overlap. Improvements can + * be made in the future. + */ +export class MouseZoneManager extends Disposable implements IMouseZoneManager { + private _zones: IMouseZone[] = []; + + private _areZonesActive: boolean = false; + private _mouseMoveListener: (e: MouseEvent) => any; + private _mouseLeaveListener: (e: MouseEvent) => any; + private _clickListener: (e: MouseEvent) => any; + + private _tooltipTimeout: number | undefined; + private _currentZone: IMouseZone | undefined; + private _lastHoverCoords: [number | undefined, number | undefined] = [undefined, undefined]; + private _initialSelectionLength: number = 0; + + constructor( + private readonly _element: HTMLElement, + private readonly _screenElement: HTMLElement, + @IBufferService private readonly _bufferService: IBufferService, + @IMouseService private readonly _mouseService: IMouseService, + @ISelectionService private readonly _selectionService: ISelectionService + ) { + super(); + + this.register(addDisposableDomListener(this._element, 'mousedown', e => this._onMouseDown(e))); + + // These events are expensive, only listen to it when mouse zones are active + this._mouseMoveListener = e => this._onMouseMove(e); + this._mouseLeaveListener = e => this._onMouseLeave(e); + this._clickListener = e => this._onClick(e); + } + + public dispose(): void { + super.dispose(); + this._deactivate(); + } + + public add(zone: IMouseZone): void { + this._zones.push(zone); + if (this._zones.length === 1) { + this._activate(); + } + } + + public clearAll(start?: number, end?: number): void { + // Exit if there's nothing to clear + if (this._zones.length === 0) { + return; + } + + // Clear all if start/end weren't set + if (!start || !end) { + start = 0; + end = this._bufferService.rows - 1; + } + + // Iterate through zones and clear them out if they're within the range + for (let i = 0; i < this._zones.length; i++) { + const zone = this._zones[i]; + if ((zone.y1 > start && zone.y1 <= end + 1) || + (zone.y2 > start && zone.y2 <= end + 1) || + (zone.y1 < start && zone.y2 > end + 1)) { + if (this._currentZone && this._currentZone === zone) { + this._currentZone.leaveCallback(); + this._currentZone = undefined; + } + this._zones.splice(i--, 1); + } + } + + // Deactivate the mouse zone manager if all the zones have been removed + if (this._zones.length === 0) { + this._deactivate(); + } + } + + private _activate(): void { + if (!this._areZonesActive) { + this._areZonesActive = true; + this._element.addEventListener('mousemove', this._mouseMoveListener); + this._element.addEventListener('mouseleave', this._mouseLeaveListener); + this._element.addEventListener('click', this._clickListener); + } + } + + private _deactivate(): void { + if (this._areZonesActive) { + this._areZonesActive = false; + this._element.removeEventListener('mousemove', this._mouseMoveListener); + this._element.removeEventListener('mouseleave', this._mouseLeaveListener); + this._element.removeEventListener('click', this._clickListener); + } + } + + private _onMouseMove(e: MouseEvent): void { + // TODO: Ideally this would only clear the hover state when the mouse moves + // outside of the mouse zone + if (this._lastHoverCoords[0] !== e.pageX || this._lastHoverCoords[1] !== e.pageY) { + this._onHover(e); + // Record the current coordinates + this._lastHoverCoords = [e.pageX, e.pageY]; + } + } + + private _onHover(e: MouseEvent): void { + const zone = this._findZoneEventAt(e); + + // Do nothing if the zone is the same + if (zone === this._currentZone) { + return; + } + + // Fire the hover end callback and cancel any existing timer if a new zone + // is being hovered + if (this._currentZone) { + this._currentZone.leaveCallback(); + this._currentZone = undefined; + if (this._tooltipTimeout) { + clearTimeout(this._tooltipTimeout); + } + } + + // Exit if there is not zone + if (!zone) { + return; + } + this._currentZone = zone; + + // Trigger the hover callback + if (zone.hoverCallback) { + zone.hoverCallback(e); + } + + // Restart the tooltip timeout + this._tooltipTimeout = setTimeout(() => this._onTooltip(e), HOVER_DURATION); + } + + private _onTooltip(e: MouseEvent): void { + this._tooltipTimeout = undefined; + const zone = this._findZoneEventAt(e); + if (zone && zone.tooltipCallback) { + zone.tooltipCallback(e); + } + } + + private _onMouseDown(e: MouseEvent): void { + // Store current terminal selection length, to check if we're performing + // a selection operation + this._initialSelectionLength = this._getSelectionLength(); + + // Ignore the event if there are no zones active + if (!this._areZonesActive) { + return; + } + + // Find the active zone, prevent event propagation if found to prevent other + // components from handling the mouse event. + const zone = this._findZoneEventAt(e); + if (zone) { + if (zone.willLinkActivate(e)) { + e.preventDefault(); + e.stopImmediatePropagation(); + } + } + } + + private _onMouseLeave(e: MouseEvent): void { + // Fire the hover end callback and cancel any existing timer if the mouse + // leaves the terminal element + if (this._currentZone) { + this._currentZone.leaveCallback(); + this._currentZone = undefined; + if (this._tooltipTimeout) { + clearTimeout(this._tooltipTimeout); + } + } + } + + private _onClick(e: MouseEvent): void { + // Find the active zone and click it if found and no selection was + // being performed + const zone = this._findZoneEventAt(e); + const currentSelectionLength = this._getSelectionLength(); + + if (zone && currentSelectionLength === this._initialSelectionLength) { + zone.clickCallback(e); + e.preventDefault(); + e.stopImmediatePropagation(); + } + } + + private _getSelectionLength(): number { + const selectionText = this._selectionService.selectionText; + return selectionText ? selectionText.length : 0; + } + + private _findZoneEventAt(e: MouseEvent): IMouseZone | undefined { + const coords = this._mouseService.getCoords(e, this._screenElement, this._bufferService.cols, this._bufferService.rows); + if (!coords) { + return undefined; + } + const x = coords[0]; + const y = coords[1]; + for (let i = 0; i < this._zones.length; i++) { + const zone = this._zones[i]; + if (zone.y1 === zone.y2) { + // Single line link + if (y === zone.y1 && x >= zone.x1 && x < zone.x2) { + return zone; + } + } else { + // Multi-line link + if ((y === zone.y1 && x >= zone.x1) || + (y === zone.y2 && x < zone.x2) || + (y > zone.y1 && y < zone.y2)) { + return zone; + } + } + } + return undefined; + } +}