Skip to content

Commit c949ac6

Browse files
committed
Support A/B Compiler Arguments Traits
- Depends on cpptools' update to provide ProjectContextResult. - Added the following new traits - intelliSenseDisclaimer: compiler information disclaimer. - intelliSenseDisclaimerBeginning: to note the beginning of IntelliSense information. - compilerArguments: a list of compiler command arguments that could affect Copilot generating completions. - directAsks: direct asking Copilot to do something instead of providing an argument. - intelliSenseDisclaimerEnd: to note the end of IntelliSense information. - A/B Experimental flags - copilotcppTraits: deprecated, no longer used. - copilotcppExcludeTraits:: deprecated, no longer used. - copilotcppIncludeTraits: string array to include individual trait, i.e., compilerArguments. - copilotcppMsvcCompilerArgumentFilter: map of regex string to absence prompt for MSVC. - copilotcppClangCompilerArgumentFilter: map of regex string to absence prompt for Clang. - copilotcppGccCompilerArgumentFilter: map of regex string to absence prompt for GCC. - copilotcppCompilerArgumentDirectAskMap: map of argument to prompt.
1 parent bfa3c75 commit c949ac6

7 files changed

Lines changed: 849 additions & 140 deletions

File tree

Extension/src/LanguageServer/client.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,19 @@ export interface ChatContextResult {
541541
targetArchitecture: string;
542542
}
543543

544+
export interface FileContextResult {
545+
compilerArguments: string[];
546+
}
547+
548+
export interface ProjectContextResult {
549+
language: string;
550+
standardVersion: string;
551+
compiler: string;
552+
targetPlatform: string;
553+
targetArchitecture: string;
554+
fileContext: FileContextResult;
555+
}
556+
544557
// Requests
545558
const PreInitializationRequest: RequestType<void, string, void> = new RequestType<void, string, void>('cpptools/preinitialize');
546559
const InitializationRequest: RequestType<CppInitializationParams, void, void> = new RequestType<CppInitializationParams, void, void>('cpptools/initialize');
@@ -560,7 +573,8 @@ const GoToDirectiveInGroupRequest: RequestType<GoToDirectiveInGroupParams, Posit
560573
const GenerateDoxygenCommentRequest: RequestType<GenerateDoxygenCommentParams, GenerateDoxygenCommentResult | undefined, void> = new RequestType<GenerateDoxygenCommentParams, GenerateDoxygenCommentResult, void>('cpptools/generateDoxygenComment');
561574
const ChangeCppPropertiesRequest: RequestType<CppPropertiesParams, void, void> = new RequestType<CppPropertiesParams, void, void>('cpptools/didChangeCppProperties');
562575
const IncludesRequest: RequestType<GetIncludesParams, GetIncludesResult, void> = new RequestType<GetIncludesParams, GetIncludesResult, void>('cpptools/getIncludes');
563-
const CppContextRequest: RequestType<void, ChatContextResult, void> = new RequestType<void, ChatContextResult, void>('cpptools/getChatContext');
576+
const CppContextRequest: RequestType<TextDocumentIdentifier, ChatContextResult, void> = new RequestType<TextDocumentIdentifier, ChatContextResult, void>('cpptools/getChatContext');
577+
const ProjectContextRequest: RequestType<TextDocumentIdentifier, ProjectContextResult, void> = new RequestType<TextDocumentIdentifier, ProjectContextResult, void>('cpptools/getProjectContext');
564578

565579
// Notifications to the server
566580
const DidOpenNotification: NotificationType<DidOpenTextDocumentParams> = new NotificationType<DidOpenTextDocumentParams>('textDocument/didOpen');
@@ -791,7 +805,8 @@ export interface Client {
791805
setShowConfigureIntelliSenseButton(show: boolean): void;
792806
addTrustedCompiler(path: string): Promise<void>;
793807
getIncludes(maxDepth: number, token: vscode.CancellationToken): Promise<GetIncludesResult>;
794-
getChatContext(token: vscode.CancellationToken): Promise<ChatContextResult>;
808+
getChatContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise<ChatContextResult>;
809+
getProjectContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise<ProjectContextResult>;
795810
}
796811

797812
export function createClient(workspaceFolder?: vscode.WorkspaceFolder): Client {
@@ -2220,10 +2235,18 @@ export class DefaultClient implements Client {
22202235
() => this.languageClient.sendRequest(IncludesRequest, params, token), token);
22212236
}
22222237

2223-
public async getChatContext(token: vscode.CancellationToken): Promise<ChatContextResult> {
2238+
public async getChatContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise<ChatContextResult> {
2239+
const params: TextDocumentIdentifier = { uri: uri.toString() };
2240+
await withCancellation(this.ready, token);
2241+
return DefaultClient.withLspCancellationHandling(
2242+
() => this.languageClient.sendRequest(CppContextRequest, params, token), token);
2243+
}
2244+
2245+
public async getProjectContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise<ProjectContextResult> {
2246+
const params: TextDocumentIdentifier = { uri: uri.toString() };
22242247
await withCancellation(this.ready, token);
22252248
return DefaultClient.withLspCancellationHandling(
2226-
() => this.languageClient.sendRequest(CppContextRequest, null, token), token);
2249+
() => this.languageClient.sendRequest(ProjectContextRequest, params, token), token);
22272250
}
22282251

