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 Nov 11, 2024
1 parent c9cae0b commit b53ad49
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 13 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
13 changes: 13 additions & 0 deletions Extension/Extension.code-workspace
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"folders": [
{
"path": "."
},
{
"path": "../../Users/lucappa/.vscode-insiders/extensions/copilot-client"
}
],
"settings": {
"typescript.tsdk": "./node_modules/typescript/lib"
}
}
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,15 @@ export interface ChatContextResult {
targetArchitecture: string;
}

export interface CompletionContextsResult {
context: SnippetEntry[];
}

export interface CompletionContextParams {
file: string;
caretOffset: number;
}

// 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 +570,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 +801,7 @@ export interface Client {
addTrustedCompiler(path: string): Promise<void>;
getIncludes(maxDepth: number, token: vscode.CancellationToken): Promise<GetIncludesResult>;
getChatContext(token: vscode.CancellationToken): Promise<ChatContextResult>;
getCompletionContext(fileName: vscode.Uri, caretOffset: number, token: vscode.CancellationToken): Promise<CompletionContextsResult>;
}

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

public async getCompletionContext(file: vscode.Uri, caretOffset: number, token: vscode.CancellationToken): Promise<CompletionContextsResult> {
await withCancellation(this.ready, token);
return DefaultClient.withLspCancellationHandling(
() => this.languageClient.sendRequest(CompletionContextRequest, { file: file.toString(), caretOffset }, token), token);
}

/**
* a Promise that can be awaited to know when it's ok to proceed.
*
Expand Down Expand Up @@ -4123,4 +4139,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(file: vscode.Uri, caretOffset: number, token: vscode.CancellationToken): Promise<CompletionContextsResult> { return Promise.resolve({} as CompletionContextsResult); }
}
18 changes: 17 additions & 1 deletion Extension/src/LanguageServer/copilotProviders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import * as vscode from 'vscode';
import * as util from '../common';
import { ChatContextResult, GetIncludesResult } from './client';
import { getActiveClient } from './extension';
import { getActiveClient, SnippetEntry } from './extension';

export interface CopilotTrait {
name: string;
Expand All @@ -25,6 +25,22 @@ export interface CopilotApi {
cancellationToken: vscode.CancellationToken
) => Promise<{ entries: vscode.Uri[]; traits?: CopilotTrait[] }>
): Disposable;
registerRelatedFilesProvider(
providerId: { extensionId: string; languageId: string },
callback: (
uri: vscode.Uri,
context: { flags: Record<string, unknown> },
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;
}

export async function registerRelatedFilesProvider(): Promise<void> {
Expand Down
85 changes: 78 additions & 7 deletions Extension/src/LanguageServer/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ import * as util from '../common';
import { getCrashCallStacksChannel } from '../logger';
import { PlatformInformation } from '../platform';
import * as telemetry from '../telemetry';
import { Client, DefaultClient, DoxygenCodeActionCommandArguments, 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 { registerRelatedFilesProvider } from './copilotProviders';
import { getCopilotApi, registerRelatedFilesProvider } from './copilotProviders';
import { CppBuildTaskProvider } from './cppBuildTaskProvider';
import { getCustomConfigProviders } from './customProviders';
import { getLanguageConfig } from './languageConfig';
Expand All @@ -34,6 +34,21 @@ import { CppSettings } from './settings';
import { LanguageStatusUI, getUI } from './ui';
import { makeLspRange, rangeEquals, showInstallCompilerWalkthrough } from './utils';

/*
interface CopilotTrait {
name: string;
value: string;
includeInPrompt?: boolean;
promptTextOverride?: string;
}*/

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

nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
export const CppSourceStr: string = "C/C++";
Expand Down Expand Up @@ -264,6 +279,26 @@ export async function activate(): Promise<void> {
}

await registerRelatedFilesProvider();

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 @@ -276,8 +311,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 @@ -488,9 +523,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 @@ -1387,3 +1422,39 @@ export async function preReleaseCheck(): Promise<void> {
}
}
}

