Merge pull request #198730 from GenericTSDeveloper/suggestVSCode

Fixed issue Misalignment of suggestion details widget (https://github.com/microsoft/monaco-editor/issues/3373)
This commit is contained in:
Johannes Rieken
2023-12-14 14:39:05 +01:00
committed by GitHub
7 changed files with 109 additions and 22 deletions
@@ -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);
+21 -1
View File
@@ -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,12 +248,16 @@ 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.
*/
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.
*/
+3 -1
View File
@@ -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);
@@ -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();
+1
View File
@@ -33,6 +33,7 @@ export const enum PartFingerprint {
OverflowingContentWidgets,
OverflowGuard,
OverlayWidgets,
OverflowingOverlayWidgets,
ScrollableElement,
TextArea,
ViewLines,
@@ -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<HTMLElement>;
}
@@ -25,17 +26,20 @@ interface IWidgetMap {
export class ViewOverlayWidgets extends ViewPart {
private readonly _viewDomNode: FastDomNode<HTMLElement>;
private _widgets: IWidgetMap;
private _viewDomNodeRect: dom.IDomNodePagePosition;
private readonly _domNode: FastDomNode<HTMLElement>;
public readonly overflowingOverlayWidgetsDomNode: FastDomNode<HTMLElement>;
private _verticalScrollbarWidth: number;
private _minimapWidth: number;
private _horizontalScrollbarHeight: number;
private _editorHeight: number;
private _editorWidth: number;
constructor(context: ViewContext) {
constructor(context: ViewContext, viewDomNode: FastDomNode<HTMLElement>) {
super(context);
this._viewDomNode = viewDomNode;
const options = this._context.configuration.options;
const layoutInfo = options.get(EditorOption.layoutInfo);
@@ -46,10 +50,15 @@ 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);
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,13 +98,18 @@ 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();
}
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();
@@ -116,7 +130,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();
}
@@ -154,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 {
@@ -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';
@@ -263,6 +263,8 @@ interface TopLeftPosition {
export class SuggestDetailsOverlay implements IOverlayWidget {
readonly allowEditorOverflow = true;
private readonly _disposables = new DisposableStore();
private readonly _resizable: ResizableHTMLElement;
@@ -341,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 = 'fixed';
this._added = true;
}
}
@@ -442,8 +443,18 @@ export class SuggestDetailsOverlay implements IOverlayWidget {
}
}
this._applyTopLeft({ left: placement.left, top: alignAtTop ? placement.top : bottom - height });
this.getDomNode().style.position = 'fixed';
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();
top -= editorBoundingBox.top;
left -= editorBoundingBox.left;
}
this._applyTopLeft({ left, top });
this._resizable.enableSashes(!alignAtTop, placement === eastPlacement, alignAtTop, placement !== eastPlacement);
@@ -455,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);
}
}
+19 -1
View File
@@ -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,13 +5284,17 @@ declare namespace monaco.editor {
/**
* The position preference for the overlay widget.
*/
preference: OverlayWidgetPositionPreference | null;
preference: OverlayWidgetPositionPreference | IOverlayWidgetPositionCoordinates | null;
}
/**
* 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.
*/