Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/hoverParticipant.ts
4780 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as dom from '../../../../../base/browser/dom.js';
7
import { MarkdownString } from '../../../../../base/common/htmlContent.js';
8
import { DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js';
9
import { autorun, autorunWithStore, constObservable } from '../../../../../base/common/observable.js';
10
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from '../../../../browser/editorBrowser.js';
11
import { EditorOption } from '../../../../common/config/editorOptions.js';
12
import { Range } from '../../../../common/core/range.js';
13
import { IModelDecoration } from '../../../../common/model.js';
14
import { HoverAnchor, HoverAnchorType, HoverForeignElementAnchor, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart, IRenderedHoverPart, IRenderedHoverParts, RenderedHoverParts } from '../../../hover/browser/hoverTypes.js';
15
import { InlineCompletionsController } from '../controller/inlineCompletionsController.js';
16
import { InlineSuggestionHintsContentWidget } from './inlineCompletionsHintsWidget.js';
17
import { IMarkdownRendererService } from '../../../../../platform/markdown/browser/markdownRenderer.js';
18
import * as nls from '../../../../../nls.js';
19
import { IAccessibilityService } from '../../../../../platform/accessibility/common/accessibility.js';
20
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
21
import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';
22
import { GhostTextView } from '../view/ghostText/ghostTextView.js';
23
24
export class InlineCompletionsHover implements IHoverPart {
25
constructor(
26
public readonly owner: IEditorHoverParticipant<InlineCompletionsHover>,
27
public readonly range: Range,
28
public readonly controller: InlineCompletionsController
29
) { }
30
31
public isValidForHoverAnchor(anchor: HoverAnchor): boolean {
32
return (
33
anchor.type === HoverAnchorType.Range
34
&& this.range.startColumn <= anchor.range.startColumn
35
&& this.range.endColumn >= anchor.range.endColumn
36
);
37
}
38
}
39
40
export class InlineCompletionsHoverParticipant implements IEditorHoverParticipant<InlineCompletionsHover> {
41
42
public readonly hoverOrdinal: number = 4;
43
44
constructor(
45
private readonly _editor: ICodeEditor,
46
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
47
@IInstantiationService private readonly _instantiationService: IInstantiationService,
48
@ITelemetryService private readonly _telemetryService: ITelemetryService,
49
@IMarkdownRendererService private readonly _markdownRendererService: IMarkdownRendererService,
50
) {
51
}
52
53
suggestHoverAnchor(mouseEvent: IEditorMouseEvent): HoverAnchor | null {
54
const controller = InlineCompletionsController.get(this._editor);
55
if (!controller) {
56
return null;
57
}
58
59
const target = mouseEvent.target;
60
if (target.type === MouseTargetType.CONTENT_VIEW_ZONE) {
61
// handle the case where the mouse is over the view zone
62
const viewZoneData = target.detail;
63
if (controller.shouldShowHoverAtViewZone(viewZoneData.viewZoneId)) {
64
return new HoverForeignElementAnchor(1000, this, Range.fromPositions(this._editor.getModel()!.validatePosition(viewZoneData.positionBefore || viewZoneData.position)), mouseEvent.event.posx, mouseEvent.event.posy, false);
65
}
66
}
67
if (target.type === MouseTargetType.CONTENT_EMPTY) {
68
// handle the case where the mouse is over the empty portion of a line following ghost text
69
if (controller.shouldShowHoverAt(target.range)) {
70
return new HoverForeignElementAnchor(1000, this, target.range, mouseEvent.event.posx, mouseEvent.event.posy, false);
71
}
72
}
73
if (target.type === MouseTargetType.CONTENT_TEXT) {
74
// handle the case where the mouse is directly over ghost text
75
const mightBeForeignElement = target.detail.mightBeForeignElement;
76
if (mightBeForeignElement && controller.shouldShowHoverAt(target.range)) {
77
return new HoverForeignElementAnchor(1000, this, target.range, mouseEvent.event.posx, mouseEvent.event.posy, false);
78
}
79
}
80
if (target.type === MouseTargetType.CONTENT_WIDGET && target.element) {
81
const ctx = GhostTextView.getWarningWidgetContext(target.element);
82
if (ctx && controller.shouldShowHoverAt(ctx.range)) {
83
return new HoverForeignElementAnchor(1000, this, ctx.range, mouseEvent.event.posx, mouseEvent.event.posy, false);
84
}
85
}
86
return null;
87
}
88
89
computeSync(anchor: HoverAnchor, lineDecorations: IModelDecoration[]): InlineCompletionsHover[] {
90
if (this._editor.getOption(EditorOption.inlineSuggest).showToolbar !== 'onHover') {
91
return [];
92
}
93
94
const controller = InlineCompletionsController.get(this._editor);
95
if (controller && controller.shouldShowHoverAt(anchor.range)) {
96
return [new InlineCompletionsHover(this, anchor.range, controller)];
97
}
98
return [];
99
}
100
101
renderHoverParts(context: IEditorHoverRenderContext, hoverParts: InlineCompletionsHover[]): IRenderedHoverParts<InlineCompletionsHover> {
102
const disposables = new DisposableStore();
103
const part = hoverParts[0];
104
105
this._telemetryService.publicLog2<{}, {
106
owner: 'hediet';
107
comment: 'This event tracks whenever an inline completion hover is shown.';
108
}>('inlineCompletionHover.shown');
109
110
if (this.accessibilityService.isScreenReaderOptimized() && !this._editor.getOption(EditorOption.screenReaderAnnounceInlineSuggestion)) {
111
disposables.add(this.renderScreenReaderText(context, part));
112
}
113
114
const model = part.controller.model.get()!;
115
const widgetNode: HTMLElement = document.createElement('div');
116
context.fragment.appendChild(widgetNode);
117
118
disposables.add(autorunWithStore((reader, store) => {
119
const w = store.add(this._instantiationService.createInstance(
120
InlineSuggestionHintsContentWidget.hot.read(reader),
121
this._editor,
122
false,
123
constObservable(null),
124
model.selectedInlineCompletionIndex,
125
model.inlineCompletionsCount,
126
model.activeCommands,
127
model.warning,
128
() => {
129
context.onContentsChanged();
130
},
131
));
132
widgetNode.replaceChildren(w.getDomNode());
133
}));
134
135
model.triggerExplicitly();
136
137
const renderedHoverPart: IRenderedHoverPart<InlineCompletionsHover> = {
138
hoverPart: part,
139
hoverElement: widgetNode,
140
dispose() { disposables.dispose(); }
141
};
142
return new RenderedHoverParts([renderedHoverPart]);
143
}
144
145
getAccessibleContent(hoverPart: InlineCompletionsHover): string {
146
return nls.localize('hoverAccessibilityStatusBar', 'There are inline completions here');
147
}
148
149
private renderScreenReaderText(context: IEditorHoverRenderContext, part: InlineCompletionsHover): IDisposable {
150
const disposables = new DisposableStore();
151
const $ = dom.$;
152
const markdownHoverElement = $('div.hover-row.markdown-hover');
153
const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents', { ['aria-live']: 'assertive' }));
154
const render = (code: string) => {
155
const inlineSuggestionAvailable = nls.localize('inlineSuggestionFollows', "Suggestion:");
156
const renderedContents = disposables.add(this._markdownRendererService.render(new MarkdownString().appendText(inlineSuggestionAvailable).appendCodeblock('text', code), {
157
context: this._editor,
158
asyncRenderCallback: () => {
159
hoverContentsElement.className = 'hover-contents code-hover-contents';
160
context.onContentsChanged();
161
}
162
}));
163
hoverContentsElement.replaceChildren(renderedContents.element);
164
};
165
166
disposables.add(autorun(reader => {
167
/** @description update hover */
168
const ghostText = part.controller.model.read(reader)?.primaryGhostText.read(reader);
169
if (ghostText) {
170
const lineText = this._editor.getModel()!.getLineContent(ghostText.lineNumber);
171
render(ghostText.renderForScreenReader(lineText));
172
} else {
173
dom.reset(hoverContentsElement);
174
}
175
}));
176
177
context.fragment.appendChild(markdownHoverElement);
178
return disposables;
179
}
180
}
181
182