export async function getIncludesWithCancellation(maxDepth: number, token: vscode.CancellationToken): Promise<GetIncludesResult> {
const includes = await clients.ActiveClient.getIncludes(maxDepth, token);
const wksFolder = clients.ActiveClient.RootUri?.toString();

if (!wksFolder) {
return includes;
}

includes.includedFiles = includes.includedFiles.filter(header => vscode.Uri.file(header).toString().startsWith(wksFolder));
return includes;
}

export async function getCompletionContextWithCancellation(caretOffset: number, token: vscode.CancellationToken): Promise<CompletionContextsResult> {
try {
const activeEditor: vscode.TextEditor | undefined = vscode.window.activeTextEditor;
if (!activeEditor) {
return { context: [] };
}

const snippets = await clients.ActiveClient.getCompletionContext(activeEditor.document.uri, caretOffset, token);
const wksFolder = clients.ActiveClient.RootUri?.toString();

if (!wksFolder) {
return snippets;
}

// Fix up URIs to be relative to the workspace folder.
// //?? TODO Fix the check, the uri do not start with wksFolder whew.
//snippets.context = snippets.context.filter(snippet =>
// vscode.Uri.file(snippet.uri).toString().startsWith(wksFolder));
return snippets;
} catch (e) {
return { context: [] };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ describe('registerRelatedFilesProvider', () => {
sinon.stub(util, 'extensionContext').value({ extension: { id: 'test-extension-id' } });

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

const arrange = ({ vscodeExtension, getIncludeFiles, chatContext, rootUri, flags }:
{ vscodeExtension?: vscode.Extension<unknown>; getIncludeFiles?: GetIncludesResult; chatContext?: ChatContextResult; rootUri?: vscode.Uri; flags?: Record<string, unknown> } =
{ vscodeExtension: undefined, getIncludeFiles: undefined, chatContext: undefined, rootUri: undefined, flags: {} }
{ vscodeExtension?: vscode.Extension<unknown>; getIncludeFiles?: GetIncludesResult; chatContext?: ChatContextResult; rootUri?: vscode.Uri; flags?: Record<string, unknown> } =

Check failure on line 81 in Extension/test/scenarios/SingleRootProject/tests/copilotProviders.test.ts

View workflow job for this annotation

GitHub Actions / job / build

Expected indentation of 4 spaces but found 8

Check failure on line 81 in Extension/test/scenarios/SingleRootProject/tests/copilotProviders.test.ts

View workflow job for this annotation

GitHub Actions / job / build

Expected indentation of 4 spaces but found 8

Check failure on line 81 in Extension/test/scenarios/SingleRootProject/tests/copilotProviders.test.ts

View workflow job for this annotation

GitHub Actions / job / build

Expected indentation of 4 spaces but found 8
{ vscodeExtension: undefined, getIncludeFiles: undefined, chatContext: undefined, rootUri: undefined, flags: {} }

Check warning on line 82 in Extension/test/scenarios/SingleRootProject/tests/copilotProviders.test.ts

View workflow job for this annotation

GitHub Actions / job / build

Expected indentation of 4 spaces but found 8

Check failure on line 82 in Extension/test/scenarios/SingleRootProject/tests/copilotProviders.test.ts

View workflow job for this annotation

GitHub Actions / job / build

Expected indentation of 4 spaces but found 8

Check warning on line 82 in Extension/test/scenarios/SingleRootProject/tests/copilotProviders.test.ts

View workflow job for this annotation

GitHub Actions / job / build

Expected indentation of 4 spaces but found 8

Check failure on line 82 in Extension/test/scenarios/SingleRootProject/tests/copilotProviders.test.ts

View workflow job for this annotation

GitHub Actions / job / build

Expected indentation of 4 spaces but found 8

Check warning on line 82 in Extension/test/scenarios/SingleRootProject/tests/copilotProviders.test.ts

View workflow job for this annotation

GitHub Actions / job / build

Expected indentation of 4 spaces but found 8

Check failure on line 82 in Extension/test/scenarios/SingleRootProject/tests/copilotProviders.test.ts

View workflow job for this annotation

GitHub Actions / job / build

Expected indentation of 4 spaces but found 8
) => {
activeClientStub.getIncludes.resolves(getIncludeFiles);
activeClientStub.getChatContext.resolves(chatContext);
Expand Down

0 comments on commit b53ad49

Please sign in to comment.