Path: blob/main/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/hoverParticipant.ts
4780 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 * as dom from '../../../../../base/browser/dom.js';6import { MarkdownString } from '../../../../../base/common/htmlContent.js';7import { DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js';8import { autorun, autorunWithStore, constObservable } from '../../../../../base/common/observable.js';9import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from '../../../../browser/editorBrowser.js';10import { EditorOption } from '../../../../common/config/editorOptions.js';11import { Range } from '../../../../common/core/range.js';12import { IModelDecoration } from '../../../../common/model.js';13import { HoverAnchor, HoverAnchorType, HoverForeignElementAnchor, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart, IRenderedHoverPart, IRenderedHoverParts, RenderedHoverParts } from '../../../hover/browser/hoverTypes.js';14import { InlineCompletionsController } from '../controller/inlineCompletionsController.js';15import { InlineSuggestionHintsContentWidget } from './inlineCompletionsHintsWidget.js';16import { IMarkdownRendererService } from '../../../../../platform/markdown/browser/markdownRenderer.js';17import * as nls from '../../../../../nls.js';18import { IAccessibilityService } from '../../../../../platform/accessibility/common/accessibility.js';19import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';20import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';21import { GhostTextView } from '../view/ghostText/ghostTextView.js';2223export class InlineCompletionsHover implements IHoverPart {24constructor(25public readonly owner: IEditorHoverParticipant<InlineCompletionsHover>,26public readonly range: Range,27public readonly controller: InlineCompletionsController28) { }2930public isValidForHoverAnchor(anchor: HoverAnchor): boolean {31return (32anchor.type === HoverAnchorType.Range33&& this.range.startColumn <= anchor.range.startColumn34&& this.range.endColumn >= anchor.range.endColumn35);36}37}3839export class InlineCompletionsHoverParticipant implements IEditorHoverParticipant<InlineCompletionsHover> {4041public readonly hoverOrdinal: number = 4;4243constructor(44private readonly _editor: ICodeEditor,45@IAccessibilityService private readonly accessibilityService: IAccessibilityService,46@IInstantiationService private readonly _instantiationService: IInstantiationService,47@ITelemetryService private readonly _telemetryService: ITelemetryService,48@IMarkdownRendererService private readonly _markdownRendererService: IMarkdownRendererService,49) {50}5152suggestHoverAnchor(mouseEvent: IEditorMouseEvent): HoverAnchor | null {53const controller = InlineCompletionsController.get(this._editor);54if (!controller) {55return null;56}5758const target = mouseEvent.target;59if (target.type === MouseTargetType.CONTENT_VIEW_ZONE) {60// handle the case where the mouse is over the view zone61const viewZoneData = target.detail;62if (controller.shouldShowHoverAtViewZone(viewZoneData.viewZoneId)) {63return new HoverForeignElementAnchor(1000, this, Range.fromPositions(this._editor.getModel()!.validatePosition(viewZoneData.positionBefore || viewZoneData.position)), mouseEvent.event.posx, mouseEvent.event.posy, false);64}65}66if (target.type === MouseTargetType.CONTENT_EMPTY) {67// handle the case where the mouse is over the empty portion of a line following ghost text68if (controller.shouldShowHoverAt(target.range)) {69return new HoverForeignElementAnchor(1000, this, target.range, mouseEvent.event.posx, mouseEvent.event.posy, false);70}71}72if (target.type === MouseTargetType.CONTENT_TEXT) {73// handle the case where the mouse is directly over ghost text74const mightBeForeignElement = target.detail.mightBeForeignElement;75if (mightBeForeignElement && controller.shouldShowHoverAt(target.range)) {76return new HoverForeignElementAnchor(1000, this, target.range, mouseEvent.event.posx, mouseEvent.event.posy, false);77}78}79if (target.type === MouseTargetType.CONTENT_WIDGET && target.element) {80const ctx = GhostTextView.getWarningWidgetContext(target.element);81if (ctx && controller.shouldShowHoverAt(ctx.range)) {82return new HoverForeignElementAnchor(1000, this, ctx.range, mouseEvent.event.posx, mouseEvent.event.posy, false);83}84}85return null;86}8788computeSync(anchor: HoverAnchor, lineDecorations: IModelDecoration[]): InlineCompletionsHover[] {89if (this._editor.getOption(EditorOption.inlineSuggest).showToolbar !== 'onHover') {90return [];91}9293const controller = InlineCompletionsController.get(this._editor);94if (controller && controller.shouldShowHoverAt(anchor.range)) {95return [new InlineCompletionsHover(this, anchor.range, controller)];96}97return [];98}99100renderHoverParts(context: IEditorHoverRenderContext, hoverParts: InlineCompletionsHover[]): IRenderedHoverParts<InlineCompletionsHover> {101const disposables = new DisposableStore();102const part = hoverParts[0];103104this._telemetryService.publicLog2<{}, {105owner: 'hediet';106comment: 'This event tracks whenever an inline completion hover is shown.';107}>('inlineCompletionHover.shown');108109if (this.accessibilityService.isScreenReaderOptimized() && !this._editor.getOption(EditorOption.screenReaderAnnounceInlineSuggestion)) {110disposables.add(this.renderScreenReaderText(context, part));111}112113const model = part.controller.model.get()!;114const widgetNode: HTMLElement = document.createElement('div');115context.fragment.appendChild(widgetNode);116117disposables.add(autorunWithStore((reader, store) => {118const w = store.add(this._instantiationService.createInstance(119InlineSuggestionHintsContentWidget.hot.read(reader),120this._editor,121false,122constObservable(null),123model.selectedInlineCompletionIndex,124model.inlineCompletionsCount,125model.activeCommands,126model.warning,127() => {128context.onContentsChanged();129},130));131widgetNode.replaceChildren(w.getDomNode());132}));133134model.triggerExplicitly();135136const renderedHoverPart: IRenderedHoverPart<InlineCompletionsHover> = {137hoverPart: part,138hoverElement: widgetNode,139dispose() { disposables.dispose(); }140};141return new RenderedHoverParts([renderedHoverPart]);142}143144getAccessibleContent(hoverPart: InlineCompletionsHover): string {145return nls.localize('hoverAccessibilityStatusBar', 'There are inline completions here');146}147148private renderScreenReaderText(context: IEditorHoverRenderContext, part: InlineCompletionsHover): IDisposable {149const disposables = new DisposableStore();150const $ = dom.$;151const markdownHoverElement = $('div.hover-row.markdown-hover');152const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents', { ['aria-live']: 'assertive' }));153const render = (code: string) => {154const inlineSuggestionAvailable = nls.localize('inlineSuggestionFollows', "Suggestion:");155const renderedContents = disposables.add(this._markdownRendererService.render(new MarkdownString().appendText(inlineSuggestionAvailable).appendCodeblock('text', code), {156context: this._editor,157asyncRenderCallback: () => {158hoverContentsElement.className = 'hover-contents code-hover-contents';159context.onContentsChanged();160}161}));162hoverContentsElement.replaceChildren(renderedContents.element);163};164165disposables.add(autorun(reader => {166/** @description update hover */167const ghostText = part.controller.model.read(reader)?.primaryGhostText.read(reader);168if (ghostText) {169const lineText = this._editor.getModel()!.getLineContent(ghostText.lineNumber);170render(ghostText.renderForScreenReader(lineText));171} else {172dom.reset(hoverContentsElement);173}174}));175176context.fragment.appendChild(markdownHoverElement);177return disposables;178}179}180181182