Improve hover positioning by introducing a secondary anchor

This commit is contained in:
Alex Dima
2022-10-18 23:38:09 +02:00
parent 78fffb9290
commit 8f9f5cca2c
5 changed files with 134 additions and 143 deletions

View File

@@ -132,10 +132,11 @@ export interface IContentWidgetPosition {
*/
position: IPosition | null;
/**
* Optionally, a range can be provided to further
* define the position of the content widget.
* Optionally, a secondary position can be provided to further
* define the position of the content widget. The secondary position
* must have the same line number as the primary position.
*/
range?: IRange | null;
secondaryPosition?: IPosition | null;
/**
* Placement preference for position, in order of preference.
*/

View File

@@ -37,7 +37,6 @@ import { SelectionsOverlay } from 'vs/editor/browser/viewParts/selections/select
import { ViewCursors } from 'vs/editor/browser/viewParts/viewCursors/viewCursors';
import { ViewZones } from 'vs/editor/browser/viewParts/viewZones/viewZones';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { ScrollType } from 'vs/editor/common/editorCommon';
import { IEditorConfiguration } from 'vs/editor/common/config/editorConfiguration';
import { RenderingContext } from 'vs/editor/browser/view/renderingContext';
@@ -502,15 +501,13 @@ export class View extends ViewEventHandler {
}
public layoutContentWidget(widgetData: IContentWidgetData): void {
let newRange = widgetData.position ? widgetData.position.range || null : null;
if (newRange === null) {
const newPosition = widgetData.position ? widgetData.position.position : null;
if (newPosition !== null) {
newRange = new Range(newPosition.lineNumber, newPosition.column, newPosition.lineNumber, newPosition.column);
}
}
const newPreference = widgetData.position ? widgetData.position.preference : null;
this._contentWidgets.setWidgetPosition(widgetData.widget, newRange, newPreference, widgetData.position?.positionAffinity ?? null);
this._contentWidgets.setWidgetPosition(
widgetData.widget,
widgetData.position?.position ?? null,
widgetData.position?.secondaryPosition ?? null,
widgetData.position?.preference ?? null,
widgetData.position?.positionAffinity ?? null
);
this._scheduleRender();
}

View File

@@ -7,28 +7,15 @@ import * as dom from 'vs/base/browser/dom';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { ContentWidgetPositionPreference, IContentWidget } from 'vs/editor/browser/editorBrowser';
import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart';
import { IRange, Range } from 'vs/editor/common/core/range';
import { Constants } from 'vs/base/common/uint';
import { HorizontalRange, LineVisibleRanges, RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext';
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 { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { IDimension } from 'vs/editor/common/core/dimension';
import { PositionAffinity } from 'vs/editor/common/model';
class Coordinate {
_coordinateBrand: void = undefined;
public readonly top: number;
public readonly left: number;
constructor(top: number, left: number) {
this.top = top;
this.left = left;
}
}
import { IPosition, Position } from 'vs/editor/common/core/position';
import { IViewModel } from 'vs/editor/common/viewModel';
export class ViewContentWidgets extends ViewPart {
@@ -113,9 +100,9 @@ export class ViewContentWidgets extends ViewPart {
this.setShouldRender();
}
public setWidgetPosition(widget: IContentWidget, range: IRange | null, preference: ContentWidgetPositionPreference[] | null, affinity: PositionAffinity | null): void {
public setWidgetPosition(widget: IContentWidget, primaryAnchor: IPosition | null, secondaryAnchor: IPosition | null, preference: ContentWidgetPositionPreference[] | null, affinity: PositionAffinity | null): void {
const myWidget = this._widgets[widget.getId()];
myWidget.setPosition(range, preference, affinity);
myWidget.setPosition(primaryAnchor, secondaryAnchor, preference, affinity);
this.setShouldRender();
}
@@ -166,11 +153,11 @@ export class ViewContentWidgets extends ViewPart {
interface IBoxLayoutResult {
fitsAbove: boolean;
aboveTop: number;
aboveLeft: number;
fitsBelow: boolean;
belowTop: number;
belowLeft: number;
left: number;
}
interface IRenderData {
@@ -193,9 +180,9 @@ class Widget {
private _contentLeft: number;
private _lineHeight: number;
private _range: IRange | null;
private _primaryAnchor: PositionPair = new PositionPair(null, null);
private _secondaryAnchor: PositionPair = new PositionPair(null, null);
private _affinity: PositionAffinity | null;
private _viewRange: Range | null;
private _preference: ContentWidgetPositionPreference[] | null;
private _cachedDomNodeOffsetWidth: number;
private _cachedDomNodeOffsetHeight: number;
@@ -222,8 +209,6 @@ class Widget {
this._contentLeft = layoutInfo.contentLeft;
this._lineHeight = options.get(EditorOption.lineHeight);
this._range = null;
this._viewRange = null;
this._affinity = null;
this._preference = [];
this._cachedDomNodeOffsetWidth = -1;
@@ -251,20 +236,25 @@ class Widget {
}
public onLineMappingChanged(e: viewEvents.ViewLineMappingChangedEvent): void {
this._setPosition(this._range, this._affinity);
this._setPosition(this._affinity, this._primaryAnchor.modelPosition, this._secondaryAnchor.modelPosition);
}
private _setPosition(range: IRange | null, affinity: PositionAffinity | null): void {
this._range = range;
this._viewRange = null;
private _setPosition(affinity: PositionAffinity | null, primaryAnchor: IPosition | null, secondaryAnchor: IPosition | null): void {
this._affinity = affinity;
this._primaryAnchor = getValidPositionPair(primaryAnchor, this._context.viewModel, this._affinity);
this._secondaryAnchor = getValidPositionPair(secondaryAnchor, this._context.viewModel, this._affinity);
if (this._range) {
// Do not trust that widgets give a valid position
const validModelRange = this._context.viewModel.model.validateRange(this._range);
if (this._context.viewModel.coordinatesConverter.modelPositionIsVisible(validModelRange.getStartPosition()) || this._context.viewModel.coordinatesConverter.modelPositionIsVisible(validModelRange.getEndPosition())) {
this._viewRange = this._context.viewModel.coordinatesConverter.convertModelRangeToViewRange(validModelRange, this._affinity ?? undefined);
function getValidPositionPair(position: IPosition | null, viewModel: IViewModel, affinity: PositionAffinity | null): PositionPair {
if (!position) {
return new PositionPair(null, null);
}
// Do not trust that widgets give a valid position
const validModelPosition = viewModel.model.validatePosition(position);
if (viewModel.coordinatesConverter.modelPositionIsVisible(validModelPosition)) {
const viewPosition = viewModel.coordinatesConverter.convertModelPositionToViewPosition(validModelPosition, affinity ?? undefined);
return new PositionPair(position, viewPosition);
}
return new PositionPair(position, null);
}
}
@@ -276,10 +266,10 @@ class Widget {
);
}
public setPosition(range: IRange | null, preference: ContentWidgetPositionPreference[] | null, affinity: PositionAffinity | null): void {
this._setPosition(range, affinity);
public setPosition(primaryAnchor: IPosition | null, secondaryAnchor: IPosition | null, preference: ContentWidgetPositionPreference[] | null, affinity: PositionAffinity | null): void {
this._setPosition(affinity, primaryAnchor, secondaryAnchor);
this._preference = preference;
if (this._viewRange && this._preference && this._preference.length > 0) {
if (this._primaryAnchor.viewPosition && this._preference && this._preference.length > 0) {
// this content widget would like to be visible if possible
// we change it from `display:none` to `display:block` even if it
// might be outside the viewport such that we can measure its size
@@ -292,15 +282,15 @@ class Widget {
this._cachedDomNodeOffsetHeight = -1;
}
private _layoutBoxInViewport(aboveAnchor: Coordinate, belowAnchor: Coordinate, width: number, height: number, ctx: RenderingContext): IBoxLayoutResult {
private _layoutBoxInViewport(anchor: AnchorCoordinate, width: number, height: number, ctx: RenderingContext): IBoxLayoutResult {
// Our visible box is split horizontally by the current line => 2 boxes
// a) the box above the line
const aboveLineTop = aboveAnchor.top;
const aboveLineTop = anchor.top;
const heightAvailableAboveLine = aboveLineTop;
// b) the box under the line
const underLineTop = belowAnchor.top;
const underLineTop = anchor.top + anchor.height;
const heightAvailableUnderLine = ctx.viewportHeight - underLineTop;
const aboveTop = aboveLineTop - height;
@@ -309,30 +299,15 @@ class Widget {
const fitsBelow = (heightAvailableUnderLine >= height);
// And its left
let actualAboveLeft = aboveAnchor.left;
let actualBelowLeft = belowAnchor.left;
if (actualAboveLeft + width > ctx.scrollLeft + ctx.viewportWidth) {
actualAboveLeft = ctx.scrollLeft + ctx.viewportWidth - width;
let left = anchor.left;
if (left + width > ctx.scrollLeft + ctx.viewportWidth) {
left = ctx.scrollLeft + ctx.viewportWidth - width;
}
if (actualBelowLeft + width > ctx.scrollLeft + ctx.viewportWidth) {
actualBelowLeft = ctx.scrollLeft + ctx.viewportWidth - width;
}
if (actualAboveLeft < ctx.scrollLeft) {
actualAboveLeft = ctx.scrollLeft;
}
if (actualBelowLeft < ctx.scrollLeft) {
actualBelowLeft = ctx.scrollLeft;
if (left < ctx.scrollLeft) {
left = ctx.scrollLeft;
}
return {
fitsAbove: fitsAbove,
aboveTop: aboveTop,
aboveLeft: actualAboveLeft,
fitsBelow: fitsBelow,
belowTop: belowTop,
belowLeft: actualBelowLeft,
};
return { fitsAbove, aboveTop, fitsBelow, belowTop, left };
}
private _layoutHorizontalSegmentInPage(windowSize: dom.Dimension, domNodePosition: dom.IDomNodePagePosition, left: number, width: number): [number, number] {
@@ -357,17 +332,16 @@ class Widget {
return [left, absoluteLeft];
}
private _layoutBoxInPage(aboveAnchor: Coordinate, belowAnchor: Coordinate, width: number, height: number, ctx: RenderingContext): IBoxLayoutResult | null {
const aboveTop = aboveAnchor.top - height;
const belowTop = belowAnchor.top;
private _layoutBoxInPage(anchor: AnchorCoordinate, width: number, height: number, ctx: RenderingContext): IBoxLayoutResult | null {
const aboveTop = anchor.top - height;
const belowTop = anchor.top + anchor.height;
const domNodePosition = dom.getDomNodePagePosition(this._viewDomNode.domNode);
const absoluteAboveTop = domNodePosition.top + aboveTop - window.scrollY;
const absoluteBelowTop = domNodePosition.top + belowTop - window.scrollY;
const windowSize = dom.getClientArea(document.body);
const [aboveLeft, absoluteAboveLeft] = this._layoutHorizontalSegmentInPage(windowSize, domNodePosition, aboveAnchor.left - ctx.scrollLeft + this._contentLeft, width);
const [belowLeft, absoluteBelowLeft] = this._layoutHorizontalSegmentInPage(windowSize, domNodePosition, belowAnchor.left - ctx.scrollLeft + this._contentLeft, width);
const [left, absoluteAboveLeft] = this._layoutHorizontalSegmentInPage(windowSize, domNodePosition, anchor.left - ctx.scrollLeft + this._contentLeft, width);
// Leave some clearance to the top/bottom
const TOP_PADDING = 22;
@@ -380,21 +354,13 @@ class Widget {
return {
fitsAbove,
aboveTop: Math.max(absoluteAboveTop, TOP_PADDING),
aboveLeft: absoluteAboveLeft,
fitsBelow,
belowTop: absoluteBelowTop,
belowLeft: absoluteBelowLeft
left: absoluteAboveLeft
};
}
return {
fitsAbove,
aboveTop,
aboveLeft,
fitsBelow,
belowTop,
belowLeft
};
return { fitsAbove, aboveTop, fitsBelow, belowTop, left };
}
private _prepareRenderWidgetAtExactPositionOverflowing(topLeft: Coordinate): Coordinate {
@@ -402,43 +368,47 @@ class Widget {
}
/**
* Compute the above and below anchors
* Compute the coordinates above and below the primary and secondary anchors.
* The content widget *must* touch the primary anchor.
* The content widget should touch if possible the secondary anchor.
*/
private _getAnchors(ctx: RenderingContext): [Coordinate | null, Coordinate | null] {
if (!this._viewRange) {
return [null, null];
}
private _getAnchorsCoordinates(ctx: RenderingContext): { primary: AnchorCoordinate | null; secondary: AnchorCoordinate | null } {
const primary = getCoordinates(this._primaryAnchor.viewPosition, this._affinity, this._lineHeight);
const secondaryViewPosition = (this._secondaryAnchor.viewPosition?.lineNumber === this._primaryAnchor.viewPosition?.lineNumber ? this._secondaryAnchor.viewPosition : null);
const secondary = getCoordinates(secondaryViewPosition, this._affinity, this._lineHeight);
return { primary, secondary };
const visibleRangesForRange = ctx.linesVisibleRangesForRange(this._viewRange, false);
const aboveAnchor = getCoordinate(LineVisibleRanges.firstLine(visibleRangesForRange), this._affinity, this._viewRange.startColumn, 0);
const belowAnchor = getCoordinate(LineVisibleRanges.lastLine(visibleRangesForRange), this._affinity, this._viewRange.startColumn, this._lineHeight);
return [aboveAnchor, belowAnchor];
function getCoordinate(line: LineVisibleRanges | null, affinity: PositionAffinity | null, startColumn: number, deltaTop: number): Coordinate | null {
if (!line) {
function getCoordinates(position: Position | null, affinity: PositionAffinity | null, lineHeight: number): AnchorCoordinate | null {
if (!position) {
return null;
}
const left = getLeft(line.ranges, affinity, startColumn);
const top = ctx.getVerticalOffsetForLineNumber(line.lineNumber) - ctx.scrollTop;
return new Coordinate(top + deltaTop, left);
const horizontalPosition = ctx.visibleRangeForPosition(position);
if (!horizontalPosition) {
return null;
}
// Left-align widgets that should appear :before content
const left = (position.column === 1 && affinity === PositionAffinity.LeftOfInjectedText ? 0 : horizontalPosition.left);
const top = ctx.getVerticalOffsetForLineNumber(position.lineNumber) - ctx.scrollTop;
return new AnchorCoordinate(top, left, lineHeight);
}
}
private _reduceAnchorCoordinates(primary: AnchorCoordinate, secondary: AnchorCoordinate | null, width: number): AnchorCoordinate {
if (!secondary) {
return primary;
}
/**
* Return the left-most position from `ranges`.
*/
function getLeft(ranges: HorizontalRange[], affinity: PositionAffinity | null, startColumn: number): number {
// Left-align widgets that should appear :before content
if (startColumn === 1 && affinity === PositionAffinity.LeftOfInjectedText) {
return 0;
}
let result = Constants.MAX_SAFE_SMALL_INTEGER;
for (const range of ranges) {
if (range.left < result) {
result = range.left;
}
}
return result;
const fontInfo = this._context.configuration.options.get(EditorOption.fontInfo);
let left = secondary.left;
if (left < primary.left) {
left = Math.max(left, primary.left - width + fontInfo.typicalFullwidthCharacterWidth);
} else {
left = Math.min(left, primary.left + width - fontInfo.typicalFullwidthCharacterWidth);
}
return new AnchorCoordinate(primary.top, left, primary.height);
}
private _prepareRenderWidget(ctx: RenderingContext): IRenderData | null {
@@ -446,8 +416,8 @@ class Widget {
return null;
}
const [aboveAnchor, belowAnchor] = this._getAnchors(ctx);
if (!aboveAnchor || !belowAnchor) {
const { primary, secondary } = this._getAnchorsCoordinates(ctx);
if (!primary) {
return null;
}
@@ -468,11 +438,13 @@ class Widget {
}
}
const anchor = this._reduceAnchorCoordinates(primary, secondary, this._cachedDomNodeOffsetWidth);
let placement: IBoxLayoutResult | null;
if (this.allowEditorOverflow) {
placement = this._layoutBoxInPage(aboveAnchor, belowAnchor, this._cachedDomNodeOffsetWidth, this._cachedDomNodeOffsetHeight, ctx);
placement = this._layoutBoxInPage(anchor, this._cachedDomNodeOffsetWidth, this._cachedDomNodeOffsetHeight, ctx);
} else {
placement = this._layoutBoxInViewport(aboveAnchor, belowAnchor, this._cachedDomNodeOffsetWidth, this._cachedDomNodeOffsetHeight, ctx);
placement = this._layoutBoxInViewport(anchor, this._cachedDomNodeOffsetWidth, this._cachedDomNodeOffsetHeight, ctx);
}
// Do two passes, first for perfect fit, second picks first option
@@ -485,7 +457,7 @@ class Widget {
return null;
}
if (pass === 2 || placement.fitsAbove) {
return { coordinate: new Coordinate(placement.aboveTop, placement.aboveLeft), position: ContentWidgetPositionPreference.ABOVE };
return { coordinate: new Coordinate(placement.aboveTop, placement.left), position: ContentWidgetPositionPreference.ABOVE };
}
} else if (pref === ContentWidgetPositionPreference.BELOW) {
if (!placement) {
@@ -493,13 +465,13 @@ class Widget {
return null;
}
if (pass === 2 || placement.fitsBelow) {
return { coordinate: new Coordinate(placement.belowTop, placement.belowLeft), position: ContentWidgetPositionPreference.BELOW };
return { coordinate: new Coordinate(placement.belowTop, placement.left), position: ContentWidgetPositionPreference.BELOW };
}
} else {
if (this.allowEditorOverflow) {
return { coordinate: this._prepareRenderWidgetAtExactPositionOverflowing(aboveAnchor), position: ContentWidgetPositionPreference.EXACT };
return { coordinate: this._prepareRenderWidgetAtExactPositionOverflowing(new Coordinate(anchor.top, anchor.left)), position: ContentWidgetPositionPreference.EXACT };
} else {
return { coordinate: aboveAnchor, position: ContentWidgetPositionPreference.EXACT };
return { coordinate: new Coordinate(anchor.top, anchor.left), position: ContentWidgetPositionPreference.EXACT };
}
}
}
@@ -512,11 +484,11 @@ class Widget {
* On this first pass, we ensure that the content widget (if it is in the viewport) has the max width set correctly.
*/
public onBeforeRender(viewportData: ViewportData): void {
if (!this._viewRange || !this._preference) {
if (!this._primaryAnchor.viewPosition || !this._preference) {
return;
}
if (this._viewRange.endLineNumber < viewportData.startLineNumber || this._viewRange.startLineNumber > viewportData.endLineNumber) {
if (this._primaryAnchor.viewPosition.lineNumber < viewportData.startLineNumber || this._primaryAnchor.viewPosition.lineNumber > viewportData.endLineNumber) {
// Outside of viewport
return;
}
@@ -564,6 +536,32 @@ class Widget {
}
}
class PositionPair {
constructor(
public readonly modelPosition: IPosition | null,
public readonly viewPosition: Position | null
) { }
}
class Coordinate {
_coordinateBrand: void = undefined;
constructor(
public readonly top: number,
public readonly left: number
) { }
}
class AnchorCoordinate {
_anchorCoordinateBrand: void = undefined;
constructor(
public readonly top: number,
public readonly left: number,
public readonly height: number
) { }
}
function safeInvoke<T extends (...args: any[]) => any>(fn: T, thisArg: ThisParameterType<T>, ...args: Parameters<T>): ReturnType<T> | null {
try {
return fn.call(thisArg, ...args);

View File

@@ -24,7 +24,6 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { Context as SuggestContext } from 'vs/editor/contrib/suggest/browser/suggest';
import { AsyncIterableObject } from 'vs/base/common/async';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { Constants } from 'vs/base/common/uint';
const $ = dom.$;
@@ -241,7 +240,7 @@ export class ContentHoverController extends Disposable {
}
private _renderMessages(anchor: HoverAnchor, messages: IHoverPart[]): void {
const { showAtPosition, showAtRange, highlightRange } = ContentHoverController.computeHoverRanges(this._editor, anchor.range, messages);
const { showAtPosition, showAtSecondaryPosition, highlightRange } = ContentHoverController.computeHoverRanges(this._editor, anchor.range, messages);
const disposables = new DisposableStore();
const statusBar = disposables.add(new EditorHoverStatusBar(this._keybindingService));
@@ -284,7 +283,7 @@ export class ContentHoverController extends Disposable {
this._widget.showAt(fragment, new ContentHoverVisibleData(
colorPicker,
showAtPosition,
showAtRange,
showAtSecondaryPosition,
this._editor.getOption(EditorOption.hover).above,
this._computer.shouldFocus,
isBeforeContent,
@@ -304,21 +303,17 @@ export class ContentHoverController extends Disposable {
public static computeHoverRanges(editor: ICodeEditor, anchorRange: Range, messages: IHoverPart[]) {
let startColumnBoundary = 1;
let endColumnBoundary = Constants.MAX_SAFE_SMALL_INTEGER;
if (editor.hasModel()) {
// Ensure the range is on the current view line
const viewModel = editor._getViewModel();
const coordinatesConverter = viewModel.coordinatesConverter;
const anchorViewRange = coordinatesConverter.convertModelRangeToViewRange(anchorRange);
const anchorViewRangeStart = new Position(anchorViewRange.startLineNumber, viewModel.getLineMinColumn(anchorViewRange.startLineNumber));
const anchorViewRangeEnd = new Position(anchorViewRange.endLineNumber, viewModel.getLineMaxColumn(anchorViewRange.endLineNumber));
startColumnBoundary = coordinatesConverter.convertViewPositionToModelPosition(anchorViewRangeStart).column;
endColumnBoundary = coordinatesConverter.convertViewPositionToModelPosition(anchorViewRangeEnd).column;
}
// The anchor range is always on a single line
const anchorLineNumber = anchorRange.startLineNumber;
let renderStartColumn = anchorRange.startColumn;
let renderEndColumn = anchorRange.endColumn;
let highlightRange: Range = messages[0].range;
let forceShowAtRange: Range | null = null;
@@ -327,7 +322,6 @@ export class ContentHoverController extends Disposable {
if (msg.range.startLineNumber === anchorLineNumber && msg.range.endLineNumber === anchorLineNumber) {
// this message has a range that is completely sitting on the line of the anchor
renderStartColumn = Math.max(Math.min(renderStartColumn, msg.range.startColumn), startColumnBoundary);
renderEndColumn = Math.min(Math.max(renderEndColumn, msg.range.endColumn), endColumnBoundary);
}
if (msg.forceShowAtRange) {
forceShowAtRange = msg.range;
@@ -335,8 +329,8 @@ export class ContentHoverController extends Disposable {
}
return {
showAtPosition: forceShowAtRange ? forceShowAtRange.getStartPosition() : new Position(anchorRange.startLineNumber, renderStartColumn),
showAtRange: forceShowAtRange ? forceShowAtRange : new Range(anchorLineNumber, renderStartColumn, anchorLineNumber, renderEndColumn),
showAtPosition: forceShowAtRange ? forceShowAtRange.getStartPosition() : new Position(anchorLineNumber, anchorRange.startColumn),
showAtSecondaryPosition: forceShowAtRange ? forceShowAtRange.getStartPosition() : new Position(anchorLineNumber, renderStartColumn),
highlightRange
};
}
@@ -382,7 +376,7 @@ class ContentHoverVisibleData {
constructor(
public readonly colorPicker: IEditorHoverColorPickerWidget | null,
public readonly showAtPosition: Position,
public readonly showAtRange: Range,
public readonly showAtSecondaryPosition: Position,
public readonly preferAbove: boolean,
public readonly stoleFocus: boolean,
public readonly isBeforeContent: boolean,
@@ -463,7 +457,7 @@ export class ContentHoverWidget extends Disposable implements IContentWidget {
return {
position: this._visibleData.showAtPosition,
range: this._visibleData.showAtRange,
secondaryPosition: this._visibleData.showAtSecondaryPosition,
preference: (
preferAbove
? [ContentWidgetPositionPreference.ABOVE, ContentWidgetPositionPreference.BELOW]

7
src/vs/monaco.d.ts vendored
View File

@@ -4780,10 +4780,11 @@ declare namespace monaco.editor {
*/
position: IPosition | null;
/**
* Optionally, a range can be provided to further
* define the position of the content widget.
* Optionally, a secondary position can be provided to further
* define the position of the content widget. The secondary position
* must have the same line number as the primary position.
*/
range?: IRange | null;
secondaryPosition?: IPosition | null;
/**
* Placement preference for position, in order of preference.
*/