Path: blob/main/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts
5285 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { AsyncIterableProducer } from '../../../../base/common/async.js';6import { CancellationToken } from '../../../../base/common/cancellation.js';7import { IMarkdownString, isEmptyMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js';8import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from '../../../browser/editorBrowser.js';9import { Position } from '../../../common/core/position.js';10import { IModelDecoration } from '../../../common/model.js';11import { ModelDecorationInjectedTextOptions } from '../../../common/model/textModel.js';12import { HoverAnchor, HoverForeignElementAnchor, IEditorHoverParticipant } from '../../hover/browser/hoverTypes.js';13import { ITextModelService } from '../../../common/services/resolverService.js';14import { getHoverProviderResultsAsAsyncIterable } from '../../hover/browser/getHover.js';15import { MarkdownHover, MarkdownHoverParticipant } from '../../hover/browser/markdownHoverParticipant.js';16import { RenderedInlayHintLabelPart, InlayHintsController } from './inlayHintsController.js';17import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';18import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';19import { EditorOption } from '../../../common/config/editorOptions.js';20import { localize } from '../../../../nls.js';21import * as platform from '../../../../base/common/platform.js';22import { asCommandLink } from './inlayHints.js';23import { isNonEmptyArray } from '../../../../base/common/arrays.js';24import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';25import { IHoverService } from '../../../../platform/hover/browser/hover.js';26import { ICommandService } from '../../../../platform/commands/common/commands.js';27import { HoverStartSource } from '../../hover/browser/hoverOperation.js';28import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js';2930class InlayHintsHoverAnchor extends HoverForeignElementAnchor {31constructor(32readonly part: RenderedInlayHintLabelPart,33owner: InlayHintsHover,34initialMousePosX: number | undefined,35initialMousePosY: number | undefined36) {37super(10, owner, part.item.anchor.range, initialMousePosX, initialMousePosY, true);38}39}4041export class InlayHintsHover extends MarkdownHoverParticipant implements IEditorHoverParticipant<MarkdownHover> {4243public override readonly hoverOrdinal: number = 6;4445constructor(46editor: ICodeEditor,47@IMarkdownRendererService markdownRendererService: IMarkdownRendererService,48@IKeybindingService keybindingService: IKeybindingService,49@IHoverService hoverService: IHoverService,50@IConfigurationService configurationService: IConfigurationService,51@ITextModelService private readonly _resolverService: ITextModelService,52@ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService,53@ICommandService commandService: ICommandService,54) {55super(editor, markdownRendererService, configurationService, languageFeaturesService, keybindingService, hoverService, commandService);56}5758suggestHoverAnchor(mouseEvent: IEditorMouseEvent): HoverAnchor | null {59const controller = InlayHintsController.get(this._editor);60if (!controller) {61return null;62}63if (mouseEvent.target.type !== MouseTargetType.CONTENT_TEXT) {64return null;65}66const options = mouseEvent.target.detail.injectedText?.options;67if (!(options instanceof ModelDecorationInjectedTextOptions && options.attachedData instanceof RenderedInlayHintLabelPart)) {68return null;69}70return new InlayHintsHoverAnchor(options.attachedData, this, mouseEvent.event.posx, mouseEvent.event.posy);71}7273override computeSync(): MarkdownHover[] {74return [];75}7677override computeAsync(anchor: HoverAnchor, _lineDecorations: IModelDecoration[], source: HoverStartSource, token: CancellationToken): AsyncIterableProducer<MarkdownHover> {78if (!(anchor instanceof InlayHintsHoverAnchor)) {79return AsyncIterableProducer.EMPTY;80}8182return new AsyncIterableProducer<MarkdownHover>(async executor => {8384const { part } = anchor;85await part.item.resolve(token);8687if (token.isCancellationRequested) {88return;89}9091// (1) Inlay Tooltip92let itemTooltip: IMarkdownString | undefined;93if (typeof part.item.hint.tooltip === 'string') {94itemTooltip = new MarkdownString().appendText(part.item.hint.tooltip);95} else if (part.item.hint.tooltip) {96itemTooltip = part.item.hint.tooltip;97}98if (itemTooltip) {99executor.emitOne(new MarkdownHover(this, anchor.range, [itemTooltip], false, 0));100}101// (1.2) Inlay dbl-click gesture102if (isNonEmptyArray(part.item.hint.textEdits)) {103executor.emitOne(new MarkdownHover(this, anchor.range, [new MarkdownString().appendText(localize('hint.dbl', "Double-click to insert"))], false, 10001));104}105106// (2) Inlay Label Part Tooltip107let partTooltip: IMarkdownString | undefined;108if (typeof part.part.tooltip === 'string') {109partTooltip = new MarkdownString().appendText(part.part.tooltip);110} else if (part.part.tooltip) {111partTooltip = part.part.tooltip;112}113if (partTooltip) {114executor.emitOne(new MarkdownHover(this, anchor.range, [partTooltip], false, 1));115}116117// (2.2) Inlay Label Part Help Hover118if (part.part.location || part.part.command) {119let linkHint: MarkdownString | undefined;120const useMetaKey = this._editor.getOption(EditorOption.multiCursorModifier) === 'altKey';121const kb = useMetaKey122? platform.isMacintosh123? localize('links.navigate.kb.meta.mac', "cmd + click")124: localize('links.navigate.kb.meta', "ctrl + click")125: platform.isMacintosh126? localize('links.navigate.kb.alt.mac', "option + click")127: localize('links.navigate.kb.alt', "alt + click");128129if (part.part.location && part.part.command) {130linkHint = new MarkdownString().appendText(localize('hint.defAndCommand', 'Go to Definition ({0}), right click for more', kb));131} else if (part.part.location) {132linkHint = new MarkdownString().appendText(localize('hint.def', 'Go to Definition ({0})', kb));133} else if (part.part.command) {134linkHint = new MarkdownString(`[${localize('hint.cmd', "Execute Command")}](${asCommandLink(part.part.command)} "${part.part.command.title}") (${kb})`, { isTrusted: true });135}136if (linkHint) {137executor.emitOne(new MarkdownHover(this, anchor.range, [linkHint], false, 10000));138}139}140141142// (3) Inlay Label Part Location tooltip143const iterable = this._resolveInlayHintLabelPartHover(part, token);144for await (const item of iterable) {145executor.emitOne(item);146}147});148}149150private async *_resolveInlayHintLabelPartHover(part: RenderedInlayHintLabelPart, token: CancellationToken): AsyncIterable<MarkdownHover> {151if (!part.part.location) {152return;153}154155const { uri, range } = part.part.location;156const ref = await this._resolverService.createModelReference(uri);157try {158const model = ref.object.textEditorModel;159if (!this._languageFeaturesService.hoverProvider.has(model)) {160return;161}162163for await (const item of getHoverProviderResultsAsAsyncIterable(this._languageFeaturesService.hoverProvider, model, new Position(range.startLineNumber, range.startColumn), token)) {164if (!isEmptyMarkdownString(item.hover.contents)) {165yield new MarkdownHover(this, part.item.anchor.range, item.hover.contents, false, 2 + item.ordinal);166}167}168} finally {169ref.dispose();170}171}172}173174175