Extracting the rendered hover data into separate class (#214419)

* adding some code

* polishing code

* polishing the code

* polishing the code

* polishing and moving to a new file

* adding e

* polishing the code

* polishing the code

* passing in directly the hover result

* removing the highlight range
This commit is contained in:
Aiday Marlen Kyzy 2024-06-07 11:38:41 +02:00 committed by GitHub
parent 6facfe23b3
commit 6125cc2332
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 266 additions and 207 deletions

View file

@ -5,15 +5,13 @@
import * as dom from 'vs/base/browser/dom';
import { KeyCode } from 'vs/base/common/keyCodes';
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { Disposable } from 'vs/base/common/lifecycle';
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { TokenizationRegistry } from 'vs/editor/common/languages';
import { HoverOperation, HoverStartMode, HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation';
import { HoverAnchor, HoverParticipantRegistry, HoverRangeAnchor, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart, IHoverWidget } from 'vs/editor/contrib/hover/browser/hoverTypes';
import { HoverAnchor, HoverParticipantRegistry, HoverRangeAnchor, IEditorHoverContext, IEditorHoverParticipant, IHoverPart, IHoverWidget } from 'vs/editor/contrib/hover/browser/hoverTypes';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { MarkdownHoverParticipant } from 'vs/editor/contrib/hover/browser/markdownHoverParticipant';
@ -21,10 +19,10 @@ import { InlayHintsHover } from 'vs/editor/contrib/inlayHints/browser/inlayHints
import { HoverVerbosityAction } from 'vs/editor/common/standalone/standaloneEnums';
import { ContentHoverWidget } from 'vs/editor/contrib/hover/browser/contentHoverWidget';
import { ContentHoverComputer } from 'vs/editor/contrib/hover/browser/contentHoverComputer';
import { ContentHoverVisibleData, HoverResult } from 'vs/editor/contrib/hover/browser/contentHoverTypes';
import { EditorHoverStatusBar } from 'vs/editor/contrib/hover/browser/contentHoverStatusBar';
import { HoverResult } from 'vs/editor/contrib/hover/browser/contentHoverTypes';
import { Emitter } from 'vs/base/common/event';
import { ColorHoverParticipant } from 'vs/editor/contrib/colorPicker/browser/colorHoverParticipant';
import { RenderedContentHover } from 'vs/editor/contrib/hover/browser/contentHoverRendered';
export class ContentHoverController extends Disposable implements IHoverWidget {
@ -177,9 +175,9 @@ export class ContentHoverController extends Disposable implements IHoverWidget {
}
this._currentResult = currentHoverResult;
if (this._currentResult) {
this._showHover(this._currentResult.anchor, this._currentResult.hoverParts);
this._showHover(this._currentResult);
} else {
this._contentHoverWidget.hide();
this._hideHover();
}
}
@ -221,18 +219,21 @@ export class ContentHoverController extends Disposable implements IHoverWidget {
this._setCurrentResult(hoverResult);
}
private _showHover(anchor: HoverAnchor, hoverParts: IHoverPart[]): void {
const fragment = document.createDocumentFragment();
const disposables = this._renderHoverPartsInFragment(fragment, hoverParts);
const fragmentHasContent = fragment.hasChildNodes();
if (fragmentHasContent) {
this._doShowHover(fragment, hoverParts, anchor, disposables);
private _showHover(hoverResult: HoverResult): void {
const context = this._getHoverContext();
const renderedHover = new RenderedContentHover(this._editor, hoverResult, this._participants, this._computer, context, this._keybindingService);
if (renderedHover.domNodeHasChildren) {
this._contentHoverWidget.show(renderedHover);
} else {
disposables.dispose();
renderedHover.dispose();
}
}
private _getHoverContext(fragment: DocumentFragment, statusBar: EditorHoverStatusBar): IEditorHoverRenderContext {
private _hideHover(): void {
this._contentHoverWidget.hide();
}
private _getHoverContext(): IEditorHoverContext {
const hide = () => {
this.hide();
};
@ -243,122 +244,9 @@ export class ContentHoverController extends Disposable implements IHoverWidget {
const setMinimumDimensions = (dimensions: dom.Dimension) => {
this._contentHoverWidget.setMinimumDimensions(dimensions);
};
const context: IEditorHoverRenderContext = { fragment, statusBar, hide, onContentsChanged, setMinimumDimensions };
return context;
return { hide, onContentsChanged, setMinimumDimensions };
}
private _renderHoverPartsInFragment(fragment: DocumentFragment, hoverParts: IHoverPart[]): DisposableStore {
const disposables = new DisposableStore();
const statusBar = new EditorHoverStatusBar(this._keybindingService);
const context = this._getHoverContext(fragment, statusBar);
disposables.add(this._renderHoverPartsUsingContext(context, hoverParts));
disposables.add(this._renderStatusBar(fragment, statusBar));
return disposables;
}
private _renderHoverPartsUsingContext(context: IEditorHoverRenderContext, hoverParts: IHoverPart[]): IDisposable {
const disposables = new DisposableStore();
for (const participant of this._participants) {
const hoverPartsForParticipant = hoverParts.filter(hoverPart => hoverPart.owner === participant);
const hasHoverPartsForParticipant = hoverPartsForParticipant.length > 0;
if (!hasHoverPartsForParticipant) {
continue;
}
disposables.add(participant.renderHoverParts(context, hoverPartsForParticipant));
}
return disposables;
}
private _renderStatusBar(fragment: DocumentFragment, statusBar: EditorHoverStatusBar): IDisposable {
if (!statusBar.hasContent) {
return Disposable.None;
}
fragment.appendChild(statusBar.hoverElement);
return statusBar;
}
private _doShowHover(fragment: DocumentFragment, hoverParts: IHoverPart[], anchor: HoverAnchor, disposables: DisposableStore): void {
const { showAtPosition, showAtSecondaryPosition, highlightRange } = ContentHoverController.computeHoverRanges(this._editor, anchor.range, hoverParts);
this._addEditorDecorations(highlightRange, disposables);
const initialMousePosX = anchor.initialMousePosX;
const initialMousePosY = anchor.initialMousePosY;
const preferAbove = this._editor.getOption(EditorOption.hover).above;
const stoleFocus = this._computer.shouldFocus;
const hoverSource = this._computer.source;
const isBeforeContent = hoverParts.some(m => m.isBeforeContent);
const contentHoverVisibleData = new ContentHoverVisibleData(
initialMousePosX,
initialMousePosY,
showAtPosition,
showAtSecondaryPosition,
preferAbove,
stoleFocus,
hoverSource,
isBeforeContent,
disposables
);
this._contentHoverWidget.showAt(fragment, contentHoverVisibleData);
}
private _addEditorDecorations(highlightRange: Range | undefined, disposables: DisposableStore) {
if (!highlightRange) {
return;
}
const highlightDecoration = this._editor.createDecorationsCollection();
highlightDecoration.set([{
range: highlightRange,
options: ContentHoverController._DECORATION_OPTIONS
}]);
disposables.add(toDisposable(() => {
highlightDecoration.clear();
}));
}
private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({
description: 'content-hover-highlight',
className: 'hoverHighlight'
});
public static computeHoverRanges(editor: ICodeEditor, anchorRange: Range, hoverParts: IHoverPart[]) {
let startColumnBoundary = 1;
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));
startColumnBoundary = coordinatesConverter.convertViewPositionToModelPosition(anchorViewRangeStart).column;
}
// The anchor range is always on a single line
const anchorLineNumber = anchorRange.startLineNumber;
let renderStartColumn = anchorRange.startColumn;
let highlightRange = hoverParts[0].range;
let forceShowAtRange = null;
for (const hoverPart of hoverParts) {
highlightRange = Range.plusRange(highlightRange, hoverPart.range);
const hoverRangeIsWithinAnchorLine = hoverPart.range.startLineNumber === anchorLineNumber && hoverPart.range.endLineNumber === anchorLineNumber;
if (hoverRangeIsWithinAnchorLine) {
// this message has a range that is completely sitting on the line of the anchor
renderStartColumn = Math.max(Math.min(renderStartColumn, hoverPart.range.startColumn), startColumnBoundary);
}
if (hoverPart.forceShowAtRange) {
forceShowAtRange = hoverPart.range;
}
}
const showAtPosition = forceShowAtRange ? forceShowAtRange.getStartPosition() : new Position(anchorLineNumber, anchorRange.startColumn);
const showAtSecondaryPosition = forceShowAtRange ? forceShowAtRange.getStartPosition() : new Position(anchorLineNumber, renderStartColumn);
return {
showAtPosition,
showAtSecondaryPosition,
highlightRange
};
}
public showsOrWillShow(mouseEvent: IEditorMouseEvent): boolean {
const isContentWidgetResizing = this._contentHoverWidget.isResizing;

View file

@ -0,0 +1,193 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEditorHoverContext, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart } from 'vs/editor/contrib/hover/browser/hoverTypes';
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { ContentHoverComputer } from 'vs/editor/contrib/hover/browser/contentHoverComputer';
import { EditorHoverStatusBar } from 'vs/editor/contrib/hover/browser/contentHoverStatusBar';
import { HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { HoverResult } from 'vs/editor/contrib/hover/browser/contentHoverTypes';
export class RenderedContentHover extends Disposable {
public closestMouseDistance: number | undefined;
public initialMousePosX: number | undefined;
public initialMousePosY: number | undefined;
public readonly showAtPosition: Position;
public readonly showAtSecondaryPosition: Position;
public readonly shouldFocus: boolean;
public readonly source: HoverStartSource;
public readonly shouldAppearBeforeContent: boolean;
private readonly _renderedHoverParts: RenderedContentHoverParts;
constructor(
editor: ICodeEditor,
hoverResult: HoverResult,
participants: IEditorHoverParticipant<IHoverPart>[],
computer: ContentHoverComputer,
context: IEditorHoverContext,
keybindingService: IKeybindingService
) {
super();
const anchor = hoverResult.anchor;
const parts = hoverResult.hoverParts;
this._renderedHoverParts = this._register(new RenderedContentHoverParts(
editor,
participants,
parts,
context,
keybindingService
));
const { showAtPosition, showAtSecondaryPosition } = RenderedContentHover.computeHoverPositions(editor, anchor.range, parts);
this.shouldAppearBeforeContent = parts.some(m => m.isBeforeContent);
this.showAtPosition = showAtPosition;
this.showAtSecondaryPosition = showAtSecondaryPosition;
this.initialMousePosX = anchor.initialMousePosX;
this.initialMousePosY = anchor.initialMousePosY;
this.shouldFocus = computer.shouldFocus;
this.source = computer.source;
}
public get domNode(): DocumentFragment {
return this._renderedHoverParts.domNode;
}
public get domNodeHasChildren(): boolean {
return this._renderedHoverParts.domNodeHasChildren;
}
public static computeHoverPositions(editor: ICodeEditor, anchorRange: Range, hoverParts: IHoverPart[]): { showAtPosition: Position; showAtSecondaryPosition: Position } {
let startColumnBoundary = 1;
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 anchorViewMinColumn = viewModel.getLineMinColumn(anchorViewRange.startLineNumber);
const anchorViewRangeStart = new Position(anchorViewRange.startLineNumber, anchorViewMinColumn);
startColumnBoundary = coordinatesConverter.convertViewPositionToModelPosition(anchorViewRangeStart).column;
}
// The anchor range is always on a single line
const anchorStartLineNumber = anchorRange.startLineNumber;
let secondaryPositionColumn = anchorRange.startColumn;
let forceShowAtRange: Range | undefined;
for (const hoverPart of hoverParts) {
const hoverPartRange = hoverPart.range;
const hoverPartRangeOnAnchorStartLine = hoverPartRange.startLineNumber === anchorStartLineNumber;
const hoverPartRangeOnAnchorEndLine = hoverPartRange.endLineNumber === anchorStartLineNumber;
const hoverPartRangeIsOnAnchorLine = hoverPartRangeOnAnchorStartLine && hoverPartRangeOnAnchorEndLine;
if (hoverPartRangeIsOnAnchorLine) {
// this message has a range that is completely sitting on the line of the anchor
const hoverPartStartColumn = hoverPartRange.startColumn;
const minSecondaryPositionColumn = Math.min(secondaryPositionColumn, hoverPartStartColumn);
secondaryPositionColumn = Math.max(minSecondaryPositionColumn, startColumnBoundary);
}
if (hoverPart.forceShowAtRange) {
forceShowAtRange = hoverPartRange;
}
}
let showAtPosition: Position;
let showAtSecondaryPosition: Position;
if (forceShowAtRange) {
const forceShowAtPosition = forceShowAtRange.getStartPosition();
showAtPosition = forceShowAtPosition;
showAtSecondaryPosition = forceShowAtPosition;
} else {
showAtPosition = anchorRange.getStartPosition();
showAtSecondaryPosition = new Position(anchorStartLineNumber, secondaryPositionColumn);
}
return {
showAtPosition,
showAtSecondaryPosition,
};
}
}
class RenderedContentHoverParts extends Disposable {
private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({
description: 'content-hover-highlight',
className: 'hoverHighlight'
});
private readonly _participants: IEditorHoverParticipant<IHoverPart>[];
private readonly _fragment: DocumentFragment;
constructor(
editor: ICodeEditor,
participants: IEditorHoverParticipant<IHoverPart>[],
hoverParts: IHoverPart[],
context: IEditorHoverContext,
keybindingService: IKeybindingService
) {
super();
this._participants = participants;
this._fragment = document.createDocumentFragment();
const statusBar = new EditorHoverStatusBar(keybindingService);
const hoverContext: IEditorHoverRenderContext = { fragment: this._fragment, statusBar, ...context };
this._register(this._renderHoverParts(hoverContext, hoverParts));
this._register(this._renderStatusBar(this._fragment, statusBar));
this._register(this._addEditorDecorations(editor, hoverParts));
}
private _addEditorDecorations(editor: ICodeEditor, hoverParts: IHoverPart[]): IDisposable {
if (hoverParts.length === 0) {
return Disposable.None;
}
let highlightRange = hoverParts[0].range;
for (const hoverPart of hoverParts) {
const hoverPartRange = hoverPart.range;
highlightRange = Range.plusRange(highlightRange, hoverPartRange);
}
const highlightDecoration = editor.createDecorationsCollection();
highlightDecoration.set([{
range: highlightRange,
options: RenderedContentHoverParts._DECORATION_OPTIONS
}]);
return toDisposable(() => {
highlightDecoration.clear();
});
}
private _renderHoverParts(context: IEditorHoverRenderContext, hoverParts: IHoverPart[]): IDisposable {
const disposables = new DisposableStore();
for (const participant of this._participants) {
const hoverPartsForParticipant = hoverParts.filter(hoverPart => hoverPart.owner === participant);
const hasHoverPartsForParticipant = hoverPartsForParticipant.length > 0;
if (!hasHoverPartsForParticipant) {
continue;
}
disposables.add(participant.renderHoverParts(context, hoverPartsForParticipant));
}
return disposables;
}
private _renderStatusBar(fragment: DocumentFragment, statusBar: EditorHoverStatusBar): IDisposable {
if (!statusBar.hasContent) {
return Disposable.None;
}
fragment.appendChild(statusBar.hoverElement);
return statusBar;
}
public get domNode(): DocumentFragment {
return this._fragment;
}
public get domNodeHasChildren(): boolean {
return this._fragment.hasChildNodes();
}
}

View file

@ -3,9 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DisposableStore } from 'vs/base/common/lifecycle';
import { Position } from 'vs/editor/common/core/position';
import { HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation';
import { HoverAnchor, IHoverPart } from 'vs/editor/contrib/hover/browser/hoverTypes';
export class HoverResult {
@ -40,20 +37,3 @@ export class FilteredHoverResult extends HoverResult {
return this.original.filter(anchor);
}
}
export class ContentHoverVisibleData {
public closestMouseDistance: number | undefined = undefined;
constructor(
public initialMousePosX: number | undefined,
public initialMousePosY: number | undefined,
public readonly showAtPosition: Position,
public readonly showAtSecondaryPosition: Position,
public readonly preferAbove: boolean,
public readonly stoleFocus: boolean,
public readonly source: HoverStartSource,
public readonly isBeforeContent: boolean,
public readonly disposables: DisposableStore
) { }
}

View file

@ -15,8 +15,8 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { getHoverAccessibleViewHint, HoverWidget } from 'vs/base/browser/ui/hover/hoverWidget';
import { PositionAffinity } from 'vs/editor/common/model';
import { ContentHoverVisibleData } from 'vs/editor/contrib/hover/browser/contentHoverTypes';
import { Emitter } from 'vs/base/common/event';
import { RenderedContentHover } from 'vs/editor/contrib/hover/browser/contentHoverRendered';
const HORIZONTAL_SCROLLING_BY = 30;
const CONTAINER_HEIGHT_PADDING = 6;
@ -26,7 +26,7 @@ export class ContentHoverWidget extends ResizableContentWidget {
public static ID = 'editor.contrib.resizableContentHoverWidget';
private static _lastDimensions: dom.Dimension = new dom.Dimension(0, 0);
private _visibleData: ContentHoverVisibleData | undefined;
private _renderedHover: RenderedContentHover | undefined;
private _positionPreference: ContentWidgetPositionPreference | undefined;
private _minimumSize: dom.Dimension;
private _contentWidth: number | undefined;
@ -39,7 +39,7 @@ export class ContentHoverWidget extends ResizableContentWidget {
public readonly onDidResize = this._onDidResize.event;
public get isVisibleFromKeyboard(): boolean {
return (this._visibleData?.source === HoverStartSource.Keyboard);
return (this._renderedHover?.source === HoverStartSource.Keyboard);
}
public get isVisible(): boolean {
@ -86,13 +86,13 @@ export class ContentHoverWidget extends ResizableContentWidget {
this._register(focusTracker.onDidBlur(() => {
this._hoverFocusedKey.set(false);
}));
this._setHoverData(undefined);
this._setRenderedHover(undefined);
this._editor.addContentWidget(this);
}
public override dispose(): void {
super.dispose();
this._visibleData?.disposables.dispose();
this._renderedHover?.dispose();
this._editor.removeContentWidget(this);
}
@ -162,7 +162,7 @@ export class ContentHoverWidget extends ResizableContentWidget {
}
private _findAvailableSpaceVertically(): number | undefined {
const position = this._visibleData?.showAtPosition;
const position = this._renderedHover?.showAtPosition;
if (!position) {
return;
}
@ -223,23 +223,20 @@ export class ContentHoverWidget extends ResizableContentWidget {
public isMouseGettingCloser(posx: number, posy: number): boolean {
if (!this._visibleData) {
if (!this._renderedHover) {
return false;
}
if (
typeof this._visibleData.initialMousePosX === 'undefined'
|| typeof this._visibleData.initialMousePosY === 'undefined'
) {
this._visibleData.initialMousePosX = posx;
this._visibleData.initialMousePosY = posy;
if (this._renderedHover.initialMousePosX === undefined || this._renderedHover.initialMousePosY === undefined) {
this._renderedHover.initialMousePosX = posx;
this._renderedHover.initialMousePosY = posy;
return false;
}
const widgetRect = dom.getDomNodePagePosition(this.getDomNode());
if (typeof this._visibleData.closestMouseDistance === 'undefined') {
this._visibleData.closestMouseDistance = computeDistanceFromPointToRectangle(
this._visibleData.initialMousePosX,
this._visibleData.initialMousePosY,
if (this._renderedHover.closestMouseDistance === undefined) {
this._renderedHover.closestMouseDistance = computeDistanceFromPointToRectangle(
this._renderedHover.initialMousePosX,
this._renderedHover.initialMousePosY,
widgetRect.left,
widgetRect.top,
widgetRect.width,
@ -255,20 +252,20 @@ export class ContentHoverWidget extends ResizableContentWidget {
widgetRect.width,
widgetRect.height
);
if (distance > this._visibleData.closestMouseDistance + 4 /* tolerance of 4 pixels */) {
if (distance > this._renderedHover.closestMouseDistance + 4 /* tolerance of 4 pixels */) {
// The mouse is getting farther away
return false;
}
this._visibleData.closestMouseDistance = Math.min(this._visibleData.closestMouseDistance, distance);
this._renderedHover.closestMouseDistance = Math.min(this._renderedHover.closestMouseDistance, distance);
return true;
}
private _setHoverData(hoverData: ContentHoverVisibleData | undefined): void {
this._visibleData?.disposables.dispose();
this._visibleData = hoverData;
this._hoverVisibleKey.set(!!hoverData);
this._hover.containerDomNode.classList.toggle('hidden', !hoverData);
private _setRenderedHover(renderedHover: RenderedContentHover | undefined): void {
this._renderedHover?.dispose();
this._renderedHover = renderedHover;
this._hoverVisibleKey.set(!!renderedHover);
this._hover.containerDomNode.classList.toggle('hidden', !renderedHover);
}
private _updateFont(): void {
@ -298,10 +295,10 @@ export class ContentHoverWidget extends ResizableContentWidget {
this._setHoverWidgetMaxDimensions(width, height);
}
private _render(node: DocumentFragment, hoverData: ContentHoverVisibleData) {
this._setHoverData(hoverData);
private _render(renderedHover: RenderedContentHover) {
this._setRenderedHover(renderedHover);
this._updateFont();
this._updateContent(node);
this._updateContent(renderedHover.domNode);
this._updateMaxDimensions();
this.onContentsChanged();
// Simply force a synchronous render on the editor
@ -310,30 +307,30 @@ export class ContentHoverWidget extends ResizableContentWidget {
}
override getPosition(): IContentWidgetPosition | null {
if (!this._visibleData) {
if (!this._renderedHover) {
return null;
}
return {
position: this._visibleData.showAtPosition,
secondaryPosition: this._visibleData.showAtSecondaryPosition,
positionAffinity: this._visibleData.isBeforeContent ? PositionAffinity.LeftOfInjectedText : undefined,
position: this._renderedHover.showAtPosition,
secondaryPosition: this._renderedHover.showAtSecondaryPosition,
positionAffinity: this._renderedHover.shouldAppearBeforeContent ? PositionAffinity.LeftOfInjectedText : undefined,
preference: [this._positionPreference ?? ContentWidgetPositionPreference.ABOVE]
};
}
public showAt(node: DocumentFragment, hoverData: ContentHoverVisibleData): void {
public show(renderedHover: RenderedContentHover): void {
if (!this._editor || !this._editor.hasModel()) {
return;
}
this._render(node, hoverData);
this._render(renderedHover);
const widgetHeight = dom.getTotalHeight(this._hover.containerDomNode);
const widgetPosition = hoverData.showAtPosition;
const widgetPosition = renderedHover.showAtPosition;
this._positionPreference = this._findPositionPreference(widgetHeight, widgetPosition) ?? ContentWidgetPositionPreference.ABOVE;
// See https://github.com/microsoft/vscode/issues/140339
// TODO: Doing a second layout of the hover after force rendering the editor
this.onContentsChanged();
if (hoverData.stoleFocus) {
if (renderedHover.shouldFocus) {
this._hover.containerDomNode.focus();
}
this._onDidResize.fire();
@ -350,16 +347,16 @@ export class ContentHoverWidget extends ResizableContentWidget {
}
public hide(): void {
if (!this._visibleData) {
if (!this._renderedHover) {
return;
}
const stoleFocus = this._visibleData.stoleFocus || this._hoverFocusedKey.get();
this._setHoverData(undefined);
const hoverStoleFocus = this._renderedHover.shouldFocus || this._hoverFocusedKey.get();
this._setRenderedHover(undefined);
this._resizableNode.maxSize = new dom.Dimension(Infinity, Infinity);
this._resizableNode.clearSashHoverState();
this._hoverFocusedKey.set(false);
this._editor.layoutContentWidget(this);
if (stoleFocus) {
if (hoverStoleFocus) {
this._editor.focus();
}
}
@ -406,9 +403,9 @@ export class ContentHoverWidget extends ResizableContentWidget {
this._updateMinimumWidth();
this._resizableNode.layout(height, width);
if (this._visibleData?.showAtPosition) {
if (this._renderedHover?.showAtPosition) {
const widgetHeight = dom.getTotalHeight(this._hover.containerDomNode);
this._positionPreference = this._findPositionPreference(widgetHeight, this._visibleData.showAtPosition);
this._positionPreference = this._findPositionPreference(widgetHeight, this._renderedHover.showAtPosition);
}
this._layoutContentWidget();
}

View file

@ -94,15 +94,7 @@ export interface IEditorHoverColorPickerWidget {
layout(): void;
}
export interface IEditorHoverRenderContext {
/**
* The fragment where dom elements should be attached.
*/
readonly fragment: DocumentFragment;
/**
* The status bar for actions for this hover.
*/
readonly statusBar: IEditorHoverStatusBar;
export interface IEditorHoverContext {
/**
* The contents rendered inside the fragment have been changed, which means that the hover should relayout.
*/
@ -117,6 +109,17 @@ export interface IEditorHoverRenderContext {
hide(): void;
}
export interface IEditorHoverRenderContext extends IEditorHoverContext {
/**
* The fragment where dom elements should be attached.
*/
readonly fragment: DocumentFragment;
/**
* The status bar for actions for this hover.
*/
readonly statusBar: IEditorHoverStatusBar;
}
export interface IEditorHoverParticipant<T extends IHoverPart = IHoverPart> {
readonly hoverOrdinal: number;
suggestHoverAnchor?(mouseEvent: IEditorMouseEvent): HoverAnchor | null;

View file

@ -7,7 +7,7 @@ import assert from 'assert';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { ContentHoverController } from 'vs/editor/contrib/hover/browser/contentHoverController';
import { RenderedContentHover } from 'vs/editor/contrib/hover/browser/contentHoverRendered';
import { IHoverPart } from 'vs/editor/contrib/hover/browser/hoverTypes';
import { TestCodeEditorInstantiationOptions, withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
@ -18,7 +18,7 @@ suite('Content Hover', () => {
test('issue #151235: Gitlens hover shows up in the wrong place', () => {
const text = 'just some text';
withTestCodeEditor(text, {}, (editor) => {
const actual = ContentHoverController.computeHoverRanges(
const actual = RenderedContentHover.computeHoverPositions(
editor,
new Range(5, 5, 5, 5),
[<IHoverPart>{ range: new Range(4, 1, 5, 6) }]
@ -27,8 +27,7 @@ suite('Content Hover', () => {
actual,
{
showAtPosition: new Position(5, 5),
showAtSecondaryPosition: new Position(5, 5),
highlightRange: new Range(4, 1, 5, 6)
showAtSecondaryPosition: new Position(5, 5)
}
);
});
@ -38,7 +37,7 @@ suite('Content Hover', () => {
const text = 'just some text';
const opts: TestCodeEditorInstantiationOptions = { wordWrap: 'wordWrapColumn', wordWrapColumn: 6 };
withTestCodeEditor(text, opts, (editor) => {
const actual = ContentHoverController.computeHoverRanges(
const actual = RenderedContentHover.computeHoverPositions(
editor,
new Range(1, 8, 1, 8),
[<IHoverPart>{ range: new Range(1, 1, 1, 15) }]
@ -47,8 +46,7 @@ suite('Content Hover', () => {
actual,
{
showAtPosition: new Position(1, 8),
showAtSecondaryPosition: new Position(1, 6),
highlightRange: new Range(1, 1, 1, 15)
showAtSecondaryPosition: new Position(1, 6)
}
);
});