Skip to content

Commit fbdf735

Browse files
committed
Add context provider support
1 parent 185207f commit fbdf735

File tree

6 files changed

+290
-86
lines changed

6 files changed

+290
-86
lines changed

package-lock.json

Lines changed: 66 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.262.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: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ 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 { registerCopilotExtensions } from './copilot/copilot';
3131

3232
let _channel: vscode.LogOutputChannel;
3333
let _traceChannel: vscode.OutputChannel;
@@ -59,21 +59,22 @@ export async function activateRoslynLanguageServer(
5959
context.extensionPath
6060
);
6161
const additionalExtensionPaths = scanExtensionPlugins();
62+
const copilotExtensionPath = getCopilotPluginPath();
6263

6364
const languageServer = await RoslynLanguageServer.initializeAsync(
6465
platformInfo,
6566
hostExecutableResolver,
6667
context,
6768
reporter,
68-
additionalExtensionPaths,
69+
additionalExtensionPaths.concat(copilotExtensionPath ? [copilotExtensionPath] : []),
6970
languageServerEvents,
7071
_channel,
7172
_traceChannel
7273
);
7374

7475
registerLanguageStatusItems(context, languageServer, languageServerEvents);
7576
registerMiscellaneousFileNotifier(context, languageServer);
76-
registerCopilotExtension(languageServer, _channel);
77+
registerCopilotExtensions(context, languageServer, copilotExtensionPath, _channel);
7778

7879
// Register any commands that need to be handled by the extension.
7980
registerCommands(context, languageServer, hostExecutableResolver, _channel);
@@ -117,6 +118,16 @@ export async function activateRoslynLanguageServer(
117118
const extensionsFromOptions = languageServerOptions.extensionsPaths ?? [];
118119
return extensionsFromPackageJson.concat(extensionsFromOptions);
119120
}
121+
122+
function getCopilotPluginPath(): string | undefined {
123+
const copilotLoadPath = getCSharpDevKit()?.packageJSON.contributes?.['csharpCopilotExtensionLoadPath'];
124+
if (copilotLoadPath) {
125+
_channel.trace(`CSharp DevKit contributes csharpCopilotExtensionLoadPath: ${copilotLoadPath}`);
126+
return path.join(getCSharpDevKit()!.extensionPath, copilotLoadPath);
127+
}
128+
129+
return undefined;
130+
}
120131
}
121132

