Skip to content

Commit b53ad49

Browse files
committed
add custom snippets PoC
1 parent c9cae0b commit b53ad49

File tree

6 files changed

+140
-13
lines changed

6 files changed

+140
-13
lines changed

Extension/.vscode/launch.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,17 @@
1414
"--skip-release-notes",
1515
"--disable-workspace-trust",
1616
"--extensionDevelopmentPath=${workspaceFolder}",
17+
"--extensionDevelopmentPath=C:/Users/lucappa/.vscode-insiders/extensions/copilot-client",
18+
"--disable-extension=ms.vscode.cpptools",
19+
"--disable-extension=github.synth-lab",
20+
"--disable-extension=github.copilot",
21+
"--disable-extension=github.copilot-nightly",
22+
"--log=github.copilot:debug",
1723
],
1824
"sourceMaps": true,
1925
"outFiles": [
20-
"${workspaceFolder}/dist/**"
26+
"${workspaceFolder}/dist/**",
27+
"C:/Users/lucappa/.vscode-insiders/extensions/copilot-client/dist/**"
2128
],
2229
// you can use a watch task as a prelaunch task and it works like you'd want it to.
2330
"preLaunchTask": "watch"

Extension/Extension.code-workspace

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"folders": [
3+
{
4+
"path": "."
5+
},
6+
{
7+
"path": "../../Users/lucappa/.vscode-insiders/extensions/copilot-client"
8+
}
9+
],
10+
"settings": {
11+
"typescript.tsdk": "./node_modules/typescript/lib"
12+
}
13+
}

Extension/src/LanguageServer/client.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ import { Location, TextEdit, WorkspaceEdit } from './commonTypes';
5555
import * as configs from './configurations';
5656
import { DataBinding } from './dataBinding';
5757
import { cachedEditorConfigSettings, getEditorConfigSettings } from './editorConfig';
58-
import { CppSourceStr, clients, configPrefix, updateLanguageConfigurations, usesCrashHandler, watchForCrashes } from './extension';
58+
import { CppSourceStr, SnippetEntry, clients, configPrefix, updateLanguageConfigurations, usesCrashHandler, watchForCrashes } from './extension';
5959
import { LocalizeStringParams, getLocaleId, getLocalizedString } from './localization';
6060
import { PersistentFolderState, PersistentWorkspaceState } from './persistentState';
6161
import { RequestCancelled, ServerCancelled, createProtocolFilter } from './protocolFilter';
@@ -541,6 +541,15 @@ export interface ChatContextResult {
541541
targetArchitecture: string;
542542
}
543543

544+
export interface CompletionContextsResult {
545+
context: SnippetEntry[];
546+
}
547+
548+
export interface CompletionContextParams {
549+
file: string;
550+
caretOffset: number;
551+
}
552+
544553
// Requests
545554
const PreInitializationRequest: RequestType<void, string, void> = new RequestType<void, string, void>('cpptools/preinitialize');
546555
const InitializationRequest: RequestType<CppInitializationParams, void, void> = new RequestType<CppInitializationParams, void, void>('cpptools/initialize');
@@ -561,7 +570,7 @@ const GenerateDoxygenCommentRequest: RequestType<GenerateDoxygenCommentParams, G
561570
const ChangeCppPropertiesRequest: RequestType<CppPropertiesParams, void, void> = new RequestType<CppPropertiesParams, void, void>('cpptools/didChangeCppProperties');
562571
const IncludesRequest: RequestType<GetIncludesParams, GetIncludesResult, void> = new RequestType<GetIncludesParams, GetIncludesResult, void>('cpptools/getIncludes');
563572
const CppContextRequest: RequestType<void, ChatContextResult, void> = new RequestType<void, ChatContextResult, void>('cpptools/getChatContext');
564-
573+
const CompletionContextRequest: RequestType<CompletionContextParams, CompletionContextsResult, void> = new RequestType<CompletionContextParams, CompletionContextsResult, void>('cpptools/getCompletionContext');
565574
// Notifications to the server
566575
const DidOpenNotification: NotificationType<DidOpenTextDocumentParams> = new NotificationType<DidOpenTextDocumentParams>('textDocument/didOpen');
567576
const FileCreatedNotification: NotificationType<FileChangedParams> = new NotificationType<FileChangedParams>('cpptools/fileCreated');
@@ -792,6 +801,7 @@ export interface Client {
792801
addTrustedCompiler(path: string): Promise<void>;
793802
getIncludes(maxDepth: number, token: vscode.CancellationToken): Promise<GetIncludesResult>;
794803
getChatContext(token: vscode.CancellationToken): Promise<ChatContextResult>;
804+
getCompletionContext(fileName: vscode.Uri, caretOffset: number, token: vscode.CancellationToken): Promise<CompletionContextsResult>;
795805
}
796806

797807
export function createClient(workspaceFolder?: vscode.WorkspaceFolder): Client {
@@ -2220,6 +2230,12 @@ export class DefaultClient implements Client {
22202230
() => this.languageClient.sendRequest(CppContextRequest, null, token), token);
22212231
}
22222232

