Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/languageContextProvider/vscode-node/languageContextProviderService.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 { type CancellationToken, languages, type TextDocument, type Disposable as VscodeDisposable } from 'vscode';
7
import { Copilot } from '../../../platform/inlineCompletions/common/api';
8
import { ILanguageContextProviderService, ProviderTarget } from '../../../platform/languageContextProvider/common/languageContextProviderService';
9
import { ContextItem, ContextKind, KnownSources, SnippetContext, TraitContext } from '../../../platform/languageServer/common/languageContextService';
10
import { filterMap } from '../../../util/common/arrays';
11
import { AsyncIterableObject } from '../../../util/vs/base/common/async';
12
import { Disposable, toDisposable } from '../../../util/vs/base/common/lifecycle';
13
import { URI } from '../../../util/vs/base/common/uri';
14
15
export class LanguageContextProviderService extends Disposable implements ILanguageContextProviderService {
16
_serviceBrand: undefined;
17
18
private providers: { provider: Copilot.ContextProvider<Copilot.SupportedContextItem>; targets: ProviderTarget[] }[] = [];
19
20
public registerContextProvider<T extends Copilot.SupportedContextItem>(provider: Copilot.ContextProvider<T>, targets: ProviderTarget[]): VscodeDisposable {
21
if (targets.length === 0) {
22
throw new Error('At least one ProviderTarget must be specified when registering a context provider.');
23
}
24
25
this.providers.push({ provider, targets });
26
return toDisposable(() => {
27
const index = this.providers.findIndex(p => p.provider === provider);
28
if (index > -1) {
29
this.providers.splice(index, 1);
30
}
31
});
32
}
33
34
public getAllProviders(target: ProviderTarget[]): readonly Copilot.ContextProvider<Copilot.SupportedContextItem>[] {
35
return this.providers.filter(p => target.some(t => p.targets.includes(t))).map(p => p.provider);
36
}
37
38
public getContextProviders(doc: TextDocument, target: ProviderTarget): Copilot.ContextProvider<Copilot.SupportedContextItem>[] {
39
return this.getAllProviders([target]).filter(provider => languages.match(provider.selector, doc));
40
}
41
42
public override dispose(): void {
43
super.dispose();
44
this.providers.length = 0;
45
}
46
47
public getContextItems(doc: TextDocument, request: Copilot.ResolveRequest, cancellationToken: CancellationToken): AsyncIterable<ContextItem> {
48
const providers = this.getContextProviders(doc, request.source === KnownSources.nes ? ProviderTarget.NES : ProviderTarget.Completions);
49
50
const items = new AsyncIterableObject<Copilot.SupportedContextItem>(async emitter => {
51
async function runProvider(provider: Copilot.ContextProvider<Copilot.SupportedContextItem>) {
52
const langCtx = provider.resolver.resolve(request, cancellationToken);
53
if (typeof (langCtx as any)[Symbol.asyncIterator] === 'function') {
54
for await (const context of langCtx as AsyncIterable<Copilot.SupportedContextItem>) {
55
emitter.emitOne(context);
56
}
57
return;
58
}
59
const result = await langCtx;
60
if (Array.isArray(result)) {
61
for (const context of result) {
62
emitter.emitOne(context);
63
}
64
} else if (typeof (result as any)[Symbol.asyncIterator] !== 'function') {
65
// Only push if it's a single SupportedContextItem, not an AsyncIterable
66
emitter.emitOne(result as Copilot.SupportedContextItem);
67
}
68
}
69
70
await Promise.allSettled(providers.map(runProvider));
71
});
72
73
const contextItems = items.map(v => LanguageContextProviderService.convertCopilotContextItem(v));
74
75
return contextItems;
76
}
77
78
private static convertCopilotContextItem(item: Copilot.SupportedContextItem): ContextItem {
79
const isSnippet = item && typeof item === 'object' && (item as any).uri !== undefined;
80
if (isSnippet) {
81
const ctx = item as Copilot.CodeSnippet;
82
return {
83
kind: ContextKind.Snippet,
84
priority: LanguageContextProviderService.convertImportanceToPriority(ctx.importance),
85
uri: URI.parse(ctx.uri),
86
value: ctx.value,
87
additionalUris: ctx.additionalUris?.map(uri => URI.parse(uri)),
88
} satisfies SnippetContext;
89
} else {
90
const ctx = item as Copilot.Trait;
91
return {
92
kind: ContextKind.Trait,
93
priority: LanguageContextProviderService.convertImportanceToPriority(ctx.importance),
94
name: ctx.name,
95
value: ctx.value,
96
} satisfies TraitContext;
97
}
98
}
99
100
// importance is coined by the copilot extension and must be an integer in [0, 100], while priority is by the chat extension and spans [0, 1]
101
private static convertImportanceToPriority(importance: number | undefined): number {
102
if (importance === undefined || importance < 0) {
103
return 0;
104
}
105
if (importance > 100) {
106
return 1;
107
}
108
return importance / 100;
109
}
110
111
public getContextItemsOnTimeout(doc: TextDocument, request: Copilot.ResolveRequest): ContextItem[] {
112
const providers = this.getContextProviders(doc, request.source === KnownSources.nes ? ProviderTarget.NES : ProviderTarget.Completions);
113
114
const unprocessedResults = filterMap(providers, p => p.resolver.resolveOnTimeout?.(request));
115
116
const copilotCtxItems = unprocessedResults.flat();
117
118
const ctxItems = copilotCtxItems.map(v => LanguageContextProviderService.convertCopilotContextItem(v));
119
120
return ctxItems;
121
}
122
123
}
124
125