diff --git a/Extension/package.json b/Extension/package.json index 662949792..85517de31 100644 --- a/Extension/package.json +++ b/Extension/package.json @@ -6464,9 +6464,9 @@ { "id": "cpptools-lmtool-configuration", "name": "cpp", - "displayName": "C/C++ configuration", + "displayName": "%c_cpp.languageModelTools.configuration.displayName%", "canBeInvokedManually": true, - "userDescription": "Configuration of the active C or C++ file, like language standard version and target platform.", + "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": {} diff --git a/Extension/package.nls.json b/Extension/package.nls.json index 774521629..3e0191ef5 100644 --- a/Extension/package.nls.json +++ b/Extension/package.nls.json @@ -1013,5 +1013,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." } diff --git a/Extension/src/LanguageServer/client.ts b/Extension/src/LanguageServer/client.ts index 30d60c854..18033a160 100644 --- a/Extension/src/LanguageServer/client.ts +++ b/Extension/src/LanguageServer/client.ts @@ -61,7 +61,7 @@ import * as refs from './references'; import { CppSettings, OtherSettings, SettingsParams, WorkspaceFolderSettingsParams, getEditorConfigSettings } 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) { @@ -799,7 +799,7 @@ export interface Client { setShowConfigureIntelliSenseButton(show: boolean): void; addTrustedCompiler(path: string): Promise; getIncludes(maxDepth: number): Promise; - getChatContext(): Promise; + getChatContext(token: vscode.CancellationToken): Promise; } export function createClient(workspaceFolder?: vscode.WorkspaceFolder): Client { @@ -2214,9 +2214,17 @@ export class DefaultClient implements Client { return this.languageClient.sendRequest(IncludesRequest, params); } - public async getChatContext(): Promise { - await this.ready; - return this.languageClient.sendRequest(CppContextRequest, null); + public async getChatContext(token: vscode.CancellationToken): Promise { + await withCancellation(this.ready, token); + const result = await this.languageClient.sendRequest(CppContextRequest, null, token); + + // sendRequest() won't throw on cancellation, but will return an + // unexpected object with an error code and message. + if (token.isCancellationRequested) { + throw new vscode.CancellationError(); + } + + return result; } /** @@ -4099,5 +4107,5 @@ class NullClient implements Client { setShowConfigureIntelliSenseButton(show: boolean): void { } addTrustedCompiler(path: string): Promise { return Promise.resolve(); } getIncludes(): Promise { return Promise.resolve({} as GetIncludesResult); } - getChatContext(): Promise { return Promise.resolve({} as ChatContextResult); } + getChatContext(token: vscode.CancellationToken): Promise { return Promise.resolve({} as ChatContextResult); } } diff --git a/Extension/src/LanguageServer/extension.ts b/Extension/src/LanguageServer/extension.ts index 8da6ddc7d..e091cc37b 100644 --- a/Extension/src/LanguageServer/extension.ts +++ b/Extension/src/LanguageServer/extension.ts @@ -251,7 +251,7 @@ export async function activate(): Promise { } if (util.extensionContext) { - const tool = vscode.lm.registerTool('cpptools-lmtool-configuration', new CppConfigurationLanguageModelTool(clients)); + const tool = vscode.lm.registerTool('cpptools-lmtool-configuration', new CppConfigurationLanguageModelTool()); disposables.push(tool); } } diff --git a/Extension/src/LanguageServer/lmTool.ts b/Extension/src/LanguageServer/lmTool.ts index 9e7d18b23..91d877f18 100644 --- a/Extension/src/LanguageServer/lmTool.ts +++ b/Extension/src/LanguageServer/lmTool.ts @@ -8,7 +8,7 @@ import * as vscode from 'vscode'; import * as util from '../common'; import * as telemetry from '../telemetry'; import { ChatContextResult } from './client'; -import { ClientCollection } from './clientCollection'; +import { getClients } from './extension'; const knownValues: { [Property in keyof ChatContextResult]?: { [id: string]: string } } = { language: { @@ -50,19 +50,17 @@ class StringLanguageModelToolResult implements vscode.LanguageModelToolResult export class CppConfigurationLanguageModelTool implements vscode.LanguageModelTool { - public constructor(private readonly clients: ClientCollection) {} - - public async invoke(_parameters: any, _token: vscode.CancellationToken): Promise { - return new StringLanguageModelToolResult(await this.getContext()); + public async invoke(_parameters: any, token: vscode.CancellationToken): Promise { + return new StringLanguageModelToolResult(await this.getContext(token)); } - private async getContext(): Promise { + private async getContext(token: vscode.CancellationToken): Promise { 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 (this.clients?.ActiveClient?.getChatContext() ?? undefined); + const chatContext: ChatContextResult | undefined = await (getClients()?.ActiveClient?.getChatContext(token) ?? undefined); if (!chatContext) { return 'No configuration information is available for the active document.'; } diff --git a/Extension/src/LanguageServer/utils.ts b/Extension/src/LanguageServer/utils.ts index da3d29b69..4f4d1384b 100644 --- a/Extension/src/LanguageServer/utils.ts +++ b/Extension/src/LanguageServer/utils.ts @@ -1,101 +1,108 @@ -/* -------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All Rights Reserved. - * See 'LICENSE' in the project root for license information. - * ------------------------------------------------------------------------------------------ */ -'use strict'; -import * as os from 'os'; -import * as vscode from 'vscode'; -import { Range } from 'vscode-languageclient'; -import { SessionState } from '../sessionState'; -import { Location, TextEdit } from './commonTypes'; -import { CppSettings } from './settings'; - -export function makeLspRange(vscRange: vscode.Range): Range { - return { - start: { line: vscRange.start.line, character: vscRange.start.character }, - end: { line: vscRange.end.line, character: vscRange.end.character } - }; -} - -export function makeVscodeRange(cpptoolsRange: Range): vscode.Range { - return new vscode.Range(cpptoolsRange.start.line, cpptoolsRange.start.character, cpptoolsRange.end.line, cpptoolsRange.end.character); -} - -export function makeVscodeLocation(cpptoolsLocation: Location): vscode.Location { - return new vscode.Location(vscode.Uri.parse(cpptoolsLocation.uri), makeVscodeRange(cpptoolsLocation.range)); -} - -export function makeVscodeTextEdits(cpptoolsTextEdits: TextEdit[]): vscode.TextEdit[] { - return cpptoolsTextEdits.map(textEdit => new vscode.TextEdit(makeVscodeRange(textEdit.range), textEdit.newText)); -} - -export function rangeEquals(range1: vscode.Range | Range, range2: vscode.Range | Range): boolean { - return range1.start.line === range2.start.line && range1.start.character === range2.start.character && - range1.end.line === range2.end.line && range1.end.character === range2.end.character; -} - -// Check this before attempting to switch a document from C to C++. -export function shouldChangeFromCToCpp(document: vscode.TextDocument): boolean { - if (document.fileName.endsWith(".C") || document.fileName.endsWith(".H")) { - const cppSettings: CppSettings = new CppSettings(); - if (cppSettings.autoAddFileAssociations) { - return !docsChangedFromCppToC.has(document.fileName); - } - // We could potentially add a new setting to enable switching to cpp even when files.associations isn't changed. - } - return false; -} - -// Call this before changing from C++ to C. -export function handleChangedFromCppToC(document: vscode.TextDocument): void { - if (shouldChangeFromCToCpp(document)) { - docsChangedFromCppToC.add(document.fileName); - } -} - -export function showInstallCompilerWalkthrough(): void { - // Because we need to conditionally enable/disable steps to alter their contents, - // we need to determine which step is actually visible. If the steps change, this - // logic will need to change to reflect them. - enum Step { - Activation = 'awaiting.activation', - NoCompilers = 'no.compilers.found', - Verify = 'verify.compiler' - } - - const step = (() => { - if (!SessionState.scanForCompilersDone.get()) { - return Step.Activation; - } else if (!SessionState.trustedCompilerFound.get()) { - return Step.NoCompilers; - } else { - return Step.Verify; - } - })(); - - const platform = (() => { - switch (os.platform()) { - case 'win32': return 'windows'; - case 'darwin': return 'mac'; - default: return 'linux'; - } - })(); - - const version = (platform === 'windows') ? SessionState.windowsVersion.get() : ''; - - const index = `ms-vscode.cpptools#${step}.${platform}${version}`; - - void vscode.commands.executeCommand( - 'workbench.action.openWalkthrough', - { category: 'ms-vscode.cpptools#cppWelcome', step: index }, - false) - // Run it twice for now because of VS Code bug #187958 - .then(() => vscode.commands.executeCommand( - "workbench.action.openWalkthrough", - { category: 'ms-vscode.cpptools#cppWelcome', step: index }, - false) - ); - return; -} - -const docsChangedFromCppToC: Set = new Set(); +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All Rights Reserved. + * See 'LICENSE' in the project root for license information. + * ------------------------------------------------------------------------------------------ */ +'use strict'; +import * as os from 'os'; +import * as vscode from 'vscode'; +import { Range } from 'vscode-languageclient'; +import { SessionState } from '../sessionState'; +import { Location, TextEdit } from './commonTypes'; +import { CppSettings } from './settings'; + +export function makeLspRange(vscRange: vscode.Range): Range { + return { + start: { line: vscRange.start.line, character: vscRange.start.character }, + end: { line: vscRange.end.line, character: vscRange.end.character } + }; +} + +export function makeVscodeRange(cpptoolsRange: Range): vscode.Range { + return new vscode.Range(cpptoolsRange.start.line, cpptoolsRange.start.character, cpptoolsRange.end.line, cpptoolsRange.end.character); +} + +export function makeVscodeLocation(cpptoolsLocation: Location): vscode.Location { + return new vscode.Location(vscode.Uri.parse(cpptoolsLocation.uri), makeVscodeRange(cpptoolsLocation.range)); +} + +export function makeVscodeTextEdits(cpptoolsTextEdits: TextEdit[]): vscode.TextEdit[] { + return cpptoolsTextEdits.map(textEdit => new vscode.TextEdit(makeVscodeRange(textEdit.range), textEdit.newText)); +} + +export function rangeEquals(range1: vscode.Range | Range, range2: vscode.Range | Range): boolean { + return range1.start.line === range2.start.line && range1.start.character === range2.start.character && + range1.end.line === range2.end.line && range1.end.character === range2.end.character; +} + +// Check this before attempting to switch a document from C to C++. +export function shouldChangeFromCToCpp(document: vscode.TextDocument): boolean { + if (document.fileName.endsWith(".C") || document.fileName.endsWith(".H")) { + const cppSettings: CppSettings = new CppSettings(); + if (cppSettings.autoAddFileAssociations) { + return !docsChangedFromCppToC.has(document.fileName); + } + // We could potentially add a new setting to enable switching to cpp even when files.associations isn't changed. + } + return false; +} + +// Call this before changing from C++ to C. +export function handleChangedFromCppToC(document: vscode.TextDocument): void { + if (shouldChangeFromCToCpp(document)) { + docsChangedFromCppToC.add(document.fileName); + } +} + +export function showInstallCompilerWalkthrough(): void { + // Because we need to conditionally enable/disable steps to alter their contents, + // we need to determine which step is actually visible. If the steps change, this + // logic will need to change to reflect them. + enum Step { + Activation = 'awaiting.activation', + NoCompilers = 'no.compilers.found', + Verify = 'verify.compiler' + } + + const step = (() => { + if (!SessionState.scanForCompilersDone.get()) { + return Step.Activation; + } else if (!SessionState.trustedCompilerFound.get()) { + return Step.NoCompilers; + } else { + return Step.Verify; + } + })(); + + const platform = (() => { + switch (os.platform()) { + case 'win32': return 'windows'; + case 'darwin': return 'mac'; + default: return 'linux'; + } + })(); + + const version = (platform === 'windows') ? SessionState.windowsVersion.get() : ''; + + const index = `ms-vscode.cpptools#${step}.${platform}${version}`; + + void vscode.commands.executeCommand( + 'workbench.action.openWalkthrough', + { category: 'ms-vscode.cpptools#cppWelcome', step: index }, + false) + // Run it twice for now because of VS Code bug #187958 + .then(() => vscode.commands.executeCommand( + "workbench.action.openWalkthrough", + { category: 'ms-vscode.cpptools#cppWelcome', step: index }, + false) + ); + return; +} + +const docsChangedFromCppToC: Set = new Set(); + +export async function withCancellation(promise: Promise, token: vscode.CancellationToken): Promise { + return new Promise((resolve, reject) => { + token.onCancellationRequested(() => reject(new vscode.CancellationError())); + promise.then((value) => resolve(value), (reason) => reject(reason)); + }); +}