2233+
public async getCompletionContext(file: vscode.Uri, caretOffset: number, token: vscode.CancellationToken): Promise<CompletionContextsResult> {
2234+
await withCancellation(this.ready, token);
2235+
return DefaultClient.withLspCancellationHandling(
2236+
() => this.languageClient.sendRequest(CompletionContextRequest, { file: file.toString(), caretOffset }, token), token);
2237+
}
2238+
22232239
/**
22242240
* a Promise that can be awaited to know when it's ok to proceed.
22252241
*
@@ -4123,4 +4139,5 @@ class NullClient implements Client {
41234139
addTrustedCompiler(path: string): Promise<void> { return Promise.resolve(); }
41244140
getIncludes(maxDepth: number, token: vscode.CancellationToken): Promise<GetIncludesResult> { return Promise.resolve({} as GetIncludesResult); }
41254141
getChatContext(token: vscode.CancellationToken): Promise<ChatContextResult> { return Promise.resolve({} as ChatContextResult); }
4142+
getCompletionContext(file: vscode.Uri, caretOffset: number, token: vscode.CancellationToken): Promise<CompletionContextsResult> { return Promise.resolve({} as CompletionContextsResult); }
41264143
}

Extension/src/LanguageServer/copilotProviders.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import * as vscode from 'vscode';
88
import * as util from '../common';
99
import { ChatContextResult, GetIncludesResult } from './client';
10-
import { getActiveClient } from './extension';
10+
import { getActiveClient, SnippetEntry } from './extension';
1111

1212
export interface CopilotTrait {
1313
name: string;
@@ -25,6 +25,22 @@ export interface CopilotApi {
2525
cancellationToken: vscode.CancellationToken
2626
) => Promise<{ entries: vscode.Uri[]; traits?: CopilotTrait[] }>
2727
): Disposable;
28+
registerRelatedFilesProvider(
29+
providerId: { extensionId: string; languageId: string },
30+
callback: (
31+
uri: vscode.Uri,
32+
context: { flags: Record<string, unknown> },
33+
cancellationToken: vscode.CancellationToken
34+
) => Promise<{ entries: vscode.Uri[]; traits?: CopilotTrait[] }>
35+
): Disposable;
36+
registerSnippetsProvider(
37+
providerId: { extensionId: string; languageId: string },
38+
callback: (
39+
uri: vscode.Uri,
40+
context: { flags: Record<string, unknown> },
41+
cancellationToken: vscode.CancellationToken
42+
) => Promise<{ entries: SnippetEntry[] }>
43+
): Disposable;
2844
}
2945

3046
export async function registerRelatedFilesProvider(): Promise<void> {

Extension/src/LanguageServer/extension.ts

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ import * as util from '../common';
2020
import { getCrashCallStacksChannel } from '../logger';
2121
import { PlatformInformation } from '../platform';
2222
import * as telemetry from '../telemetry';
23-
import { Client, DefaultClient, DoxygenCodeActionCommandArguments, openFileVersions } from './client';
23+
import { Client, CompletionContextsResult, DefaultClient, DoxygenCodeActionCommandArguments, GetIncludesResult, openFileVersions } from './client';
2424
import { ClientCollection } from './clientCollection';
2525
import { CodeActionDiagnosticInfo, CodeAnalysisDiagnosticIdentifiersAndUri, codeAnalysisAllFixes, codeAnalysisCodeToFixes, codeAnalysisFileToCodeActions } from './codeAnalysis';
26-
import { registerRelatedFilesProvider } from './copilotProviders';
26+
import { getCopilotApi, registerRelatedFilesProvider } from './copilotProviders';
2727
import { CppBuildTaskProvider } from './cppBuildTaskProvider';
2828
import { getCustomConfigProviders } from './customProviders';
2929
import { getLanguageConfig } from './languageConfig';
@@ -34,6 +34,21 @@ import { CppSettings } from './settings';
3434
import { LanguageStatusUI, getUI } from './ui';
3535
import { makeLspRange, rangeEquals, showInstallCompilerWalkthrough } from './utils';
3636

37+
/*
38+
interface CopilotTrait {
39+
name: string;
40+
value: string;
41+
includeInPrompt?: boolean;
42+
promptTextOverride?: string;
43+
}*/
44+
45+
export interface SnippetEntry {
46+
uri: string;
47+
text: string;
48+
startLine: number;
49+
endLine: number;
50+
}
51+
3752
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
3853
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
3954
export const CppSourceStr: string = "C/C++";
@@ -264,6 +279,26 @@ export async function activate(): Promise<void> {
264279
}
265280

266281
await registerRelatedFilesProvider();
282+
283+
const isCustomSnippetProviderApiEnabled = await telemetry.isExperimentEnabled("CppToolsCustomSnippetsApi");
284+
if (isCustomSnippetProviderApiEnabled) {
285+
const api = await getCopilotApi();
286+
if (util.extensionContext && api) {
287+
try {
288+
for (const languageId of ['c', 'cpp', 'cuda-cpp']) {
289+
api.registerSnippetsProvider(
290+
{ extensionId: util.extensionContext.extension.id, languageId },
291+
async (uri: vscode.Uri, context: { flags: Record<string, unknown> }, token: vscode.CancellationToken) => {
292+
const result = await getCompletionContextWithCancellation(context.flags['caretOffset'] as number, token);
293+
return { entries: result.context };
294+
}
295+
);
296+
}
297+
} catch {
298+
console.log("Failed to register Copilot related files provider.");
299+
}
300+
}
301+
}
267302
}
268303

