From 71773967c436988bbcb6e2db307c9453c83d2e93 Mon Sep 17 00:00:00 2001 From: James Park Date: Mon, 20 Nov 2023 20:42:52 -0800 Subject: [PATCH 1/7] fix --- src/vs/editor/browser/widget/media/editor.css | 1 - .../suggest/browser/suggestWidgetDetails.ts | 25 +++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/browser/widget/media/editor.css b/src/vs/editor/browser/widget/media/editor.css index 1d60940158ad4..b5c5a686e1714 100644 --- a/src/vs/editor/browser/widget/media/editor.css +++ b/src/vs/editor/browser/widget/media/editor.css @@ -47,7 +47,6 @@ /* -------------------- Misc -------------------- */ .monaco-editor .overflow-guard { - position: relative; overflow: hidden; } diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts index 7f4646be05317..6839665736b26 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts @@ -17,6 +17,7 @@ import { ResizableHTMLElement } from 'vs/base/browser/ui/resizable/resizable'; import * as nls from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { CompletionItem } from './suggest'; +import { assert } from 'vs/base/common/assert'; export function canExpandCompletionItem(item: CompletionItem | undefined): boolean { return !!item && Boolean(item.completion.documentation || item.completion.detail && item.completion.detail !== item.completion.label); @@ -365,8 +366,28 @@ export class SuggestDetailsOverlay implements IOverlayWidget { } placeAtAnchor(anchor: HTMLElement, preferAlignAtTop: boolean) { - const anchorBox = anchor.getBoundingClientRect(); - this._anchorBox = anchorBox; + let relativeAnchorBox: dom.IDomNodePagePosition; + + const editorAsHtmlElement = this._editor.getDomNode(); + if (editorAsHtmlElement) { + // get the bounding rectangle of the editor and the suggest widget (relative to the viewport) + const monacoEditorBoundingBox = editorAsHtmlElement.getBoundingClientRect(); + const suggest = anchor.getBoundingClientRect(); + + // get bounding rectangle of the suggest widget relative to the editor + relativeAnchorBox = { + top: suggest.top - monacoEditorBoundingBox.top, + left: suggest.left - monacoEditorBoundingBox.left, + width: suggest.width, + height: suggest.height, + } + } else { + // Editor Dom Node should be available + assert(false); + relativeAnchorBox = anchor.getBoundingClientRect(); + } + + this._anchorBox = relativeAnchorBox; this._preferAlignAtTop = preferAlignAtTop; this._placeAtAnchor(this._anchorBox, this._userSize ?? this.widget.size, preferAlignAtTop); } From d4f6f5303008205ad32e86ee7d9ae85109add2ff Mon Sep 17 00:00:00 2001 From: James Park Date: Mon, 20 Nov 2023 20:57:59 -0800 Subject: [PATCH 2/7] fixed --- src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts index 6839665736b26..a4ec3b3ffcc0e 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts @@ -345,7 +345,7 @@ export class SuggestDetailsOverlay implements IOverlayWidget { show(): void { if (!this._added) { this._editor.addOverlayWidget(this); - this.getDomNode().style.position = 'fixed'; + this.getDomNode().style.position = 'absolute'; this._added = true; } } @@ -460,7 +460,7 @@ export class SuggestDetailsOverlay implements IOverlayWidget { } this._applyTopLeft({ left: placement.left, top: alignAtTop ? placement.top : bottom - height }); - this.getDomNode().style.position = 'fixed'; + this.getDomNode().style.position = 'absolute'; this._resizable.enableSashes(!alignAtTop, placement === eastPlacement, alignAtTop, placement !== eastPlacement); From d1f51ab48d4c6985d2d12749cce4fcb913170586 Mon Sep 17 00:00:00 2001 From: James Park Date: Mon, 20 Nov 2023 21:00:20 -0800 Subject: [PATCH 3/7] foo --- src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts index a4ec3b3ffcc0e..c4209d2fb9cb2 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts @@ -380,7 +380,7 @@ export class SuggestDetailsOverlay implements IOverlayWidget { left: suggest.left - monacoEditorBoundingBox.left, width: suggest.width, height: suggest.height, - } + }; } else { // Editor Dom Node should be available assert(false); From 3d1e701a8364bf78ad62f1e7a05763104b7052f6 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 13 Dec 2023 14:24:17 +0100 Subject: [PATCH 4/7] allow overlay widgets to overflow --- src/vs/editor/browser/controller/mouseTarget.ts | 13 ++++++++++--- src/vs/editor/browser/editorBrowser.ts | 4 ++++ src/vs/editor/browser/view.ts | 2 ++ src/vs/editor/browser/view/viewPart.ts | 1 + .../viewParts/overlayWidgets/overlayWidgets.ts | 14 ++++++++++++-- src/vs/monaco.d.ts | 4 ++++ 6 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index 4b7929cd8ed43..b6bb7881850a2 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -219,6 +219,13 @@ class ElementPath { && path[1] === PartFingerprint.OverlayWidgets ); } + + public static isChildOfOverflowingOverlayWidgets(path: Uint8Array): boolean { + return ( + path.length >= 1 + && path[0] === PartFingerprint.OverflowingOverlayWidgets + ); + } } export class HitTestContext { @@ -490,7 +497,7 @@ export class MouseTargetFactory { } // Is it an overlay widget? - if (ElementPath.isChildOfOverlayWidgets(path)) { + if (ElementPath.isChildOfOverlayWidgets(path) || ElementPath.isChildOfOverflowingOverlayWidgets(path)) { return true; } @@ -545,7 +552,7 @@ export class MouseTargetFactory { let result: IMouseTarget | null = null; - if (!ElementPath.isChildOfOverflowGuard(request.targetPath) && !ElementPath.isChildOfOverflowingContentWidgets(request.targetPath)) { + if (!ElementPath.isChildOfOverflowGuard(request.targetPath) && !ElementPath.isChildOfOverflowingContentWidgets(request.targetPath) && !ElementPath.isChildOfOverflowingOverlayWidgets(request.targetPath)) { // We only render dom nodes inside the overflow guard or in the overflowing content widgets result = result || request.fulfillUnknown(); } @@ -579,7 +586,7 @@ export class MouseTargetFactory { private static _hitTestOverlayWidget(ctx: HitTestContext, request: ResolvedHitTestRequest): IMouseTarget | null { // Is it an overlay widget? - if (ElementPath.isChildOfOverlayWidgets(request.targetPath)) { + if (ElementPath.isChildOfOverlayWidgets(request.targetPath) || ElementPath.isChildOfOverflowingOverlayWidgets(request.targetPath)) { const widgetId = ctx.findAttribute(request.target, 'widgetId'); if (widgetId) { return request.fulfillOverlayWidget(widgetId); diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index c048cd07ba986..3ca4a1a4a7c4a 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -238,6 +238,10 @@ export interface IOverlayWidgetPosition { * An overlay widgets renders on top of the text. */ export interface IOverlayWidget { + /** + * Render this overlay widget in a location where it could overflow the editor's view dom node. + */ + allowEditorOverflow?: boolean; /** * Get a unique identifier of the overlay widget. */ diff --git a/src/vs/editor/browser/view.ts b/src/vs/editor/browser/view.ts index 6df5d5c6d6fa1..710a16a343c2b 100644 --- a/src/vs/editor/browser/view.ts +++ b/src/vs/editor/browser/view.ts @@ -231,8 +231,10 @@ export class View extends ViewEventHandler { if (overflowWidgetsDomNode) { overflowWidgetsDomNode.appendChild(this._contentWidgets.overflowingContentWidgetsDomNode.domNode); + overflowWidgetsDomNode.appendChild(this._overlayWidgets.overflowingOverlayWidgetsDomNode.domNode); } else { this.domNode.appendChild(this._contentWidgets.overflowingContentWidgetsDomNode); + this.domNode.appendChild(this._overlayWidgets.overflowingOverlayWidgetsDomNode); } this._applyLayout(); diff --git a/src/vs/editor/browser/view/viewPart.ts b/src/vs/editor/browser/view/viewPart.ts index ad88b9f955175..8ae1a965ecfd2 100644 --- a/src/vs/editor/browser/view/viewPart.ts +++ b/src/vs/editor/browser/view/viewPart.ts @@ -33,6 +33,7 @@ export const enum PartFingerprint { OverflowingContentWidgets, OverflowGuard, OverlayWidgets, + OverflowingOverlayWidgets, ScrollableElement, TextArea, ViewLines, diff --git a/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts b/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts index 0165d4eb63ffb..073be925e124a 100644 --- a/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts +++ b/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts @@ -27,6 +27,7 @@ export class ViewOverlayWidgets extends ViewPart { private _widgets: IWidgetMap; private readonly _domNode: FastDomNode; + public readonly overflowingOverlayWidgetsDomNode: FastDomNode; private _verticalScrollbarWidth: number; private _minimapWidth: number; @@ -50,6 +51,10 @@ export class ViewOverlayWidgets extends ViewPart { this._domNode = createFastDomNode(document.createElement('div')); PartFingerprints.write(this._domNode, PartFingerprint.OverlayWidgets); this._domNode.setClassName('overlayWidgets'); + + this.overflowingOverlayWidgetsDomNode = createFastDomNode(document.createElement('div')); + PartFingerprints.write(this.overflowingOverlayWidgetsDomNode, PartFingerprint.OverflowingOverlayWidgets); + this.overflowingOverlayWidgetsDomNode.setClassName('overflowingOverlayWidgets'); } public override dispose(): void { @@ -89,7 +94,12 @@ export class ViewOverlayWidgets extends ViewPart { // This is sync because a widget wants to be in the dom domNode.setPosition('absolute'); domNode.setAttribute('widgetId', widget.getId()); - this._domNode.appendChild(domNode); + + if (widget.allowEditorOverflow) { + this.overflowingOverlayWidgetsDomNode.appendChild(domNode); + } else { + this._domNode.appendChild(domNode); + } this.setShouldRender(); this._updateMaxMinWidth(); @@ -116,7 +126,7 @@ export class ViewOverlayWidgets extends ViewPart { const domNode = widgetData.domNode.domNode; delete this._widgets[widgetId]; - domNode.parentNode!.removeChild(domNode); + domNode.remove(); this.setShouldRender(); this._updateMaxMinWidth(); } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index eb224d668bfc2..3a7c4f3b4e22a 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -5277,6 +5277,10 @@ declare namespace monaco.editor { * An overlay widgets renders on top of the text. */ export interface IOverlayWidget { + /** + * Render this overlay widget in a location where it could overflow the editor's view dom node. + */ + allowEditorOverflow?: boolean; /** * Get a unique identifier of the overlay widget. */ From c5be2dcaadcb155fd02838546e14be013242add0 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 13 Dec 2023 14:25:09 +0100 Subject: [PATCH 5/7] restore overflow-guard behaviour, tweak placeAtAnchor a little and mark details overlay widgets as overflowing --- src/vs/editor/browser/widget/media/editor.css | 1 + .../suggest/browser/suggestWidgetDetails.ts | 37 +++++++++---------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/vs/editor/browser/widget/media/editor.css b/src/vs/editor/browser/widget/media/editor.css index b5c5a686e1714..1d60940158ad4 100644 --- a/src/vs/editor/browser/widget/media/editor.css +++ b/src/vs/editor/browser/widget/media/editor.css @@ -47,6 +47,7 @@ /* -------------------- Misc -------------------- */ .monaco-editor .overflow-guard { + position: relative; overflow: hidden; } diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts index 09488d0c884dc..9ce7b795acbef 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts @@ -17,7 +17,6 @@ import { ResizableHTMLElement } from 'vs/base/browser/ui/resizable/resizable'; import * as nls from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { CompletionItem } from './suggest'; -import { assert } from 'vs/base/common/assert'; export function canExpandCompletionItem(item: CompletionItem | undefined): boolean { return !!item && Boolean(item.completion.documentation || item.completion.detail && item.completion.detail !== item.completion.label); @@ -264,6 +263,8 @@ interface TopLeftPosition { export class SuggestDetailsOverlay implements IOverlayWidget { + readonly allowEditorOverflow = true; + private readonly _disposables = new DisposableStore(); private readonly _resizable: ResizableHTMLElement; @@ -370,27 +371,23 @@ export class SuggestDetailsOverlay implements IOverlayWidget { } placeAtAnchor(anchor: HTMLElement, preferAlignAtTop: boolean) { - let relativeAnchorBox: dom.IDomNodePagePosition; - - const editorAsHtmlElement = this._editor.getDomNode(); - if (editorAsHtmlElement) { - // get the bounding rectangle of the editor and the suggest widget (relative to the viewport) - const monacoEditorBoundingBox = editorAsHtmlElement.getBoundingClientRect(); - const suggest = anchor.getBoundingClientRect(); - - // get bounding rectangle of the suggest widget relative to the editor - relativeAnchorBox = { - top: suggest.top - monacoEditorBoundingBox.top, - left: suggest.left - monacoEditorBoundingBox.left, - width: suggest.width, - height: suggest.height, - }; - } else { - // Editor Dom Node should be available - assert(false); - relativeAnchorBox = anchor.getBoundingClientRect(); + const editorDomNode = this._editor.getDomNode(); + if (!editorDomNode) { + // might happen when running tests + return; } + // get the bounding rectangle of the editor and the suggest widget (relative to the viewport) + const editorBoundingBox = editorDomNode.getBoundingClientRect(); + const anchorBoundingBox = anchor.getBoundingClientRect(); + + // get bounding rectangle of the suggest widget relative to the editor + const relativeAnchorBox: dom.IDomNodePagePosition = { + top: anchorBoundingBox.top - editorBoundingBox.top, + left: anchorBoundingBox.left - editorBoundingBox.left, + width: anchorBoundingBox.width, + height: anchorBoundingBox.height, + }; this._anchorBox = relativeAnchorBox; this._preferAlignAtTop = preferAlignAtTop; this._placeAtAnchor(this._anchorBox, this._userSize ?? this.widget.size, preferAlignAtTop); From 8e3ae234e4b94b0f41eba2807df7afb014691895 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 13 Dec 2023 14:55:35 +0100 Subject: [PATCH 6/7] make positioning editor-relative only after computing and picking placements (since they use the absolute document's box) --- .../suggest/browser/suggestWidgetDetails.ts | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts index 9ce7b795acbef..f809b363bc566 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts @@ -371,24 +371,8 @@ export class SuggestDetailsOverlay implements IOverlayWidget { } placeAtAnchor(anchor: HTMLElement, preferAlignAtTop: boolean) { - const editorDomNode = this._editor.getDomNode(); - if (!editorDomNode) { - // might happen when running tests - return; - } - - // get the bounding rectangle of the editor and the suggest widget (relative to the viewport) - const editorBoundingBox = editorDomNode.getBoundingClientRect(); - const anchorBoundingBox = anchor.getBoundingClientRect(); - - // get bounding rectangle of the suggest widget relative to the editor - const relativeAnchorBox: dom.IDomNodePagePosition = { - top: anchorBoundingBox.top - editorBoundingBox.top, - left: anchorBoundingBox.left - editorBoundingBox.left, - width: anchorBoundingBox.width, - height: anchorBoundingBox.height, - }; - this._anchorBox = relativeAnchorBox; + const anchorBox = anchor.getBoundingClientRect(); + this._anchorBox = anchorBox; this._preferAlignAtTop = preferAlignAtTop; this._placeAtAnchor(this._anchorBox, this._userSize ?? this.widget.size, preferAlignAtTop); } @@ -460,6 +444,14 @@ export class SuggestDetailsOverlay implements IOverlayWidget { } } + const editorDomNode = this._editor.getDomNode(); + if (editorDomNode) { + // get bounding rectangle of the suggest widget relative to the editor + const editorBoundingBox = editorDomNode.getBoundingClientRect(); + placement.top -= editorBoundingBox.top; + placement.left -= editorBoundingBox.left; + } + this._applyTopLeft({ left: placement.left, top: alignAtTop ? placement.top : bottom - height }); this.getDomNode().style.position = 'absolute'; From f3f0dcf3776255afee17606c5304c53af29f6e3d Mon Sep 17 00:00:00 2001 From: Johannes Date: Thu, 14 Dec 2023 14:22:38 +0100 Subject: [PATCH 7/7] add `IOverlayWidgetPositionCoordinates` and support fixed overflow overlay widgets --- src/vs/editor/browser/editorBrowser.ts | 18 ++++++++++- src/vs/editor/browser/view.ts | 2 +- .../overlayWidgets/overlayWidgets.ts | 31 +++++++++++++++---- .../suggest/browser/suggestWidgetDetails.ts | 22 ++++++------- src/vs/monaco.d.ts | 16 +++++++++- 5 files changed, 69 insertions(+), 20 deletions(-) diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 3ca4a1a4a7c4a..0b8fc09755a33 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -225,6 +225,22 @@ export const enum OverlayWidgetPositionPreference { */ TOP_CENTER } + + +/** + * Represents editor-relative coordinates of an overlay widget. + */ +export interface IOverlayWidgetPositionCoordinates { + /** + * The top position for the overlay widget, relative to the editor. + */ + top: number; + /** + * The left position for the overlay widget, relative to the editor. + */ + left: number; +} + /** * A position for rendering overlay widgets. */ @@ -232,7 +248,7 @@ export interface IOverlayWidgetPosition { /** * The position preference for the overlay widget. */ - preference: OverlayWidgetPositionPreference | null; + preference: OverlayWidgetPositionPreference | IOverlayWidgetPositionCoordinates | null; } /** * An overlay widgets renders on top of the text. diff --git a/src/vs/editor/browser/view.ts b/src/vs/editor/browser/view.ts index 710a16a343c2b..5bc11e49309fc 100644 --- a/src/vs/editor/browser/view.ts +++ b/src/vs/editor/browser/view.ts @@ -194,7 +194,7 @@ export class View extends ViewEventHandler { this._viewParts.push(this._viewCursors); // Overlay widgets - this._overlayWidgets = new ViewOverlayWidgets(this._context); + this._overlayWidgets = new ViewOverlayWidgets(this._context, this.domNode); this._viewParts.push(this._overlayWidgets); const rulers = new Rulers(this._context); diff --git a/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts b/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts index 073be925e124a..9bc7a585356ce 100644 --- a/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts +++ b/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts @@ -5,17 +5,18 @@ import 'vs/css!./overlayWidgets'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; -import { IOverlayWidget, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; +import { IOverlayWidget, IOverlayWidgetPositionCoordinates, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart'; import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext'; import { ViewContext } from 'vs/editor/common/viewModel/viewContext'; import * as viewEvents from 'vs/editor/common/viewEvents'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import * as dom from 'vs/base/browser/dom'; interface IWidgetData { widget: IOverlayWidget; - preference: OverlayWidgetPositionPreference | null; + preference: OverlayWidgetPositionPreference | IOverlayWidgetPositionCoordinates | null; domNode: FastDomNode; } @@ -25,18 +26,20 @@ interface IWidgetMap { export class ViewOverlayWidgets extends ViewPart { + private readonly _viewDomNode: FastDomNode; private _widgets: IWidgetMap; + private _viewDomNodeRect: dom.IDomNodePagePosition; private readonly _domNode: FastDomNode; public readonly overflowingOverlayWidgetsDomNode: FastDomNode; - private _verticalScrollbarWidth: number; private _minimapWidth: number; private _horizontalScrollbarHeight: number; private _editorHeight: number; private _editorWidth: number; - constructor(context: ViewContext) { + constructor(context: ViewContext, viewDomNode: FastDomNode) { super(context); + this._viewDomNode = viewDomNode; const options = this._context.configuration.options; const layoutInfo = options.get(EditorOption.layoutInfo); @@ -47,6 +50,7 @@ export class ViewOverlayWidgets extends ViewPart { this._horizontalScrollbarHeight = layoutInfo.horizontalScrollbarHeight; this._editorHeight = layoutInfo.height; this._editorWidth = layoutInfo.width; + this._viewDomNodeRect = { top: 0, left: 0, width: 0, height: 0 }; this._domNode = createFastDomNode(document.createElement('div')); PartFingerprints.write(this._domNode, PartFingerprint.OverlayWidgets); @@ -105,7 +109,7 @@ export class ViewOverlayWidgets extends ViewPart { this._updateMaxMinWidth(); } - public setWidgetPosition(widget: IOverlayWidget, preference: OverlayWidgetPositionPreference | null): boolean { + public setWidgetPosition(widget: IOverlayWidget, preference: OverlayWidgetPositionPreference | IOverlayWidgetPositionCoordinates | null): boolean { const widgetData = this._widgets[widget.getId()]; if (widgetData.preference === preference) { this._updateMaxMinWidth(); @@ -164,11 +168,26 @@ export class ViewOverlayWidgets extends ViewPart { } else if (widgetData.preference === OverlayWidgetPositionPreference.TOP_CENTER) { domNode.setTop(0); domNode.domNode.style.right = '50%'; + } else { + const { top, left } = widgetData.preference; + const fixedOverflowWidgets = this._context.configuration.options.get(EditorOption.fixedOverflowWidgets); + if (fixedOverflowWidgets && widgetData.widget.allowEditorOverflow) { + // top, left are computed relative to the editor and we need them relative to the page + const editorBoundingBox = this._viewDomNodeRect!; + domNode.setTop(top + editorBoundingBox.top); + domNode.setLeft(left + editorBoundingBox.left); + domNode.setPosition('fixed'); + + } else { + domNode.setTop(top); + domNode.setLeft(left); + domNode.setPosition('absolute'); + } } } public prepareRender(ctx: RenderingContext): void { - // Nothing to read + this._viewDomNodeRect = dom.getDomNodePagePosition(this._viewDomNode.domNode); } public render(ctx: RestrictedRenderingContext): void { diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts index f809b363bc566..381cec7fbb2a1 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts @@ -11,7 +11,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; -import { ICodeEditor, IOverlayWidget } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { ResizableHTMLElement } from 'vs/base/browser/ui/resizable/resizable'; import * as nls from 'vs/nls'; @@ -343,14 +343,13 @@ export class SuggestDetailsOverlay implements IOverlayWidget { return this._resizable.domNode; } - getPosition(): null { - return null; + getPosition(): IOverlayWidgetPosition | null { + return this._topLeft ? { preference: this._topLeft } : null; } show(): void { if (!this._added) { this._editor.addOverlayWidget(this); - this.getDomNode().style.position = 'absolute'; this._added = true; } } @@ -444,16 +443,18 @@ export class SuggestDetailsOverlay implements IOverlayWidget { } } + let { top, left } = placement; + if (!alignAtTop) { + top = bottom - height; + } const editorDomNode = this._editor.getDomNode(); if (editorDomNode) { // get bounding rectangle of the suggest widget relative to the editor const editorBoundingBox = editorDomNode.getBoundingClientRect(); - placement.top -= editorBoundingBox.top; - placement.left -= editorBoundingBox.left; + top -= editorBoundingBox.top; + left -= editorBoundingBox.left; } - - this._applyTopLeft({ left: placement.left, top: alignAtTop ? placement.top : bottom - height }); - this.getDomNode().style.position = 'absolute'; + this._applyTopLeft({ left, top }); this._resizable.enableSashes(!alignAtTop, placement === eastPlacement, alignAtTop, placement !== eastPlacement); @@ -465,7 +466,6 @@ export class SuggestDetailsOverlay implements IOverlayWidget { private _applyTopLeft(topLeft: TopLeftPosition): void { this._topLeft = topLeft; - this.getDomNode().style.left = `${this._topLeft.left}px`; - this.getDomNode().style.top = `${this._topLeft.top}px`; + this._editor.layoutOverlayWidget(this); } } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 3a7c4f3b4e22a..d082987b0d347 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -5263,6 +5263,20 @@ declare namespace monaco.editor { TOP_CENTER = 2 } + /** + * Represents editor-relative coordinates of an overlay widget. + */ + export interface IOverlayWidgetPositionCoordinates { + /** + * The top position for the overlay widget, relative to the editor. + */ + top: number; + /** + * The left position for the overlay widget, relative to the editor. + */ + left: number; + } + /** * A position for rendering overlay widgets. */ @@ -5270,7 +5284,7 @@ declare namespace monaco.editor { /** * The position preference for the overlay widget. */ - preference: OverlayWidgetPositionPreference | null; + preference: OverlayWidgetPositionPreference | IOverlayWidgetPositionCoordinates | null; } /**