22292252
/**
@@ -4129,5 +4152,6 @@ class NullClient implements Client {
41294152
setShowConfigureIntelliSenseButton(show: boolean): void { }
41304153
addTrustedCompiler(path: string): Promise<void> { return Promise.resolve(); }
41314154
getIncludes(maxDepth: number, token: vscode.CancellationToken): Promise<GetIncludesResult> { return Promise.resolve({} as GetIncludesResult); }
4132-
getChatContext(token: vscode.CancellationToken): Promise<ChatContextResult> { return Promise.resolve({} as ChatContextResult); }
4155+
getChatContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise<ChatContextResult> { return Promise.resolve({} as ChatContextResult); }
4156+
getProjectContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise<ProjectContextResult> { return Promise.resolve({} as ProjectContextResult); }
41334157
}

Extension/src/LanguageServer/copilotProviders.ts

Lines changed: 107 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
'use strict';
66

77
import * as vscode from 'vscode';
8+
import { localize } from 'vscode-nls';
89
import * as util from '../common';
9-
import { ChatContextResult, GetIncludesResult } from './client';
10+
import * as logger from '../logger';
11+
import * as telemetry from '../telemetry';
12+
import { GetIncludesResult } from './client';
1013
import { getActiveClient } from './extension';
14+
import { getCompilerArgumentFilterMap, getProjectContext } from './lmTool';
1115

1216
export interface CopilotTrait {
1317
name: string;
@@ -34,35 +38,109 @@ export async function registerRelatedFilesProvider(): Promise<void> {
3438
for (const languageId of ['c', 'cpp', 'cuda-cpp']) {
3539
api.registerRelatedFilesProvider(
3640
{ extensionId: util.extensionContext.extension.id, languageId },
37-
async (_uri: vscode.Uri, context: { flags: Record<string, unknown> }, token: vscode.CancellationToken) => {
38-
39-
const getIncludesHandler = async () => (await getIncludesWithCancellation(1, token))?.includedFiles.map(file => vscode.Uri.file(file)) ?? [];
40-
const getTraitsHandler = async () => {
41-
const chatContext: ChatContextResult | undefined = await (getActiveClient().getChatContext(token) ?? undefined);
42-
43-
if (!chatContext) {
44-
return undefined;
41+
async (uri: vscode.Uri, context: { flags: Record<string, unknown> }, token: vscode.CancellationToken) => {
42+
const telemetryProperties: Record<string, string> = {};
43+
try {
44+
const getIncludesHandler = async () => (await getIncludesWithCancellation(1, token))?.includedFiles.map(file => vscode.Uri.file(file)) ?? [];
45+
const getTraitsHandler = async () => {
46+
const cppContext = await getProjectContext(uri, context, token);
47+
48+
if (!cppContext) {
49+
return undefined;
50+
}
51+
52+
let traits: CopilotTrait[] = [
53+
{ name: "intelliSenseDisclaimer", value: '', includeInPrompt: true, promptTextOverride: `IntelliSense is currently configured with the following compiler information. It reflects the active configuration, and the project may have more configurations targeting different platforms.` },
54+
{ name: "intelliSenseDisclaimerBeginning", value: '', includeInPrompt: true, promptTextOverride: `Beginning of IntelliSense information.` }
55+
];
56+
if (cppContext.language) {
57+
traits.push({ name: "language", value: cppContext.language, includeInPrompt: true, promptTextOverride: `The language is ${cppContext.language}.` });
58+
}
59+
if (cppContext.compiler) {
60+
traits.push({ name: "compiler", value: cppContext.compiler, includeInPrompt: true, promptTextOverride: `This project compiles using ${cppContext.compiler}.` });
61+
}
62+
if (cppContext.standardVersion) {
63+
traits.push({ name: "standardVersion", value: cppContext.standardVersion, includeInPrompt: true, promptTextOverride: `This project uses the ${cppContext.standardVersion} language standard.` });
64+
}
65+
if (cppContext.targetPlatform) {
66+
traits.push({ name: "targetPlatform", value: cppContext.targetPlatform, includeInPrompt: true, promptTextOverride: `This build targets ${cppContext.targetPlatform}.` });
67+
}
68+
if (cppContext.targetArchitecture) {
69+
traits.push({ name: "targetArchitecture", value: cppContext.targetArchitecture, includeInPrompt: true, promptTextOverride: `This build targets ${cppContext.targetArchitecture}.` });
70+
}
71+
72+
if (cppContext.compiler) {
73+
// We will process compiler arguments based on copilotcppXXXCompilerArgumentFilters and copilotcppCompilerArgumentDirectAskMap feature flags.
74+
// The copilotcppXXXCompilerArgumentFilters are maps. The keys are regex strings for filtering and the values, if not empty,
75+
// are the prompt text to use when no arguments are found.
76+
// copilotcppCompilerArgumentDirectAskMap map individual matched argument to a prompt text.
77+
// For duplicate matches, the last one will be used.
78+
const filterMap = getCompilerArgumentFilterMap(cppContext.compiler, context);
79+
if (filterMap !== undefined) {
80+
const directAskMap: { [key: string]: string } = JSON.parse(context.flags.copilotcppCompilerArgumentDirectAskMap as string ?? '{}');
81+
let directAsks: string = '';
82+
const remainingArguments: string[] = [];
83+
84+
for (const key in filterMap) {
85+
if (!key) {
86+
continue;
87+
}
88+
const filter = new RegExp(key);
89+
const matchedArguments = cppContext.compilerArguments.filter(arg => filter?.test(arg));
90+
if (matchedArguments.length > 0) {
91+
// Use the last one in case of multiple match.
92+
if (directAskMap[matchedArguments[matchedArguments.length - 1]]) {
93+
directAsks += `${directAskMap[matchedArguments[matchedArguments.length - 1]]} `;
94+
} else {
95+
remainingArguments.push(matchedArguments[matchedArguments.length - 1]);
96+
}
97+
} else if (filterMap[key]) {
98+
// Use the prompt text in the absence of argument.
99+
directAsks += `${filterMap[key]} `;
100+
}
101+
}
102+
103+
const compilerArgumentsValue = remainingArguments.join(", ");
104+
traits.push({ name: "compilerArguments", value: compilerArgumentsValue, includeInPrompt: true, promptTextOverride: `The compiler arguments include: ${compilerArgumentsValue}.` });
105+
106+
if (directAsks) {
107+
traits.push({ name: "directAsks", value: directAsks, includeInPrompt: true, promptTextOverride: directAsks });
108+
}
109+
}
110+
}
111+
112+
traits.push({ name: "intelliSenseDisclaimerEnd", value: '', includeInPrompt: true, promptTextOverride: `End of IntelliSense information.` });
113+
114+
const includeTraitsArray = context.flags.copilotcppIncludeTraits as string[] ?? [];
115+
const includeTraits = new Set(includeTraitsArray);
116+
telemetryProperties["includeTraits"] = includeTraitsArray.join(' ');
117+
118+
// standardVersion trait is enabled by default.
119+
traits = traits.filter(trait => includeTraits.has(trait.name) || trait.name === 'standardVersion');
120+
121+
telemetryProperties["traits"] = traits.map(trait => trait.name).join(' ');
122+
return traits.length > 0 ? traits : undefined;
123+
};
124+
125+
// Call both handlers in parallel
126+
const traitsPromise = getTraitsHandler();
127+
const includesPromise = getIncludesHandler();
128+
129+
return { entries: await includesPromise, traits: await traitsPromise };
130+
}
131+
catch (exception) {
132+
try {
133+
const err: Error = exception as Error;
134+
logger.getOutputChannelLogger().appendLine(localize("copilot.relatedfilesprovider.error", "Error while retrieving result. Reason: {0}", err.message));
45135
}
46-
47-
let traits: CopilotTrait[] = [
48-
{ name: "language", value: chatContext.language, includeInPrompt: true, promptTextOverride: `The language is ${chatContext.language}.` },
49-
{ name: "compiler", value: chatContext.compiler, includeInPrompt: true, promptTextOverride: `This project compiles using ${chatContext.compiler}.` },
50-
{ name: "standardVersion", value: chatContext.standardVersion, includeInPrompt: true, promptTextOverride: `This project uses the ${chatContext.standardVersion} language standard.` },
51-
{ name: "targetPlatform", value: chatContext.targetPlatform, includeInPrompt: true, promptTextOverride: `This build targets ${chatContext.targetPlatform}.` },
52-
{ name: "targetArchitecture", value: chatContext.targetArchitecture, includeInPrompt: true, promptTextOverride: `This build targets ${chatContext.targetArchitecture}.` }
53-
];
54-
55-
const excludeTraits = context.flags.copilotcppExcludeTraits as string[] ?? [];
56-
traits = traits.filter(trait => !excludeTraits.includes(trait.name));
57-
58-
return traits.length > 0 ? traits : undefined;
59-
};
60-
61-
// Call both handlers in parallel
62-
const traitsPromise = ((context.flags.copilotcppTraits as boolean) ?? false) ? getTraitsHandler() : Promise.resolve(undefined);
63-
const includesPromise = getIncludesHandler();
64-
65-
return { entries: await includesPromise, traits: await traitsPromise };
136+
catch {
137+
// Intentionally swallow any exception.
138+
}
139+
telemetryProperties["error"] = "true";
140+
throw exception; // Throw the exception for auto-retry.
141+
} finally {
142+
telemetry.logCopilotEvent('RelatedFilesProvider', telemetryProperties);
143+
}
66144
}
67145
);
68146
}

0 commit comments

Comments
 (0)