Skip to content

Commit

Permalink
Add C++ configuration as a language model tool (luca) (#12685)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukka authored Sep 6, 2024
1 parent 8718b96 commit 731cccd
Show file tree
Hide file tree
Showing 8 changed files with 288 additions and 107 deletions.
2 changes: 1 addition & 1 deletion Extension/.vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
},
"[typescript]": {
"editor.tabSize": 4,
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"editor.defaultFormatter": "vscode.typescript-language-features",
"editor.formatOnSave": true,
"files.insertFinalNewline": true,
"editor.codeActionsOnSave": {
Expand Down
16 changes: 15 additions & 1 deletion Extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"Snippets"
],
"enabledApiProposals": [
"terminalDataWriteEvent"
"terminalDataWriteEvent",
"lmTools"
],
"capabilities": {
"untrustedWorkspaces": {
Expand Down Expand Up @@ -6440,6 +6441,19 @@
"description": "%c_cpp.codeActions.refactor.extract.function.description%"
}
}
],
"languageModelTools": [
{
"id": "cpptools-lmtool-configuration",
"name": "cpp",
"displayName": "%c_cpp.languageModelTools.configuration.displayName%",
"canBeInvokedManually": true,
"userDescription": "%c_cpp.languageModelTools.configuration.userDescription%",
"modelDescription": "For the active C or C++ file, this tool provides: the language (C, C++, or CUDA), the language standard version (such as C++11, C++14, C++17, or C++20), the compiler (such as GCC, Clang, or MSVC), the target platform (such as x86, x64, or ARM), and the target architecture (such as 32-bit or 64-bit).",
"icon": "$(file-code)",
"parametersSchema": {},
"when": "(config.C_Cpp.experimentalFeatures =~ /^[eE]nabled$/)"
}
]
},
"scripts": {
Expand Down
4 changes: 3 additions & 1 deletion Extension/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -1006,5 +1006,7 @@
"c_cpp.configuration.refactoring.includeHeader.markdownDescription": "Controls whether to include the header file of a refactored function/symbol to its corresponding source file when doing a refactoring action, such as create declaration/definition.",
"c_cpp.configuration.refactoring.includeHeader.always.description": "Always include the header file if it is not included explicitly in its source file.",
"c_cpp.configuration.refactoring.includeHeader.ifNeeded.description": "Only include the header file if it is not included explicitly in its source file or through implicit inclusion.",
"c_cpp.configuration.refactoring.includeHeader.never.description": "Never include the header file."
"c_cpp.configuration.refactoring.includeHeader.never.description": "Never include the header file.",
"c_cpp.languageModelTools.configuration.displayName": "C/C++ configuration",
"c_cpp.languageModelTools.configuration.userDescription": "Configuration of the active C or C++ file, like language standard version and target platform."
}
36 changes: 33 additions & 3 deletions Extension/src/LanguageServer/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import * as fs from 'fs';
import * as os from 'os';
import { SourceFileConfiguration, SourceFileConfigurationItem, Version, WorkspaceBrowseConfiguration } from 'vscode-cpptools';
import { IntelliSenseStatus, Status } from 'vscode-cpptools/out/testApi';
import { CloseAction, DidOpenTextDocumentParams, ErrorAction, LanguageClientOptions, NotificationType, Position, Range, RequestType, TextDocumentIdentifier, TextDocumentPositionParams } from 'vscode-languageclient';
import { CloseAction, DidOpenTextDocumentParams, ErrorAction, LanguageClientOptions, NotificationType, Position, Range, RequestType, ResponseError, TextDocumentIdentifier, TextDocumentPositionParams } from 'vscode-languageclient';
import { LanguageClient, ServerOptions } from 'vscode-languageclient/node';
import * as nls from 'vscode-nls';
import { DebugConfigurationProvider } from '../Debugger/configurationProvider';
Expand Down Expand Up @@ -58,12 +58,12 @@ import { cachedEditorConfigSettings, getEditorConfigSettings } from './editorCon
import { CppSourceStr, clients, configPrefix, updateLanguageConfigurations, usesCrashHandler, watchForCrashes } from './extension';
import { LocalizeStringParams, getLocaleId, getLocalizedString } from './localization';
import { PersistentFolderState, PersistentWorkspaceState } from './persistentState';
import { createProtocolFilter } from './protocolFilter';
import { RequestCancelled, ServerCancelled, createProtocolFilter } from './protocolFilter';
import * as refs from './references';
import { CppSettings, OtherSettings, SettingsParams, WorkspaceFolderSettingsParams } from './settings';
import { SettingsTracker } from './settingsTracker';
import { ConfigurationType, LanguageStatusUI, getUI } from './ui';
import { handleChangedFromCppToC, makeLspRange, makeVscodeLocation, makeVscodeRange } from './utils';
import { handleChangedFromCppToC, makeLspRange, makeVscodeLocation, makeVscodeRange, withCancellation } from './utils';
import minimatch = require("minimatch");

