Skip to content

Commit

Permalink
add custom snippets PoC
Browse files Browse the repository at this point in the history
  • Loading branch information
lukka committed Oct 26, 2024
1 parent f09715f commit 8550109
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 11 deletions.
9 changes: 8 additions & 1 deletion Extension/.vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,17 @@
"--skip-release-notes",
"--disable-workspace-trust",
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionDevelopmentPath=C:/Users/lucappa/.vscode-insiders/extensions/copilot-client",
"--disable-extension=ms.vscode.cpptools",
"--disable-extension=github.synth-lab",
"--disable-extension=github.copilot",
"--disable-extension=github.copilot-nightly",
"--log=github.copilot:debug",
],
"sourceMaps": true,
"outFiles": [
"${workspaceFolder}/dist/**"
"${workspaceFolder}/dist/**",
"C:/Users/lucappa/.vscode-insiders/extensions/copilot-client/dist/**"
],
// you can use a watch task as a prelaunch task and it works like you'd want it to.
"preLaunchTask": "watch"
Expand Down
21 changes: 19 additions & 2 deletions Extension/src/LanguageServer/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ import { Location, TextEdit, WorkspaceEdit } from './commonTypes';
import * as configs from './configurations';
import { DataBinding } from './dataBinding';
import { cachedEditorConfigSettings, getEditorConfigSettings } from './editorConfig';
import { CppSourceStr, clients, configPrefix, updateLanguageConfigurations, usesCrashHandler, watchForCrashes } from './extension';
import { CppSourceStr, SnippetEntry, clients, configPrefix, updateLanguageConfigurations, usesCrashHandler, watchForCrashes } from './extension';
import { LocalizeStringParams, getLocaleId, getLocalizedString } from './localization';
import { PersistentFolderState, PersistentWorkspaceState } from './persistentState';
import { RequestCancelled, ServerCancelled, createProtocolFilter } from './protocolFilter';
Expand Down Expand Up @@ -541,6 +541,14 @@ export interface ChatContextResult {
targetArchitecture: string;
}

export interface CompletionContextsResult {
context: SnippetEntry[];
}

export interface CompletionContextParams {
position: Position;
}

// Requests
const PreInitializationRequest: RequestType<void, string, void> = new RequestType<void, string, void>('cpptools/preinitialize');
const InitializationRequest: RequestType<CppInitializationParams, void, void> = new RequestType<CppInitializationParams, void, void>('cpptools/initialize');
Expand All @@ -561,7 +569,7 @@ const GenerateDoxygenCommentRequest: RequestType<GenerateDoxygenCommentParams, G
const ChangeCppPropertiesRequest: RequestType<CppPropertiesParams, void, void> = new RequestType<CppPropertiesParams, void, void>('cpptools/didChangeCppProperties');
const IncludesRequest: RequestType<GetIncludesParams, GetIncludesResult, void> = new RequestType<GetIncludesParams, GetIncludesResult, void>('cpptools/getIncludes');
const CppContextRequest: RequestType<void, ChatContextResult, void> = new RequestType<void, ChatContextResult, void>('cpptools/getChatContext');

const CompletionContextRequest: RequestType<CompletionContextParams, CompletionContextsResult, void> = new RequestType<CompletionContextParams, CompletionContextsResult, void>('cpptools/getCompletionContext');
// Notifications to the server
const DidOpenNotification: NotificationType<DidOpenTextDocumentParams> = new NotificationType<DidOpenTextDocumentParams>('textDocument/didOpen');
const FileCreatedNotification: NotificationType<FileChangedParams> = new NotificationType<FileChangedParams>('cpptools/fileCreated');
Expand Down Expand Up @@ -792,6 +800,7 @@ export interface Client {
addTrustedCompiler(path: string): Promise<void>;
getIncludes(maxDepth: number, token: vscode.CancellationToken): Promise<GetIncludesResult>;
getChatContext(token: vscode.CancellationToken): Promise<ChatContextResult>;
getCompletionContext(caretOffset: number, token: vscode.CancellationToken): Promise<CompletionContextsResult>;
}

export function createClient(workspaceFolder?: vscode.WorkspaceFolder): Client {
Expand Down Expand Up @@ -2219,6 +2228,13 @@ export class DefaultClient implements Client {
() => this.languageClient.sendRequest(CppContextRequest, null, token), token);
}

public async getCompletionContext(_caretOffset: number, token: vscode.CancellationToken): Promise<CompletionContextsResult> {
await withCancellation(this.ready, token);
return DefaultClient.withLspCancellationHandling(
//?? TODO: convert offset to line/character pair.
() => this.languageClient.sendRequest(CompletionContextRequest, { position: Position.create(1, 2) }, token), token);
}

