Skip to content

Commit

Permalink
Add Compiler Command Line Defines and Arguments Traits to Completions…
Browse files Browse the repository at this point in the history
… Prompt

- The requirement was to add compiler command line as a trait to complettions prompt, however, the best we could provide are defines/arguments that users configured for IntelliSense.
  • Loading branch information
kuchungmsft committed Oct 19, 2024
1 parent 456af8c commit 2516fa6
Show file tree
Hide file tree
Showing 5 changed files with 295 additions and 45 deletions.
2 changes: 2 additions & 0 deletions Extension/src/LanguageServer/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,8 @@ export interface ChatContextResult {
compiler: string;
targetPlatform: string;
targetArchitecture: string;
compilerArgs?: string[];
compilerUserDefines?: string[];
}

// Requests
Expand Down
13 changes: 12 additions & 1 deletion Extension/src/LanguageServer/copilotProviders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ export async function registerRelatedFilesProvider(): Promise<void> {

const getIncludesHandler = async () => (await getIncludesWithCancellation(1, token))?.includedFiles.map(file => vscode.Uri.file(file)) ?? [];
const getTraitsHandler = async () => {
const chatContext: ChatContextResult | undefined = await (getActiveClient().getChatContext(token) ?? undefined);
const client = getActiveClient();
const chatContext: ChatContextResult | undefined = await (client.getChatContext(token) ?? undefined);

if (!chatContext) {
return undefined;
Expand All @@ -52,6 +53,16 @@ export async function registerRelatedFilesProvider(): Promise<void> {
{ name: "targetArchitecture", value: chatContext.targetArchitecture, includeInPrompt: true, promptTextOverride: `This build targets ${chatContext.targetArchitecture}.` }
];

const compilerArgs = chatContext.compilerArgs?.join(' ') ?? undefined;
if (compilerArgs) {
traits.push({ name: "compilerArgs", value: compilerArgs, includeInPrompt: true, promptTextOverride: `The compiler command line arguments contain: ${compilerArgs}.` });
}

const compilerUserDefines = chatContext.compilerUserDefines?.join(', ') ?? undefined;
if (compilerUserDefines) {
traits.push({ name: "compilerUserDefines", value: compilerUserDefines, includeInPrompt: true, promptTextOverride: `The compiler command line user defines contain: ${compilerUserDefines}.` });
}

const excludeTraits = context.flags.copilotcppExcludeTraits as string[] ?? [];
traits = traits.filter(trait => !excludeTraits.includes(trait.name));

Expand Down
30 changes: 18 additions & 12 deletions Extension/src/LanguageServer/lmTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,20 +67,26 @@ export class CppConfigurationLanguageModelTool implements vscode.LanguageModelTo
return 'No configuration information is available for the active document.';
}

telemetry.logLanguageModelToolEvent(
'cpp',
{
"language": chatContext.language,
"compiler": chatContext.compiler,
"standardVersion": chatContext.standardVersion,
"targetPlatform": chatContext.targetPlatform,
"targetArchitecture": chatContext.targetArchitecture
});
const telemetryProperties: Record<string, string> = {
"language": chatContext.language,
"compiler": chatContext.compiler,
"standardVersion": chatContext.standardVersion,
"targetPlatform": chatContext.targetPlatform,
"targetArchitecture": chatContext.targetArchitecture
};
if (chatContext.compilerArgs) {
telemetryProperties["compilerArgs"] = chatContext.compilerArgs.join(' ');
}
if (chatContext.compilerUserDefines) {
telemetryProperties["compilerUserDefines"] = chatContext.compilerUserDefines.join(', ');
}
telemetry.logLanguageModelToolEvent('cpp', telemetryProperties);

type KnownKeys = 'language' | 'standardVersion' | 'compiler' | 'targetPlatform' | 'targetArchitecture';
for (const key in knownValues) {
const knownKey = key as keyof ChatContextResult;
if (knownValues[knownKey] && chatContext[knownKey]) {
chatContext[knownKey] = knownValues[knownKey][chatContext[knownKey]] || chatContext[knownKey];
const knownKey = key as KnownKeys;
if (knownKey && knownValues[knownKey] && chatContext[knownKey]) {
chatContext[knownKey] = knownValues[knownKey][chatContext[knownKey] as string] || chatContext[knownKey];
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { ChatContextResult, DefaultClient, GetIncludesResult } from '../../../..
import { CopilotApi, CopilotTrait } from '../../../../src/LanguageServer/copilotProviders';
import * as extension from '../../../../src/LanguageServer/extension';

describe('registerRelatedFilesProvider', () => {
describe('copilotProviders Tests', () => {
let moduleUnderTest: any;
let mockCopilotApi: sinon.SinonStubbedInstance<CopilotApi>;
let getActiveClientStub: sinon.SinonStub;
Expand Down Expand Up @@ -102,7 +102,7 @@ describe('registerRelatedFilesProvider', () => {
ok(mockCopilotApi.registerRelatedFilesProvider.calledWithMatch(sinon.match({ extensionId: 'test-extension-id', languageId: sinon.match.in(['c', 'cpp', 'cuda-cpp']) })), 'registerRelatedFilesProvider should be called with the correct providerId and languageId');
});

it('should not add #cpp traits when ChatContext isn\'t available.', async () => {
it('should not provide cpp context traits when ChatContext isn\'t available.', async () => {
arrange({
vscodeExtension: vscodeExtension,
getIncludeFiles: { includedFiles: ['c:\\system\\include\\vector', 'c:\\system\\include\\string', 'C:\\src\\my_project\\foo.h'] },
Expand All @@ -124,7 +124,7 @@ describe('registerRelatedFilesProvider', () => {
ok(result.traits === undefined, 'result.traits should be undefined');
});

it('should not add #cpp traits when copilotcppTraits flag is false.', async () => {
it('should not provide cpp context traits when copilotcppTraits flag is false.', async () => {
arrange({
vscodeExtension: vscodeExtension,
getIncludeFiles: { includedFiles: ['c:\\system\\include\\vector', 'c:\\system\\include\\string', 'C:\\src\\my_project\\foo.h'] },
Expand Down Expand Up @@ -152,7 +152,7 @@ describe('registerRelatedFilesProvider', () => {
ok(result.traits === undefined, 'result.traits should be undefined');
});

it('should add #cpp traits when copilotcppTraits flag is true.', async () => {
it('should provide cpp context traits when copilotcppTraits flag is true.', async () => {
arrange({
vscodeExtension: vscodeExtension,
getIncludeFiles: { includedFiles: ['c:\\system\\include\\vector', 'c:\\system\\include\\string', 'C:\\src\\my_project\\foo.h'] },
Expand All @@ -170,39 +170,64 @@ describe('registerRelatedFilesProvider', () => {

const result = await callbackPromise;

ok(vscodeGetExtensionsStub.calledOnce, 'vscode.extensions.getExtension should be called once');
ok(mockCopilotApi.registerRelatedFilesProvider.calledThrice, 'registerRelatedFilesProvider should be called three times');
ok(mockCopilotApi.registerRelatedFilesProvider.calledWithMatch(sinon.match({ extensionId: 'test-extension-id', languageId: sinon.match.in(['c', 'cpp', 'cuda-cpp']) })), 'registerRelatedFilesProvider should be called with the correct providerId and languageId');
ok(getActiveClientStub.callCount !== 0, 'getActiveClient should be called');
ok(callbackPromise, 'callbackPromise should be defined');
ok(result, 'result should be defined');
ok(result.entries.length === 1, 'result.entries should have 1 included file');
ok(result.entries[0].toString() === 'file:///c%3A/src/my_project/foo.h', 'result.entries should have "file:///c%3A/src/my_project/foo.h"');
ok(result.traits, 'result.traits should be defined');
ok(result.traits.length === 5, 'result.traits should have 5 traits');
ok(result.traits[0].name === 'language', 'result.traits[0].name should be "language"');
ok(result.traits[0].value === 'c++', 'result.traits[0].value should be "c++"');
ok(result.traits[0].includeInPrompt, 'result.traits[0].includeInPrompt should be true');
ok(result.traits[0].promptTextOverride === 'The language is c++.', 'result.traits[0].promptTextOverride should be "The language is c++."');
ok(result.traits[1].name === 'compiler', 'result.traits[1].name should be "compiler"');
ok(result.traits[1].value === 'msvc', 'result.traits[1].value should be "msvc"');
ok(result.traits[1].includeInPrompt, 'result.traits[1].includeInPrompt should be true');
ok(result.traits[1].promptTextOverride === 'This project compiles using msvc.', 'result.traits[1].promptTextOverride should be "This project compiles using msvc."');
ok(result.traits[2].name === 'standardVersion', 'result.traits[2].name should be "standardVersion"');
ok(result.traits[2].value === 'c++20', 'result.traits[2].value should be "c++20"');
ok(result.traits[2].includeInPrompt, 'result.traits[2].includeInPrompt should be true');
ok(result.traits[2].promptTextOverride === 'This project uses the c++20 language standard.', 'result.traits[2].promptTextOverride should be "This project uses the c++20 language standard."');
ok(result.traits[3].name === 'targetPlatform', 'result.traits[3].name should be "targetPlatform"');
ok(result.traits[3].value === 'windows', 'result.traits[3].value should be "windows"');
ok(result.traits[3].includeInPrompt, 'result.traits[3].includeInPrompt should be true');
ok(result.traits[3].promptTextOverride === 'This build targets windows.', 'result.traits[3].promptTextOverride should be "This build targets windows."');
ok(result.traits[4].name === 'targetArchitecture', 'result.traits[4].name should be "targetArchitecture"');
ok(result.traits[4].value === 'x64', 'result.traits[4].value should be "x64"');
ok(result.traits[4].includeInPrompt, 'result.traits[4].includeInPrompt should be true');
ok(result.traits[4].promptTextOverride === 'This build targets x64.', 'result.traits[4].promptTextOverride should be "This build targets x64."');
ok(result.traits.find((trait) => trait.name === 'language'), 'result.traits should have a language trait');
ok(result.traits.find((trait) => trait.name === 'language')?.value === 'c++', 'result.traits should have a language trait with value "c++"');
ok(result.traits.find((trait) => trait.name === 'language')?.includeInPrompt, 'result.traits should have a language trait with includeInPrompt true');
ok(result.traits.find((trait) => trait.name === 'language')?.promptTextOverride === 'The language is c++.', 'result.traits should have a language trait with promptTextOverride "The language is c++."');
ok(result.traits.find((trait) => trait.name === 'compiler'), 'result.traits should have a compiler trait');
ok(result.traits.find((trait) => trait.name === 'compiler')?.value === 'msvc', 'result.traits should have a compiler trait with value "msvc"');
ok(result.traits.find((trait) => trait.name === 'compiler')?.includeInPrompt, 'result.traits should have a compiler trait with includeInPrompt true');
ok(result.traits.find((trait) => trait.name === 'compiler')?.promptTextOverride === 'This project compiles using msvc.', 'result.traits should have a compiler trait with promptTextOverride "This project compiles using msvc."');
ok(result.traits.find((trait) => trait.name === 'standardVersion'), 'result.traits should have a standardVersion trait');
ok(result.traits.find((trait) => trait.name === 'standardVersion')?.value === 'c++20', 'result.traits should have a standardVersion trait with value "c++20"');
ok(result.traits.find((trait) => trait.name === 'standardVersion')?.includeInPrompt, 'result.traits should have a standardVersion trait with includeInPrompt true');
ok(result.traits.find((trait) => trait.name === 'standardVersion')?.promptTextOverride === 'This project uses the c++20 language standard.', 'result.traits should have a standardVersion trait with promptTextOverride "This project uses the c++20 language standard."');
ok(result.traits.find((trait) => trait.name === 'targetPlatform'), 'result.traits should have a targetPlatform trait');
ok(result.traits.find((trait) => trait.name === 'targetPlatform')?.value === 'windows', 'result.traits should have a targetPlatform trait with value "windows"');
ok(result.traits.find((trait) => trait.name === 'targetPlatform')?.includeInPrompt, 'result.traits should have a targetPlatform trait with includeInPrompt true');
ok(result.traits.find((trait) => trait.name === 'targetPlatform')?.promptTextOverride === 'This build targets windows.', 'result.traits should have a targetPlatform trait with promptTextOverride "This build targets windows."');
ok(result.traits.find((trait) => trait.name === 'targetArchitecture'), 'result.traits should have a targetArchitecture trait');
ok(result.traits.find((trait) => trait.name === 'targetArchitecture')?.value === 'x64', 'result.traits should have a targetArchitecture trait with value "x64"');
ok(result.traits.find((trait) => trait.name === 'targetArchitecture')?.includeInPrompt, 'result.traits should have a targetArchitecture trait with includeInPrompt true');
ok(result.traits.find((trait) => trait.name === 'targetArchitecture')?.promptTextOverride === 'This build targets x64.', 'result.traits should have a targetArchitecture trait with promptTextOverride "This build targets x64."');
});

it('should provide compiler defines and arguments traits if available.', async () => {
arrange({
vscodeExtension: vscodeExtension,
getIncludeFiles: { includedFiles: ['c:\\system\\include\\vector', 'c:\\system\\include\\string', 'C:\\src\\my_project\\foo.h'] },
chatContext: {
language: 'c++',
standardVersion: 'c++20',
compiler: 'msvc',
targetPlatform: 'windows',
targetArchitecture: 'x64',
compilerArgs: ['/std:c++17', '/permissive-'],
compilerUserDefines: ['DEBUG', 'TEST']
},
rootUri: vscode.Uri.file('C:\\src\\my_project'),
flags: { copilotcppTraits: true }
});
await moduleUnderTest.registerRelatedFilesProvider();

const result = await callbackPromise;

ok(result, 'result should be defined');
ok(result.traits, 'result.traits should be defined');
ok(result.traits.find((trait) => trait.name === 'compilerArgs'), 'result.traits should have a compiler args trait');
ok(result.traits.find((trait) => trait.name === 'compilerArgs')?.value === '/std:c++17 /permissive-', 'result.traits should have a compiler args trait with value "/std:c++17 /permissive-"');
ok(result.traits.find((trait) => trait.name === 'compilerArgs')?.includeInPrompt, 'result.traits should have a compiler args trait with includeInPrompt true');
ok(result.traits.find((trait) => trait.name === 'compilerArgs')?.promptTextOverride === 'The compiler command line arguments contain: /std:c++17 /permissive-.', 'result.traits should have a compiler args trait with promptTextOverride "The compiler command line arguments contain: /std:c++17 /permissive-"');
ok(result.traits.find((trait) => trait.name === 'compilerUserDefines'), 'result.traits should have a compiler defines trait');
ok(result.traits.find((trait) => trait.name === 'compilerUserDefines')?.value === 'DEBUG, TEST', 'result.traits should have a compiler defines trait with value "DEBUG, TEST"');
ok(result.traits.find((trait) => trait.name === 'compilerUserDefines')?.includeInPrompt, 'result.traits should have a compiler defines trait with includeInPrompt true');
ok(result.traits.find((trait) => trait.name === 'compilerUserDefines')?.promptTextOverride === 'The compiler command line user defines contain: DEBUG, TEST.', 'result.traits should have a compiler defines trait with promptTextOverride "The compiler command line user defines contain: DEBUG, TEST."');
});

it('should exclude #cpp traits per copilotcppExcludeTraits.', async () => {
it('should exclude cpp context traits per copilotcppExcludeTraits.', async () => {
const excludeTraits = ['compiler', 'targetPlatform'];
arrange({
vscodeExtension: vscodeExtension,
Expand Down
Loading

0 comments on commit 2516fa6

Please sign in to comment.