122133
function registerLanguageStatusItems(
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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+
11+
export interface DocumentContext {
12+
textDocument: lsp.TextDocumentIdentifier;
13+
position: lsp.Position;
14+
}
15+
16+
export interface ContextResolveParam {
17+
documentContext: DocumentContext;
18+
completionId: string;
19+
timeBudget: number;
20+
data?: any;
21+
}
22+
23+
const resolveContextRequest = new lsp.RequestType<ContextResolveParam, SupportedContextItem[], void>(
24+
'roslyn/resolveContext',
25+
lsp.ParameterStructures.auto
26+
);
27+
28+
interface CopilotApi {
29+
getContextProviderAPI(version: string): Promise<ContextProviderApiV1 | undefined>;
30+
}
31+
32+
function createContextResolveParam(request: ResolveRequest): ContextResolveParam | undefined {
33+
let document: vscode.TextDocument | undefined;
34+
if (vscode.window.activeTextEditor?.document.uri.toString() === request.documentContext.uri) {
35+
document = vscode.window.activeTextEditor.document;
36+
} else {
37+
document = vscode.workspace.textDocuments.find((doc) => doc.uri.toString() === request.documentContext.uri);
38+
}
39+
if (document === undefined) {
40+
return undefined;
41+
}
42+
43+
const position = document.positionAt(request.documentContext.offset);
44+
const uri = vscode.Uri.parse(request.documentContext.uri);
45+
const textDocument = lsp.TextDocumentIdentifier.create(uri.fsPath);
46+
47+
const contextResolveParam: ContextResolveParam = {
48+
documentContext: {
49+
textDocument: textDocument,
50+
position: position,
51+
},
52+
completionId: request.completionId,
53+
timeBudget: request.timeBudget,
54+
};
55+
return contextResolveParam;
56+
}
57+
58+
export async function registerCopilotContextProviders(
59+
copilotExt: CopilotApi | undefined,
60+
context: vscode.ExtensionContext,
61+
languageServer: RoslynLanguageServer,
62+
channel: vscode.LogOutputChannel
63+
) {
64+
const contextProviderApi = await copilotExt?.getContextProviderAPI('v1');
65+
66+
if (!contextProviderApi) {
67+
channel.debug('Incompatible GitHub Copilot extension installed. Skip registeration of C# context providers.');
68+
return;
69+
}
70+
71+
context.subscriptions.push(
72+
contextProviderApi.registerContextProvider<SupportedContextItem>({
73+
id: CSharpExtensionId, // use extension id as provider id for now
74+
selector: [{ language: 'csharp' }],
75+
resolver: {
76+
resolve: async (request, token) => {
77+
const contextResolveParam = createContextResolveParam(request);
78+
if (!contextResolveParam) {
79+
return [];
80+
}
81+
const traits = await languageServer.sendRequest(resolveContextRequest, contextResolveParam, token);
82+
return traits;
83+
},
84+
},
85+
})
86+
);
87+
88+
channel.debug('Registration of C# context provider for GitHub Copilot extension succeeded.');
89+
}

src/lsptoolshost/copilot/copilot.ts

Lines changed: 22 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -3,97 +3,36 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import * as vscode from 'vscode';
7-
import { CSharpExtensionId } from '../../constants/csharpExtensionId';
8-
import { CopilotRelatedDocumentsReport, CopilotRelatedDocumentsRequest } from '../server/roslynProtocol';
96
import { RoslynLanguageServer } from '../server/roslynLanguageServer';
10-
import { UriConverter } from '../utils/uriConverter';
11-
import { TextDocumentIdentifier } from 'vscode-languageserver-protocol';
12-
13-
interface CopilotTrait {
14-
name: string;
15-
value: string;
16-
includeInPrompt?: boolean;
17-
promptTextOverride?: string;
18-
}
19-
20-
interface CopilotRelatedFilesProviderRegistration {
21-
registerRelatedFilesProvider(
22-
providerId: { extensionId: string; languageId: string },
23-
callback: (
24-
uri: vscode.Uri,
25-
context: { flags: Record<string, unknown> },
26-
cancellationToken?: vscode.CancellationToken
27-
) => Promise<{ entries: vscode.Uri[]; traits?: CopilotTrait[] }>
28-
): vscode.Disposable;
29-
}
7+
import * as vscode from 'vscode';
8+
import { registerCopilotContextProviders } from './contextProviders';
9+
import { registerCopilotRelatedFilesProvider } from './relatedFilesProvider';
3010

31-
export function registerCopilotExtension(languageServer: RoslynLanguageServer, channel: vscode.LogOutputChannel) {
11+
export function registerCopilotExtensions(
12+
context: vscode.ExtensionContext,
13+
languageServer: RoslynLanguageServer,
14+
copilotPluginPath: string | undefined,
15+
channel: vscode.LogOutputChannel
16+
) {
3217
const ext = vscode.extensions.getExtension('github.copilot');
3318
if (!ext) {
34-
channel.debug('GitHub Copilot extension not installed. Skip registeration of C# related files provider.');
19+
channel.debug('GitHub Copilot extension not installed. Skip registeration of Copilot related functionalities.');
3520
return;
3621
}
37-
ext.activate().then(() => {
38-
const relatedAPI = ext.exports as CopilotRelatedFilesProviderRegistration | undefined;
39-
if (!relatedAPI) {
40-
channel.debug(
41-
'Incompatible GitHub Copilot extension installed. Skip registeration of C# related files provider.'
42-
);
43-
return;
44-
}
45-
46-
channel.debug('registration of C# related files provider for GitHub Copilot extension succeeded.');
47-
48-
const id = {
49-
extensionId: CSharpExtensionId,
50-
languageId: 'csharp',
51-
};
5222

53-
relatedAPI.registerRelatedFilesProvider(id, async (uri, _, token) => {
54-
const buildResult = (
55-
activeDocumentUri: vscode.Uri,
56-
reports: CopilotRelatedDocumentsReport[],
57-
builder: vscode.Uri[]
58-
) => {
59-
if (reports) {
60-
for (const report of reports) {
61-
if (report._vs_file_paths) {
62-
for (const filePath of report._vs_file_paths) {
63-
// The Roslyn related document service would return the active document as related file to itself
64-
// if the code contains reference to the types defined in the same document. Skip it so the active file
65-
// won't be used as additonal context.
66-
const relatedUri = vscode.Uri.file(filePath);
67-
if (relatedUri.fsPath !== activeDocumentUri.fsPath) {
68-
builder.push(relatedUri);
69-
}
70-
}
71-
}
72-
}
73-
}
74-
};
75-
const relatedFiles: vscode.Uri[] = [];
76-
const uriString = UriConverter.serialize(uri);
77-
const textDocument = TextDocumentIdentifier.create(uriString);
23+
ext.activate().then(async (copilotExt) => {
24+
if (copilotPluginPath) {
7825
try {
79-
await languageServer.sendRequestWithProgress(
80-
CopilotRelatedDocumentsRequest.type,
81-
{
82-
_vs_textDocument: textDocument,
83-
position: {
84-
line: 0,
85-
character: 0,
86-
},
87-
},
88-
async (r) => buildResult(uri, r, relatedFiles),
89-
token
90-
);
91-
} catch (e) {
92-
if (e instanceof Error) {
93-
channel.appendLine(e.message);
94-
}
26+
await registerCopilotContextProviders(copilotExt, context, languageServer, channel);
27+
} catch (error) {
28+
channel.error('Failed to register Copilot context providers', error);
9529
}
96-
return { entries: relatedFiles };
97-
});
30+
}
31+
32+
try {
33+
await registerCopilotRelatedFilesProvider(copilotExt, context, languageServer, channel);
34+
} catch (error) {
35+
channel.error('Failed to register Copilot context providers', error);
36+
}
9837
});
9938
}

0 commit comments

Comments
 (0)