function deepCopy(obj: any) {
Expand Down Expand Up @@ -542,6 +542,14 @@ interface GetIncludesResult {
includedFiles: string[];
}

export interface ChatContextResult {
language: string;
standardVersion: string;
compiler: string;
targetPlatform: string;
targetArchitecture: string;
}

// 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 @@ -562,6 +570,7 @@ const GoToDirectiveInGroupRequest: RequestType<GoToDirectiveInGroupParams, Posit
const GenerateDoxygenCommentRequest: RequestType<GenerateDoxygenCommentParams, GenerateDoxygenCommentResult | undefined, void> = new RequestType<GenerateDoxygenCommentParams, GenerateDoxygenCommentResult, void>('cpptools/generateDoxygenComment');
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');

// Notifications to the server
const DidOpenNotification: NotificationType<DidOpenTextDocumentParams> = new NotificationType<DidOpenTextDocumentParams>('textDocument/didOpen');
Expand Down Expand Up @@ -792,6 +801,7 @@ export interface Client {
setShowConfigureIntelliSenseButton(show: boolean): void;
addTrustedCompiler(path: string): Promise<void>;
getIncludes(maxDepth: number): Promise<GetIncludesResult>;
getChatContext(token: vscode.CancellationToken): Promise<ChatContextResult>;
}

export function createClient(workspaceFolder?: vscode.WorkspaceFolder): Client {
Expand Down Expand Up @@ -2234,6 +2244,25 @@ export class DefaultClient implements Client {
return this.languageClient.sendRequest(IncludesRequest, params);
}

public async getChatContext(token: vscode.CancellationToken): Promise<ChatContextResult> {
await withCancellation(this.ready, token);
let result: ChatContextResult;
try {
result = await this.languageClient.sendRequest(CppContextRequest, null, token);
} catch (e: any) {
if (e instanceof ResponseError && (e.code === RequestCancelled || e.code === ServerCancelled)) {
throw new vscode.CancellationError();
}

throw e;
}
if (token.isCancellationRequested) {
throw new vscode.CancellationError();
}

return result;
}

/**
* a Promise that can be awaited to know when it's ok to proceed.
*
Expand Down Expand Up @@ -4119,4 +4148,5 @@ class NullClient implements Client {
setShowConfigureIntelliSenseButton(show: boolean): void { }
addTrustedCompiler(path: string): Promise<void> { return Promise.resolve(); }
getIncludes(): Promise<GetIncludesResult> { return Promise.resolve({} as GetIncludesResult); }
getChatContext(token: vscode.CancellationToken): Promise<ChatContextResult> { return Promise.resolve({} as ChatContextResult); }
}
6 changes: 6 additions & 0 deletions Extension/src/LanguageServer/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { CodeActionDiagnosticInfo, CodeAnalysisDiagnosticIdentifiersAndUri, code
import { CppBuildTaskProvider } from './cppBuildTaskProvider';
import { getCustomConfigProviders } from './customProviders';
import { getLanguageConfig } from './languageConfig';
import { CppConfigurationLanguageModelTool } from './lmTool';
import { PersistentState } from './persistentState';
import { NodeType, TreeNode } from './referencesModel';
import { CppSettings } from './settings';
Expand Down Expand Up @@ -248,6 +249,11 @@ export async function activate(): Promise<void> {
clients.timeTelemetryCollector.setFirstFile(activeEditor.document.uri);
activeDocument = activeEditor.document;
}

if (util.extensionContext && new CppSettings().experimentalFeatures) {
const tool = vscode.lm.registerTool('cpptools-lmtool-configuration', new CppConfigurationLanguageModelTool());
disposables.push(tool);
}
}

export function updateLanguageConfigurations(): void {
Expand Down
102 changes: 102 additions & 0 deletions Extension/src/LanguageServer/lmTool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
'use strict';

import * as vscode from 'vscode';
import { localize } from 'vscode-nls';
import * as util from '../common';
import * as logger from '../logger';
import * as telemetry from '../telemetry';
import { ChatContextResult } from './client';
import { getClients } from './extension';

const knownValues: { [Property in keyof ChatContextResult]?: { [id: string]: string } } = {
language: {
'c': 'C',
'cpp': 'C++',
'cuda-cpp': 'CUDA C++'
},
compiler: {
'msvc': 'MSVC',
'clang': 'Clang',
'gcc': 'GCC'
},
standardVersion: {
'c++98': 'C++98',
'c++03': 'C++03',
'c++11': 'C++11',
'c++14': 'C++14',
'c++17': 'C++17',
'c++20': 'C++20',
'c++23': 'C++23',
'c90': "C90",
'c99': "C99",
'c11': "C11",
'c17': "C17",
'c23': "C23"
},
targetPlatform: {
'windows': 'Windows',
'Linux': 'Linux',
'macos': 'macOS'
}
};

class StringLanguageModelToolResult implements vscode.LanguageModelToolResult {
public constructor(public readonly value: string) { }
public toString(): string { return this.value; }
}

export class CppConfigurationLanguageModelTool implements vscode.LanguageModelTool {
public async invoke(_parameters: any, token: vscode.CancellationToken): Promise<vscode.LanguageModelToolResult> {
return new StringLanguageModelToolResult(await this.getContext(token));
}

private async getContext(token: vscode.CancellationToken): Promise<string> {
try {
const currentDoc = vscode.window.activeTextEditor?.document;
if (!currentDoc || (!util.isCpp(currentDoc) && !util.isHeaderFile(currentDoc.uri))) {
return 'The active document is not a C, C++, or CUDA file.';
}

const chatContext: ChatContextResult | undefined = await (getClients()?.ActiveClient?.getChatContext(token) ?? undefined);
if (!chatContext) {
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
});

for (const key in knownValues) {
const knownKey = key as keyof ChatContextResult;
if (knownValues[knownKey] && chatContext[knownKey]) {
chatContext[knownKey] = knownValues[knownKey][chatContext[knownKey]] || chatContext[knownKey];
}
}

return `The user is working on a ${chatContext.language} project. The project uses language version ${chatContext.standardVersion}, compiles using the ${chatContext.compiler} compiler, targets the ${chatContext.targetPlatform} platform, and targets the ${chatContext.targetArchitecture} architecture.`;
}
catch {
await this.reportError();
return "";
}
}

private async reportError(): Promise<void> {
try {
logger.getOutputChannelLogger().appendLine(localize("copilot.cppcontext.error", "Error while retrieving the #cpp context."));
}
catch {
// Intentionally swallow any exception.
}
}
}
Loading

0 comments on commit 731cccd

Please sign in to comment.