Skip to content

Commit 5caa088

Browse files
authored
Add context provider support (#8078)
2 parents ce7942c + 20630c0 commit 5caa088

File tree

7 files changed

+349
-106
lines changed

7 files changed

+349
-106
lines changed

package-lock.json

Lines changed: 67 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
"ms-dotnettools.vscode-dotnet-runtime"
8888
],
8989
"dependencies": {
90+
"@github/copilot-language-server": "1.290.0",
9091
"@microsoft/servicehub-framework": "4.2.99-beta",
9192
"@octokit/rest": "^20.0.1",
9293
"@types/cross-spawn": "6.0.2",

src/lsptoolshost/activate.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@ import { registerCodeActionFixAllCommands } from './diagnostics/fixAllCodeAction
2121
import { commonOptions, languageServerOptions } from '../shared/options';
2222
import { registerNestedCodeActionCommands } from './diagnostics/nestedCodeAction';
2323
import { registerRestoreCommands } from './projectRestore/restore';
24-
import { registerCopilotExtension } from './copilot/copilot';
2524
import { registerSourceGeneratedFilesContentProvider } from './generators/sourceGeneratedFilesContentProvider';
2625
import { registerMiscellaneousFileNotifier } from './workspace/miscellaneousFileNotifier';
2726
import { TelemetryEventNames } from '../shared/telemetryEventNames';
2827
import { WorkspaceStatus } from './workspace/workspaceStatus';
2928
import { ProjectContextStatus } from './projectContext/projectContextStatus';
3029
import { RoslynLanguageServer } from './server/roslynLanguageServer';
30+
import { registerCopilotRelatedFilesProvider } from './copilot/relatedFilesProvider';
31+
import { registerCopilotContextProviders } from './copilot/contextProviders';
3132

3233
let _channel: vscode.LogOutputChannel;
3334
let _traceChannel: vscode.OutputChannel;
@@ -73,7 +74,8 @@ export async function activateRoslynLanguageServer(
7374

7475
registerLanguageStatusItems(context, languageServer, languageServerEvents);
7576
registerMiscellaneousFileNotifier(context, languageServer);
76-
registerCopilotExtension(languageServer, _channel);
77+
registerCopilotRelatedFilesProvider(context, languageServer, _channel);
78+
registerCopilotContextProviders(context, languageServer, _channel);
7779

7880
// Register any commands that need to be handled by the extension.
7981
registerCommands(context, languageServer, hostExecutableResolver, _channel);
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
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+
import { ContextProviderApiV1, ResolveRequest, SupportedContextItem } from '@github/copilot-language-server';
6+
import * as vscode from 'vscode';
7+
import * as lsp from 'vscode-languageserver-protocol';
8+
import { RoslynLanguageServer } from '../server/roslynLanguageServer';
9+
import { CSharpExtensionId } from '../../constants/csharpExtensionId';
10+
import { csharpDevkitExtensionId, getCSharpDevKit } from '../../utils/getCSharpDevKit';
11+
import path from 'path';
12+
import { readJsonSync } from 'fs-extra';
13+
14+
export const copilotLanguageServerExtensionComponentName = '@microsoft/visualstudio.copilot.roslyn.languageserver';
15+
export const copilotLanguageServerExtensionAssemblyName = 'Microsoft.VisualStudio.Copilot.Roslyn.LanguageServer.dll';
16+
const copilotLanguageServerExtensionCapabilitiesFileName = 'capabilities.json';
17+
18+
export interface DocumentContext {
19+
textDocument: lsp.TextDocumentIdentifier;
20+
position: lsp.Position;
21+
}
22+
23+
export interface ContextResolveParam {
24+
documentContext: DocumentContext;
25+
completionId: string;
26+
timeBudget: number;
27+
data?: any;
28+
}
29+
30+
const resolveContextMethodName = 'roslyn/resolveContext';
31+
const resolveContextMethodSupportedVersion = '1';
32+
const resolveContextRequest = new lsp.RequestType<ContextResolveParam, SupportedContextItem[], void>(
33+
resolveContextMethodName,
34+
lsp.ParameterStructures.auto
35+
);
36+
37+
interface CopilotApi {
38+
getContextProviderAPI(version: string): Promise<ContextProviderApiV1 | undefined>;
39+
}
40+
41+
function createContextResolveParam(request: ResolveRequest): ContextResolveParam | undefined {
42+
let document: vscode.TextDocument | undefined;
43+
if (vscode.window.activeTextEditor?.document.uri.toString() === request.documentContext.uri) {
44+
document = vscode.window.activeTextEditor.document;
45+
} else {
46+
document = vscode.workspace.textDocuments.find((doc) => doc.uri.toString() === request.documentContext.uri);
47+
}
48+
if (document === undefined) {
49+
return undefined;
50+
}
51+
52+
const position = document.positionAt(request.documentContext.offset);
53+
const uri = vscode.Uri.parse(request.documentContext.uri);
54+
const textDocument = lsp.TextDocumentIdentifier.create(uri.fsPath);
55+
56+
const contextResolveParam: ContextResolveParam = {
57+
documentContext: {
58+
textDocument: textDocument,
59+
position: position,
60+
},
61+
completionId: request.completionId,
62+
timeBudget: request.timeBudget,
63+
};
64+
return contextResolveParam;
65+
}
66+
67+
export function registerCopilotContextProviders(
68+
context: vscode.ExtensionContext,
69+
languageServer: RoslynLanguageServer,
70+
channel: vscode.LogOutputChannel
71+
) {
72+
const devkit = getCSharpDevKit();
73+
if (!devkit) {
74+
return;
75+
}
76+
77+
devkit.activate().then(async (devKitExports) => {
78+
try {
79+
// Check if the Copilot Language Server extension is installed and has the correct capabilities
80+
let hasCapabilities = false;
81+
const copilotServerExtensionfolder = devKitExports.components[copilotLanguageServerExtensionComponentName];
82+
if (copilotServerExtensionfolder) {
83+
const capabilitiesFilePath = path.join(
84+
copilotServerExtensionfolder,
85+
copilotLanguageServerExtensionCapabilitiesFileName
86+
);
87+
const capabilitiesContent = await readJsonSync(capabilitiesFilePath);
88+
if (
89+
capabilitiesContent?.capabilities?.find(
90+
(capability: any) =>
91+
capability?.method === resolveContextMethodName &&
92+
capability?.version === resolveContextMethodSupportedVersion
93+
)
94+
) {
95+
hasCapabilities = true;
96+
}
97+
}
98+
99+
if (!hasCapabilities) {
100+
channel.debug(
101+
`Failed to find compatible version of context provider from installed version of ${csharpDevkitExtensionId}.`
102+
);
103+
return;
104+
}
105+
106+
const copilotApi = vscode.extensions.getExtension<CopilotApi>('github.copilot');
107+
if (!copilotApi) {
108+
channel.debug(
109+
'Failed to find compatible version of GitHub Copilot extension installed. Skip registeration of Copilot context provider.'
110+
);
111+
return;
112+
}
113+
114+
const api = await copilotApi.activate();
115+
const contextProviderApi = await api.getContextProviderAPI('v1');
116+
117+
if (!contextProviderApi) {
118+
channel.debug(
119+
'Incompatible GitHub Copilot extension installed. Skip registeration of C# context providers.'
120+
);
121+
return;
122+
}
123+
124+
context.subscriptions.push(
125+
contextProviderApi.registerContextProvider<SupportedContextItem>({
126+
id: CSharpExtensionId, // use extension id as provider id for now
127+
selector: [{ language: 'csharp' }],
128+
resolver: {
129+
resolve: async (request, token) => {
130+
const contextResolveParam = createContextResolveParam(request);
131+
if (!contextResolveParam) {
132+
return [];
133+
}
134+
const items = await languageServer.sendRequest(
135+
resolveContextRequest,
136+
contextResolveParam,
137+
token
138+
);
139+
channel.trace(`Copilot context provider resolved ${items.length} items`);
140+
return items;
141+
},
142+
},
143+
})
144+
);
145+
146+
channel.debug('Registration of C# context provider for GitHub Copilot extension succeeded.');
147+
} catch (error) {
148+
channel.error('Failed to register Copilot context providers', error);
149+
}
150+
});
151+
}

src/lsptoolshost/copilot/copilot.ts

Lines changed: 0 additions & 99 deletions
This file was deleted.

0 commit comments

Comments
 (0)