/**
* a Promise that can be awaited to know when it's ok to proceed.
*
Expand Down Expand Up @@ -4120,4 +4136,5 @@ class NullClient implements Client {
addTrustedCompiler(path: string): Promise<void> { return Promise.resolve(); }
getIncludes(maxDepth: number, token: vscode.CancellationToken): Promise<GetIncludesResult> { return Promise.resolve({} as GetIncludesResult); }
getChatContext(token: vscode.CancellationToken): Promise<ChatContextResult> { return Promise.resolve({} as ChatContextResult); }
getCompletionContext(caretOffset: number, token: vscode.CancellationToken): Promise<CompletionContextsResult> { return Promise.resolve({} as CompletionContextsResult); }
}
65 changes: 57 additions & 8 deletions Extension/src/LanguageServer/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import * as util from '../common';
import { getCrashCallStacksChannel } from '../logger';
import { PlatformInformation } from '../platform';
import * as telemetry from '../telemetry';
import { Client, DefaultClient, DoxygenCodeActionCommandArguments, GetIncludesResult, openFileVersions } from './client';
import { Client, CompletionContextsResult, DefaultClient, DoxygenCodeActionCommandArguments, GetIncludesResult, openFileVersions } from './client';
import { ClientCollection } from './clientCollection';
import { CodeActionDiagnosticInfo, CodeAnalysisDiagnosticIdentifiersAndUri, codeAnalysisAllFixes, codeAnalysisCodeToFixes, codeAnalysisFileToCodeActions } from './codeAnalysis';
import { CppBuildTaskProvider } from './cppBuildTaskProvider';
Expand All @@ -40,6 +40,13 @@ interface CopilotTrait {
promptTextOverride?: string;
}

export interface SnippetEntry {
uri: string;
text: string;
startLine: number;
endLine: number;
}

interface CopilotApi {
registerRelatedFilesProvider(
providerId: { extensionId: string; languageId: string },
Expand All @@ -49,6 +56,15 @@ interface CopilotApi {
cancellationToken: vscode.CancellationToken
) => Promise<{ entries: vscode.Uri[]; traits?: CopilotTrait[] }>
): Disposable;
registerSnippetsProvider(
providerId: { extensionId: string; languageId: string },
callback: (
uri: vscode.Uri,
context: { flags: Record<string, unknown> },
cancellationToken: vscode.CancellationToken
) => Promise<{ entries: SnippetEntry[] }>
): Disposable;

}

nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
Expand Down Expand Up @@ -290,6 +306,26 @@ export async function activate(): Promise<void> {
}
}
}

const isCustomSnippetProviderApiEnabled = await telemetry.isExperimentEnabled("CppToolsCustomSnippetsApi");
if (isCustomSnippetProviderApiEnabled) {
const api = await getCopilotApi();
if (util.extensionContext && api) {
try {
for (const languageId of ['c', 'cpp', 'cuda-cpp']) {
api.registerSnippetsProvider(
{ extensionId: util.extensionContext.extension.id, languageId },
async (uri: vscode.Uri, context: { flags: Record<string, unknown> }, token: vscode.CancellationToken) => {
const result = await getCompletionContextWithCancellation(context.flags['caretOffset'] as number, token);
return { entries: result.context };
}
);
}
} catch {
console.log("Failed to register Copilot related files provider.");
}
}
}
}

export function updateLanguageConfigurations(): void {
Expand All @@ -302,8 +338,8 @@ export function updateLanguageConfigurations(): void {
}

/**
* workspace events
*/
* workspace events
*/
async function onDidChangeSettings(event: vscode.ConfigurationChangeEvent): Promise<void> {
const client: Client = clients.getDefaultClient();
if (client instanceof DefaultClient) {
Expand Down Expand Up @@ -384,8 +420,8 @@ function onInterval(): void {
}

/**
* registered commands
*/
* registered commands
*/
export function registerCommands(enabled: boolean, isRelatedFilesApiEnabled: boolean): void {
commandDisposables.forEach(d => d.dispose());
commandDisposables.length = 0;
Expand Down Expand Up @@ -513,9 +549,9 @@ async function onSwitchHeaderSource(): Promise<void> {
}

/**
* Allow the user to select a workspace when multiple workspaces exist and get the corresponding Client back.
* The resulting client is used to handle some command that was previously invoked.
*/
* Allow the user to select a workspace when multiple workspaces exist and get the corresponding Client back.
* The resulting client is used to handle some command that was previously invoked.
*/
async function selectClient(): Promise<Client> {
if (clients.Count === 1) {
return clients.ActiveClient;
Expand Down Expand Up @@ -1435,6 +1471,19 @@ async function getIncludes(maxDepth: number): Promise<GetIncludesResult> {
}
}

export async function getCompletionContextWithCancellation(caretOffset: number, token: vscode.CancellationToken): Promise<CompletionContextsResult> {
const snippets = await clients.ActiveClient.getCompletionContext(caretOffset, token);
const wksFolder = clients.ActiveClient.RootUri?.toString();

if (!wksFolder) {
return snippets;
}

// Fix up URIs to be relative to the workspace folder.
snippets.context = snippets.context.filter(snippet => vscode.Uri.file(snippet.uri).toString().startsWith(wksFolder));
return snippets;
}

async function getCopilotApi(): Promise<CopilotApi | undefined> {
const copilotExtension = vscode.extensions.getExtension<CopilotApi>('github.copilot');
if (!copilotExtension) {
Expand Down

0 comments on commit 8550109

Please sign in to comment.