Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/codeBlocks/vscode-node/provider.ts
13399 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 vscode from 'vscode';
7
import { ITelemetryService } from '../../../platform/telemetry/common/telemetry';
8
import { getLanguage, getLanguageForResource, ILanguage, WellKnownLanguageId, wellKnownLanguages } from '../../../util/common/languages';
9
import { isUri } from '../../../util/common/types';
10
import { createSha256Hash } from '../../../util/common/crypto';
11
import { IInstantiationService, ServicesAccessor } from '../../../util/vs/platform/instantiation/common/instantiation';
12
import { Location, Range, Uri } from '../../../vscodeTypes';
13
import { findWordInReferences } from '../../linkify/vscode-node/findWord';
14
import { PromptReference } from '../../prompt/common/conversation';
15
16
const codeBlockScheme = 'vscode-chat-code-block';
17
18
/**
19
* Hovers that are provided by a language provider in cases where the correct types are not known.
20
*
21
* A good example of this is how js/ts shows `any` for any unknown types. In these cases, we instead want to try looking
22
* up a more helpful hover using the workspace symbols.
23
*/
24
const genericHoverMessages: RegExp[] = [
25
/^\n```(typescript|javascript|tsx|jsx)\S*\nany\n```\n$/i,
26
];
27
28
/**
29
* Groupings of languages that can reference each other for intellisense.
30
*
31
* For example, when trying to look up a symbol in a JS code block, we shouldn't bother
32
* looking up symbols in c++ or markdown files.
33
*/
34
const languageReferenceGroups: readonly Set<string>[] = [
35
new Set<WellKnownLanguageId>([
36
'typescript',
37
'javascript',
38
'typescriptreact',
39
'javascriptreact',
40
]),
41
42
// Put all other languages in their own group
43
...Array.from(wellKnownLanguages.keys(), lang => new Set([lang]))
44
];
45
46
/**
47
* Provides support for Intellisense chat code blocks.
48
*/
49
class CodeBlockIntelliSenseProvider implements vscode.DefinitionProvider, vscode.ImplementationProvider, vscode.TypeDefinitionProvider, vscode.HoverProvider {
50
51
constructor(
52
@IInstantiationService private readonly instantiationService: IInstantiationService,
53
@ITelemetryService private readonly telemetryService: ITelemetryService,
54
) { }
55
56
async provideDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.LocationLink[] | undefined> {
57
return this.goTo('vscode.experimental.executeDefinitionProvider_recursive', document, position, token);
58
}
59
60
async provideImplementation(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.LocationLink[] | undefined> {
61
return this.goTo('vscode.experimental.executeImplementationProvider_recursive', document, position, token);
62
}
63
64
async provideTypeDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.LocationLink[] | undefined> {
65
return this.goTo('vscode.experimental.executeTypeDefinitionProvider_recursive', document, position, token);
66
}
67
68
async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.Hover | undefined> {
69
const localHoverResponse = await this.execHover(document.uri, position);
70
const localHovers = this.filterOutGenericHovers(localHoverResponse);
71
if (localHovers?.length) {
72
return this.convertHover(localHovers);
73
}
74
75
if (token.isCancellationRequested) {
76
return;
77
}
78
79
const referencesCtx = await this.getReferencesContext(document, position, token);
80
if (!referencesCtx || token.isCancellationRequested) {
81
return;
82
}
83
84
for (const wordMatch of referencesCtx.wordMatches) {
85
const hovers = await this.execHover(wordMatch.uri, wordMatch.range.start);
86
if (token.isCancellationRequested) {
87
return;
88
}
89
if (hovers?.length) {
90
return this.convertHover(hovers);
91
}
92
}
93
94
return this.convertHover(localHoverResponse);
95
}
96
97
private async execHover(uri: Uri, position: vscode.Position): Promise<vscode.Hover[]> {
98
return vscode.commands.executeCommand<vscode.Hover[]>('vscode.experimental.executeHoverProvider_recursive', uri, position);
99
}
100
101
private convertHover(hovers: readonly vscode.Hover[]): vscode.Hover | undefined {
102
return hovers.length ?
103
new vscode.Hover(hovers.flatMap(x => x.contents), hovers[0].range)
104
: undefined;
105
}
106
107
private filterOutGenericHovers(localHoverResponse: vscode.Hover[]): vscode.Hover[] {
108
return localHoverResponse.filter(hover => {
109
return hover.contents.some(entry => {
110
if (typeof entry === 'string') {
111
return entry.length;
112
}
113
114
if (!entry.value.length) {
115
return false;
116
}
117
118
for (const pattern of genericHoverMessages) {
119
if (pattern.test(entry.value)) {
120
return false;
121
}
122
}
123
124
return true;
125
});
126
});
127
}
128
129
130
private async goTo(command: string, document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.LocationLink[] | undefined> {
131
const codeBlockId = await createSha256Hash(document.uri.fragment);
132
if (token.isCancellationRequested) {
133
return;
134
}
135
136
/* __GDPR__
137
"codeBlock.action.goTo" : {
138
"owner": "mjbvz",
139
"comment": "Counts interactions with code blocks in chat responses",
140
"languageId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Language of the currently open document." },
141
"command": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The go to command being run." },
142
"codeBlockId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Unique hash of the code block." }
143
}
144
*/
145
this.telemetryService.sendMSFTTelemetryEvent('codeBlock.action.goTo', {
146
languageId: document.languageId,
147
command,
148
codeBlockId,
149
});
150
151
const localLocations = await this.executeGoToInChatBlocks(command, document, position);
152
if (localLocations?.length) {
153
return localLocations;
154
}
155
156
if (token.isCancellationRequested) {
157
return;
158
}
159
160
return this.executeGoToInChatReferences(command, document, position, token);
161
}
162
163
private async executeGoToInChatBlocks(command: string, document: vscode.TextDocument, position: vscode.Position): Promise<vscode.LocationLink[] | undefined> {
164
const result = await this.executeGoTo(command, document.uri, position);
165
return result?.map((result): vscode.LocationLink => {
166
if ('uri' in result) {
167
return {
168
targetRange: result.range,
169
targetUri: result.uri,
170
};
171
} else {
172
return result;
173
}
174
});
175
}
176
177
private async executeGoTo(command: string, uri: vscode.Uri, position: vscode.Position): Promise<Array<vscode.Location | vscode.LocationLink> | undefined> {
178
return vscode.commands.executeCommand<Array<vscode.Location | vscode.LocationLink>>(command, uri, position);
179
}
180
181
private async executeGoToInChatReferences(command: string, document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<Array<vscode.LocationLink> | undefined> {
182
const ctx = await this.getReferencesContext(document, position, token);
183
if (!ctx || token.isCancellationRequested) {
184
return;
185
}
186
187
for (const wordMatch of ctx.wordMatches) {
188
const result = await this.executeGoTo(command, wordMatch.uri, wordMatch.range.start);
189
if (token.isCancellationRequested) {
190
return;
191
}
192
193
if (result) {
194
return result.map((result): vscode.LocationLink => {
195
if ('uri' in result) {
196
return {
197
targetRange: result.range,
198
targetUri: result.uri,
199
originSelectionRange: ctx.wordRange,
200
};
201
} else {
202
return {
203
targetSelectionRange: result.targetSelectionRange,
204
targetRange: result.targetRange,
205
targetUri: result.targetUri,
206
originSelectionRange: ctx.wordRange,
207
};
208
}
209
});
210
}
211
}
212
213
return undefined;
214
}
215
216
private async getReferencesContext(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<{ wordRange: vscode.Range; wordMatches: vscode.Location[] } | undefined> {
217
const references = this.getReferences(document);
218
if (!references?.length) {
219
return;
220
}
221
222
const wordRange = document.getWordRangeAtPosition(position);
223
if (!wordRange) {
224
return;
225
}
226
227
const word = document.getText(wordRange);
228
const wordMatches = await this.instantiationService.invokeFunction(accessor => findWordInReferences(accessor, references, word, {}, token));
229
return { wordRange, wordMatches };
230
}
231
232
private getReferences(document: vscode.TextDocument): readonly PromptReference[] {
233
const refs = this.extractReferences(document);
234
235
// Filter out references that don't belong to the same language family
236
const docLang = getLanguage(document);
237
const docLangGroup = getReferenceGroupForLanguage(docLang);
238
if (!docLangGroup) {
239
// Unknown language so skip filtering
240
return refs;
241
}
242
243
return refs.filter(ref => {
244
const uri = refToUri(ref);
245
if (!uri) {
246
return false;
247
}
248
249
const lang = getLanguageForResource(uri);
250
if (!docLangGroup.has(lang.languageId)) {
251
return false;
252
}
253
254
return true;
255
});
256
}
257
258
private extractReferences(document: vscode.TextDocument): readonly PromptReference[] {
259
try {
260
const fragment = decodeURIComponent(document.uri.fragment);
261
const parsedFragment = JSON.parse(fragment);
262
return parsedFragment.references.map((ref: any): PromptReference => {
263
if ('range' in ref) {
264
return new PromptReference(new Location(
265
Uri.from(ref.uri),
266
new Range(ref.range.startLineNumber - 1, ref.range.startColumn - 1, ref.range.endLineNumber - 1, ref.range.endColumn - 1)));
267
} else {
268
return new PromptReference(Uri.from(ref.uri));
269
}
270
});
271
} catch {
272
return [];
273
}
274
}
275
}
276
277
function refToUri(ref: PromptReference) {
278
return isUri(ref.anchor)
279
? ref.anchor
280
: 'uri' in ref.anchor
281
? ref.anchor.uri
282
: 'value' in ref.anchor && isUri(ref.anchor.value) ? ref.anchor.value : undefined;
283
}
284
285
function getReferenceGroupForLanguage(docLang: ILanguage) {
286
return languageReferenceGroups.find(group => group.has(docLang.languageId));
287
}
288
289
export function register(accessor: ServicesAccessor): vscode.Disposable {
290
const goToProvider = accessor.get(IInstantiationService).createInstance(CodeBlockIntelliSenseProvider);
291
const selector: vscode.DocumentSelector = { scheme: codeBlockScheme, exclusive: true };
292
293
return vscode.Disposable.from(
294
vscode.languages.registerDefinitionProvider(selector, goToProvider),
295
vscode.languages.registerTypeDefinitionProvider(selector, goToProvider),
296
vscode.languages.registerImplementationProvider(selector, goToProvider),
297
vscode.languages.registerHoverProvider(selector, goToProvider),
298
);
299
}
300
301