From 5ff3c23a15a86f466ecf710761b9fdb7faff215c Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 22 Jul 2024 15:32:04 +0200 Subject: [PATCH] Fixing hover disappearance in chat pannel (#219099) * adding code * adding code to send the event at the lowest level * adding code * adding some code * polish * fixing hover disappearing too quickly, not looking at delay * polishing the code * cleaning * adding cleaning code * settign timeout to 0 --- .../editor/browser/controller/mouseHandler.ts | 91 +++++++++++++++---- .../editor/browser/controller/mouseTarget.ts | 24 ++++- .../browser/controller/pointerHandler.ts | 6 +- src/vs/editor/browser/view.ts | 3 + 4 files changed, 97 insertions(+), 27 deletions(-) diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index 55d58d6eef95b..1f70dce4903c9 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -24,6 +24,7 @@ import { MouseWheelClassifier } from 'vs/base/browser/ui/scrollbar/scrollableEle export interface IPointerHandlerHelper { viewDomNode: HTMLElement; + overflowWidgetsDomNode: HTMLElement | null; linesContentDomNode: HTMLElement; viewLinesDomNode: HTMLElement; @@ -62,6 +63,8 @@ export class MouseHandler extends ViewEventHandler { private lastMouseLeaveTime: number; private _height: number; private _mouseLeaveMonitor: IDisposable | null = null; + private _mouseOnOverflowWidgetsDomNode: boolean = false; + private _mouseOnViewDomNode: boolean = false; constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) { super(); @@ -76,7 +79,7 @@ export class MouseHandler extends ViewEventHandler { this.viewController, this.viewHelper, this.mouseTargetFactory, - (e, testEventTarget) => this._createMouseTarget(e, testEventTarget), + (e, testEventTarget) => this._createMouseTargetForView(e, testEventTarget), (e) => this._getMouseColumn(e) )); @@ -88,7 +91,8 @@ export class MouseHandler extends ViewEventHandler { this._register(mouseEvents.onContextMenu(this.viewHelper.viewDomNode, (e) => this._onContextMenu(e, true))); this._register(mouseEvents.onMouseMove(this.viewHelper.viewDomNode, (e) => { - this._onMouseMove(e); + this._mouseOnViewDomNode = true; + this._onMouseMoveOverView(e); // See https://github.com/microsoft/vscode/issues/138789 // When moving the mouse really quickly, the browser sometimes forgets to @@ -101,7 +105,12 @@ export class MouseHandler extends ViewEventHandler { this._mouseLeaveMonitor = dom.addDisposableListener(this.viewHelper.viewDomNode.ownerDocument, 'mousemove', (e) => { if (!this.viewHelper.viewDomNode.contains(e.target as Node | null)) { // went outside the editor! - this._onMouseLeave(new EditorMouseEvent(e, false, this.viewHelper.viewDomNode)); + this._mouseOnViewDomNode = false; + setTimeout(() => { + if (!this._mouseOnOverflowWidgetsDomNode) { + this._onMouseLeave(new EditorMouseEvent(e, false, this.viewHelper.viewDomNode)); + } + }, 0); } }); } @@ -109,7 +118,32 @@ export class MouseHandler extends ViewEventHandler { this._register(mouseEvents.onMouseUp(this.viewHelper.viewDomNode, (e) => this._onMouseUp(e))); - this._register(mouseEvents.onMouseLeave(this.viewHelper.viewDomNode, (e) => this._onMouseLeave(e))); + this._register(mouseEvents.onMouseLeave(this.viewHelper.viewDomNode, (e) => { + this._mouseOnViewDomNode = false; + setTimeout(() => { + if (!this._mouseOnOverflowWidgetsDomNode) { + this._onMouseLeave(e); + } + }, 0); + })); + + const overflowWidgetsDomNode = this.viewHelper.overflowWidgetsDomNode; + if (overflowWidgetsDomNode) { + this._register(mouseEvents.onMouseMove(overflowWidgetsDomNode, (e) => { + this._mouseOnOverflowWidgetsDomNode = true; + this._mouseLeaveMonitor?.dispose(); + this._mouseLeaveMonitor = null; + this._onMouseMoveOverOverflowWidgetsDomNode(e); + })); + this._register(mouseEvents.onMouseLeave(overflowWidgetsDomNode, (e) => { + this._mouseOnOverflowWidgetsDomNode = false; + setTimeout(() => { + if (!this._mouseOnViewDomNode) { + this._onMouseLeave(e); + } + }, 0); + })); + } // `pointerdown` events can't be used to determine if there's a double click, or triple click // because their `e.detail` is always 0. @@ -234,10 +268,10 @@ export class MouseHandler extends ViewEventHandler { } const relativePos = createCoordinatesRelativeToEditor(this.viewHelper.viewDomNode, editorPos, pos); - return this.mouseTargetFactory.createMouseTarget(this.viewHelper.getLastRenderData(), editorPos, pos, relativePos, null); + return this.mouseTargetFactory.createMouseTargetForView(this.viewHelper.getLastRenderData(), editorPos, pos, relativePos, null); } - protected _createMouseTarget(e: EditorMouseEvent, testEventTarget: boolean): IMouseTarget { + protected _createMouseTargetForView(e: EditorMouseEvent, testEventTarget: boolean): IMouseTarget { let target = e.target; if (!this.viewHelper.viewDomNode.contains(target)) { const shadowRoot = dom.getShadowRoot(this.viewHelper.viewDomNode); @@ -247,7 +281,11 @@ export class MouseHandler extends ViewEventHandler { ); } } - return this.mouseTargetFactory.createMouseTarget(this.viewHelper.getLastRenderData(), e.editorPos, e.pos, e.relativePos, testEventTarget ? target : null); + return this.mouseTargetFactory.createMouseTargetForView(this.viewHelper.getLastRenderData(), e.editorPos, e.pos, e.relativePos, testEventTarget ? target : null); + } + + private _createMouseTargetForOverflowWidgetsDomNode(e: EditorMouseEvent): IMouseTarget { + return this.mouseTargetFactory.createMouseTargetForOverflowWidgetsDomNode(this.viewHelper.getLastRenderData(), e.editorPos, e.pos, e.relativePos, e.target); } private _getMouseColumn(e: EditorMouseEvent): number { @@ -257,11 +295,30 @@ export class MouseHandler extends ViewEventHandler { protected _onContextMenu(e: EditorMouseEvent, testEventTarget: boolean): void { this.viewController.emitContextMenu({ event: e, - target: this._createMouseTarget(e, testEventTarget) + target: this._createMouseTargetForView(e, testEventTarget) + }); + } + + protected _onMouseMoveOverView(e: EditorMouseEvent): void { + this._onMouseMove(e, this._createMouseTargetForView(e, true)); + } + + private _onMouseMoveOverOverflowWidgetsDomNode(e: EditorMouseEvent): void { + this._onMouseMove(e, this._createMouseTargetForOverflowWidgetsDomNode(e)); + } + + private _onMouseMove(e: EditorMouseEvent, target: IMouseTarget): void { + const shouldIgnoreMouseMoveEvent = this._shouldIgnoreMouseMoveEvent(e); + if (shouldIgnoreMouseMoveEvent) { + return undefined; + } + this.viewController.emitMouseMove({ + event: e, + target }); } - protected _onMouseMove(e: EditorMouseEvent): void { + private _shouldIgnoreMouseMoveEvent(e: EditorMouseEvent): boolean { const targetIsWidget = this.mouseTargetFactory.mouseTargetIsWidget(e); if (!targetIsWidget) { e.preventDefault(); @@ -269,18 +326,14 @@ export class MouseHandler extends ViewEventHandler { if (this._mouseDownOperation.isActive()) { // In selection/drag operation - return; + return true; } const actualMouseMoveTime = e.timestamp; if (actualMouseMoveTime < this.lastMouseLeaveTime) { // Due to throttling, this event occurred before the mouse left the editor, therefore ignore it. - return; + return true; } - - this.viewController.emitMouseMove({ - event: e, - target: this._createMouseTarget(e, true) - }); + return false; } protected _onMouseLeave(e: EditorMouseEvent): void { @@ -298,12 +351,12 @@ export class MouseHandler extends ViewEventHandler { protected _onMouseUp(e: EditorMouseEvent): void { this.viewController.emitMouseUp({ event: e, - target: this._createMouseTarget(e, true) + target: this._createMouseTargetForView(e, true) }); } protected _onMouseDown(e: EditorMouseEvent, pointerId: number): void { - const t = this._createMouseTarget(e, true); + const t = this._createMouseTargetForView(e, true); const targetIsContent = (t.type === MouseTargetType.CONTENT_TEXT || t.type === MouseTargetType.CONTENT_EMPTY); const targetIsGutter = (t.type === MouseTargetType.GUTTER_GLYPH_MARGIN || t.type === MouseTargetType.GUTTER_LINE_NUMBERS || t.type === MouseTargetType.GUTTER_LINE_DECORATIONS); @@ -742,7 +795,7 @@ class TopBottomDragScrollingOperation extends Disposable { const horizontalScrollbarHeight = this._context.configuration.options.get(EditorOption.layoutInfo).horizontalScrollbarHeight; const pos = new PageCoordinates(this._mouseEvent.pos.x, editorPos.y + editorPos.height - horizontalScrollbarHeight - 0.1); const relativePos = createCoordinatesRelativeToEditor(this._viewHelper.viewDomNode, editorPos, pos); - mouseTarget = this._mouseTargetFactory.createMouseTarget(this._viewHelper.getLastRenderData(), editorPos, pos, relativePos, null); + mouseTarget = this._mouseTargetFactory.createMouseTargetForView(this._viewHelper.getLastRenderData(), editorPos, pos, relativePos, null); } if (!mouseTarget.position || mouseTarget.position.lineNumber !== edgeLineNumber) { if (this._position.outsidePosition === 'above') { diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index 4a1addb3fc0eb..c0f66317e9a2b 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -238,6 +238,7 @@ export class HitTestContext { public readonly viewModel: IViewModel; public readonly layoutInfo: EditorLayoutInfo; public readonly viewDomNode: HTMLElement; + public readonly overflowWidgetsDomNode: HTMLElement | null; public readonly lineHeight: number; public readonly stickyTabStops: boolean; public readonly typicalHalfwidthCharacterWidth: number; @@ -251,6 +252,7 @@ export class HitTestContext { const options = context.configuration.options; this.layoutInfo = options.get(EditorOption.layoutInfo); this.viewDomNode = viewHelper.viewDomNode; + this.overflowWidgetsDomNode = viewHelper.overflowWidgetsDomNode ?? null; this.lineHeight = options.get(EditorOption.lineHeight); this.stickyTabStops = options.get(EditorOption.stickyTabStops); this.typicalHalfwidthCharacterWidth = options.get(EditorOption.fontInfo).typicalHalfwidthCharacterWidth; @@ -413,6 +415,7 @@ class HitTestRequest extends BareHitTestRequest { private _useHitTestTarget: boolean; private _targetPathCacheElement: HTMLElement | null = null; private _targetPathCacheValue: Uint8Array = new Uint8Array(0); + private _targetElement: HTMLElement | null = null; public get target(): HTMLElement | null { if (this._useHitTestTarget) { @@ -422,17 +425,18 @@ class HitTestRequest extends BareHitTestRequest { } public get targetPath(): Uint8Array { - if (this._targetPathCacheElement !== this.target) { + if (this._targetPathCacheElement !== this.target && this._targetElement) { this._targetPathCacheElement = this.target; - this._targetPathCacheValue = PartFingerprints.collect(this.target, this._ctx.viewDomNode); + this._targetPathCacheValue = PartFingerprints.collect(this.target, this._targetElement); } return this._targetPathCacheValue; } - constructor(ctx: HitTestContext, editorPos: EditorPagePosition, pos: PageCoordinates, relativePos: CoordinatesRelativeToEditor, eventTarget: HTMLElement | null) { + constructor(ctx: HitTestContext, editorPos: EditorPagePosition, pos: PageCoordinates, relativePos: CoordinatesRelativeToEditor, eventTarget: HTMLElement | null, targetElement: HTMLElement | null = null) { super(ctx, editorPos, pos, relativePos); this._ctx = ctx; this._eventTarget = eventTarget; + this._targetElement = targetElement; // If no event target is passed in, we will use the hit test target const hasEventTarget = Boolean(this._eventTarget); @@ -532,9 +536,9 @@ export class MouseTargetFactory { return false; } - public createMouseTarget(lastRenderData: PointerHandlerLastRenderData, editorPos: EditorPagePosition, pos: PageCoordinates, relativePos: CoordinatesRelativeToEditor, target: HTMLElement | null): IMouseTarget { + public createMouseTargetForView(lastRenderData: PointerHandlerLastRenderData, editorPos: EditorPagePosition, pos: PageCoordinates, relativePos: CoordinatesRelativeToEditor, target: HTMLElement | null): IMouseTarget { const ctx = new HitTestContext(this._context, this._viewHelper, lastRenderData); - const request = new HitTestRequest(ctx, editorPos, pos, relativePos, target); + const request = new HitTestRequest(ctx, editorPos, pos, relativePos, target, ctx.viewDomNode); try { const r = MouseTargetFactory._createMouseTarget(ctx, request); @@ -555,6 +559,16 @@ export class MouseTargetFactory { } } + public createMouseTargetForOverflowWidgetsDomNode(lastRenderData: PointerHandlerLastRenderData, editorPos: EditorPagePosition, pos: PageCoordinates, relativePos: CoordinatesRelativeToEditor, target: HTMLElement | null) { + const ctx = new HitTestContext(this._context, this._viewHelper, lastRenderData); + const request = new HitTestRequest(ctx, editorPos, pos, relativePos, target, ctx.overflowWidgetsDomNode); + try { + return MouseTargetFactory._createMouseTarget(ctx, request); + } catch (err) { + return request.fulfillUnknown(); + } + } + private static _createMouseTarget(ctx: HitTestContext, request: HitTestRequest): IMouseTarget { // console.log(`${domHitTestExecuted ? '=>' : ''}CAME IN REQUEST: ${request}`); diff --git a/src/vs/editor/browser/controller/pointerHandler.ts b/src/vs/editor/browser/controller/pointerHandler.ts index 2019016ac4ce2..258884eafa87e 100644 --- a/src/vs/editor/browser/controller/pointerHandler.ts +++ b/src/vs/editor/browser/controller/pointerHandler.ts @@ -47,7 +47,7 @@ export class PointerEventHandler extends MouseHandler { // PonterEvents const pointerEvents = new EditorPointerEventFactory(this.viewHelper.viewDomNode); - this._register(pointerEvents.onPointerMove(this.viewHelper.viewDomNode, (e) => this._onMouseMove(e))); + this._register(pointerEvents.onPointerMove(this.viewHelper.viewDomNode, (e) => this._onMouseMoveOverView(e))); this._register(pointerEvents.onPointerUp(this.viewHelper.viewDomNode, (e) => this._onMouseUp(e))); this._register(pointerEvents.onPointerLeave(this.viewHelper.viewDomNode, (e) => this._onMouseLeave(e))); this._register(pointerEvents.onPointerDown(this.viewHelper.viewDomNode, (e, pointerId) => this._onMouseDown(e, pointerId))); @@ -73,7 +73,7 @@ export class PointerEventHandler extends MouseHandler { } private _dispatchGesture(event: GestureEvent, inSelectionMode: boolean): void { - const target = this._createMouseTarget(new EditorMouseEvent(event, false, this.viewHelper.viewDomNode), false); + const target = this._createMouseTargetForView(new EditorMouseEvent(event, false, this.viewHelper.viewDomNode), false); if (target.position) { this.viewController.dispatchMouse({ position: target.position, @@ -119,7 +119,7 @@ class TouchHandler extends MouseHandler { this.viewHelper.focusTextArea(); - const target = this._createMouseTarget(new EditorMouseEvent(event, false, this.viewHelper.viewDomNode), false); + const target = this._createMouseTargetForView(new EditorMouseEvent(event, false, this.viewHelper.viewDomNode), false); if (target.position) { // Send the tap event also to the