269304
export function updateLanguageConfigurations(): void {
@@ -276,8 +311,8 @@ export function updateLanguageConfigurations(): void {
276311
}
277312

278313
/**
279-
* workspace events
280-
*/
314+
* workspace events
315+
*/
281316
async function onDidChangeSettings(event: vscode.ConfigurationChangeEvent): Promise<void> {
282317
const client: Client = clients.getDefaultClient();
283318
if (client instanceof DefaultClient) {
@@ -488,9 +523,9 @@ async function onSwitchHeaderSource(): Promise<void> {
488523
}
489524

490525
/**
491-
* Allow the user to select a workspace when multiple workspaces exist and get the corresponding Client back.
492-
* The resulting client is used to handle some command that was previously invoked.
493-
*/
526+
* Allow the user to select a workspace when multiple workspaces exist and get the corresponding Client back.
527+
* The resulting client is used to handle some command that was previously invoked.
528+
*/
494529
async function selectClient(): Promise<Client> {
495530
if (clients.Count === 1) {
496531
return clients.ActiveClient;
@@ -1387,3 +1422,39 @@ export async function preReleaseCheck(): Promise<void> {
13871422
}
13881423
}
13891424
}
1425+
1426+
export async function getIncludesWithCancellation(maxDepth: number, token: vscode.CancellationToken): Promise<GetIncludesResult> {
1427+
const includes = await clients.ActiveClient.getIncludes(maxDepth, token);
1428+
const wksFolder = clients.ActiveClient.RootUri?.toString();
1429+
1430+
if (!wksFolder) {
1431+
return includes;
1432+
}
1433+
1434+
includes.includedFiles = includes.includedFiles.filter(header => vscode.Uri.file(header).toString().startsWith(wksFolder));
1435+
return includes;
1436+
}
1437+
1438+
export async function getCompletionContextWithCancellation(caretOffset: number, token: vscode.CancellationToken): Promise<CompletionContextsResult> {
1439+
try {
1440+
const activeEditor: vscode.TextEditor | undefined = vscode.window.activeTextEditor;
1441+
if (!activeEditor) {
1442+
return { context: [] };
1443+
}
1444+
1445+
const snippets = await clients.ActiveClient.getCompletionContext(activeEditor.document.uri, caretOffset, token);
1446+
const wksFolder = clients.ActiveClient.RootUri?.toString();
1447+
1448+
if (!wksFolder) {
1449+
return snippets;
1450+
}
1451+
1452+
// Fix up URIs to be relative to the workspace folder.
1453+
// //?? TODO Fix the check, the uri do not start with wksFolder whew.
1454+
//snippets.context = snippets.context.filter(snippet =>
1455+
// vscode.Uri.file(snippet.uri).toString().startsWith(wksFolder));
1456+
return snippets;
1457+
} catch (e) {
1458+
return { context: [] };
1459+
}
1460+
}

Extension/test/scenarios/SingleRootProject/tests/copilotProviders.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ describe('registerRelatedFilesProvider', () => {
3939
sinon.stub(util, 'extensionContext').value({ extension: { id: 'test-extension-id' } });
4040

4141
class MockCopilotApi implements CopilotApi {
42+
registerSnippetsProvider(_providerId: { extensionId: string; languageId: string }, _callback: (uri: vscode.Uri, context: { flags: Record<string, unknown> }, cancellationToken: vscode.CancellationToken) => Promise<{ entries: extension.SnippetEntry[] }>): Disposable {
43+
throw new Error('Method not implemented.');
44+
}
4245
public registerRelatedFilesProvider(
4346
_providerId: { extensionId: string; languageId: string },
4447
_callback: (
@@ -75,8 +78,8 @@ describe('registerRelatedFilesProvider', () => {
7578
});
7679

7780
const arrange = ({ vscodeExtension, getIncludeFiles, chatContext, rootUri, flags }:
78-
{ vscodeExtension?: vscode.Extension<unknown>; getIncludeFiles?: GetIncludesResult; chatContext?: ChatContextResult; rootUri?: vscode.Uri; flags?: Record<string, unknown> } =
79-
{ vscodeExtension: undefined, getIncludeFiles: undefined, chatContext: undefined, rootUri: undefined, flags: {} }
81+
{ vscodeExtension?: vscode.Extension<unknown>; getIncludeFiles?: GetIncludesResult; chatContext?: ChatContextResult; rootUri?: vscode.Uri; flags?: Record<string, unknown> } =
82+
{ vscodeExtension: undefined, getIncludeFiles: undefined, chatContext: undefined, rootUri: undefined, flags: {} }
8083
) => {
8184
activeClientStub.getIncludes.resolves(getIncludeFiles);
8285
activeClientStub.getChatContext.resolves(chatContext);

0 commit comments

Comments
 (0)