mirror of
https://github.com/Microsoft/vscode
synced 2024-10-05 19:02:54 +00:00
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:
parent
6facfe23b3
commit
6125cc2332
|
@ -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;
|
||||
|
|
193
src/vs/editor/contrib/hover/browser/contentHoverRendered.ts
Normal file
193
src/vs/editor/contrib/hover/browser/contentHoverRendered.ts
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
) { }
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue