From 36cea3678419aba278ca1a48f86aafe461b12cd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dirk=20B=C3=A4umer?= Date: Wed, 27 Aug 2025 04:05:29 +0200 Subject: [PATCH 1/4] Register the CPP context provider with Copilot Chat as well. (#13877) * Register context provider API with copilot chat as well. --- .../copilotCompletionContextProvider.ts | 59 +++++++++++++++---- .../src/LanguageServer/copilotProviders.ts | 39 ++++++++++-- 2 files changed, 81 insertions(+), 17 deletions(-) diff --git a/Extension/src/LanguageServer/copilotCompletionContextProvider.ts b/Extension/src/LanguageServer/copilotCompletionContextProvider.ts index cdf75308f..a456ab73c 100644 --- a/Extension/src/LanguageServer/copilotCompletionContextProvider.ts +++ b/Extension/src/LanguageServer/copilotCompletionContextProvider.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All Rights Reserved. * See 'LICENSE' in the project root for license information. * ------------------------------------------------------------------------------------------ */ -import { ContextResolver, ResolveRequest, SupportedContextItem } from '@github/copilot-language-server'; +import { ContextResolver, ResolveRequest, SupportedContextItem, type ContextProvider } from '@github/copilot-language-server'; import { randomUUID } from 'crypto'; import * as vscode from 'vscode'; import { DocumentSelector } from 'vscode-languageserver-protocol'; @@ -11,7 +11,7 @@ import { getOutputChannelLogger, Logger } from '../logger'; import * as telemetry from '../telemetry'; import { CopilotCompletionContextResult } from './client'; import { CopilotCompletionContextTelemetry } from './copilotCompletionContextTelemetry'; -import { getCopilotApi } from './copilotProviders'; +import { getCopilotChatApi, getCopilotClientApi, type CopilotContextProviderAPI } from './copilotProviders'; import { clients } from './extension'; import { CppSettings } from './settings'; @@ -83,7 +83,7 @@ export class CopilotCompletionContextProvider implements ContextResolver = {}; const registerCopilotContextProvider = 'registerCopilotContextProvider'; try { - const copilotApi = await getCopilotApi(); - if (!copilotApi) { throw new CopilotContextProviderException("getCopilotApi() returned null, Copilot is missing or inactive."); } - const hasGetContextProviderAPI = "getContextProviderAPI" in copilotApi; - if (!hasGetContextProviderAPI) { throw new CopilotContextProviderException("getContextProviderAPI() is not available."); } - const contextAPI = await copilotApi.getContextProviderAPI("v1"); - if (!contextAPI) { throw new CopilotContextProviderException("getContextProviderAPI(v1) returned null."); } - this.contextProviderDisposable = contextAPI.registerContextProvider({ + const copilotApi = await getCopilotClientApi(); + const copilotChatApi = await getCopilotChatApi(); + if (!copilotApi && !copilotChatApi) { throw new CopilotContextProviderException("getCopilotApi() returned null, Copilot is missing or inactive."); } + const contextProvider = { id: CopilotCompletionContextProvider.providerId, selector: CopilotCompletionContextProvider.defaultCppDocumentSelector, resolver: this - }); - properties["cppCodeSnippetsProviderRegistered"] = "true"; + }; + type InstallSummary = { hasGetContextProviderAPI: boolean; hasAPI: boolean }; + const installSummary: { client?: InstallSummary; chat?: InstallSummary } = {}; + if (copilotApi) { + installSummary.client = await this.installContextProvider(copilotApi, contextProvider); + } + if (copilotChatApi) { + installSummary.chat = await this.installContextProvider(copilotChatApi, contextProvider); + } + if (installSummary.client?.hasAPI || installSummary.chat?.hasAPI) { + properties["cppCodeSnippetsProviderRegistered"] = "true"; + } else { + if (installSummary.client?.hasGetContextProviderAPI === false && + installSummary.chat?.hasGetContextProviderAPI === false) { + throw new CopilotContextProviderException("getContextProviderAPI() is not available."); + } else { + throw new CopilotContextProviderException("getContextProviderAPI(v1) returned null."); + } + } } catch (e) { console.debug("Failed to register the Copilot Context Provider."); properties["error"] = "Failed to register the Copilot Context Provider"; @@ -466,4 +485,18 @@ ${copilotCompletionContext?.areSnippetsMissing ? "(missing code snippets)" : ""} telemetry.logCopilotEvent(registerCopilotContextProvider, { ...properties }); } } + + private async installContextProvider(copilotAPI: CopilotContextProviderAPI, contextProvider: ContextProvider): Promise<{ hasGetContextProviderAPI: boolean; hasAPI: boolean }> { + const hasGetContextProviderAPI = typeof copilotAPI.getContextProviderAPI === 'function'; + if (hasGetContextProviderAPI) { + const contextAPI = await copilotAPI.getContextProviderAPI("v1"); + if (contextAPI) { + this.contextProviderDisposables = this.contextProviderDisposables ?? []; + this.contextProviderDisposables.push(contextAPI.registerContextProvider(contextProvider)); + } + return { hasGetContextProviderAPI, hasAPI: contextAPI !== undefined }; + } else { + return { hasGetContextProviderAPI: false, hasAPI: false }; + } + } } diff --git a/Extension/src/LanguageServer/copilotProviders.ts b/Extension/src/LanguageServer/copilotProviders.ts index e0551edcb..31cf21f3e 100644 --- a/Extension/src/LanguageServer/copilotProviders.ts +++ b/Extension/src/LanguageServer/copilotProviders.ts @@ -24,7 +24,11 @@ export interface CopilotTrait { promptTextOverride?: string; } -export interface CopilotApi { +export interface CopilotContextProviderAPI { + getContextProviderAPI(version: string): Promise; +} + +export interface CopilotApi extends CopilotContextProviderAPI { registerRelatedFilesProvider( providerId: { extensionId: string; languageId: string }, callback: ( @@ -33,11 +37,10 @@ export interface CopilotApi { cancellationToken: vscode.CancellationToken ) => Promise<{ entries: vscode.Uri[]; traits?: CopilotTrait[] } | undefined> ): Disposable; - getContextProviderAPI(version: string): Promise; } export async function registerRelatedFilesProvider(): Promise { - const api = await getCopilotApi(); + const api = await getCopilotClientApi(); if (util.extensionContext && api) { try { for (const languageId of ['c', 'cpp', 'cuda-cpp']) { @@ -129,7 +132,7 @@ async function getIncludes(uri: vscode.Uri, maxDepth: number): Promise { +export async function getCopilotClientApi(): Promise { const copilotExtension = vscode.extensions.getExtension('github.copilot'); if (!copilotExtension) { return undefined; @@ -145,3 +148,31 @@ export async function getCopilotApi(): Promise { return copilotExtension.exports; } } + +export async function getCopilotChatApi(): Promise { + type CopilotChatApi = { getAPI?(version: number): CopilotContextProviderAPI | undefined }; + const copilotExtension = vscode.extensions.getExtension('github.copilot-chat'); + if (!copilotExtension) { + return undefined; + } + + let exports: CopilotChatApi | undefined; + if (!copilotExtension.isActive) { + try { + exports = await copilotExtension.activate(); + } catch { + return undefined; + } + } else { + exports = copilotExtension.exports; + } + if (!exports || typeof exports.getAPI !== 'function') { + return undefined; + } + const result = exports.getAPI(1); + return result; +} + +interface Disposable { + dispose(): void; +} From 303b838a41d31509df0456fa6a9b38bbbd716ff7 Mon Sep 17 00:00:00 2001 From: Sean McManus Date: Fri, 5 Sep 2025 04:34:59 -0700 Subject: [PATCH 2/4] Update changelog and version for 1.26.4 --- Extension/CHANGELOG.md | 4 ++++ Extension/package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Extension/CHANGELOG.md b/Extension/CHANGELOG.md index 3893dbcf1..1a8a90771 100644 --- a/Extension/CHANGELOG.md +++ b/Extension/CHANGELOG.md @@ -1,5 +1,9 @@ # C/C++ for Visual Studio Code Changelog +## Version 1.26.4: September 8, 2025 +* Update GitHub Copilot APIs. [PR #13877](https://github.com/microsoft/vscode-cpptools/pull/13877) + * Thank you for the contribution. [@dbaeumer (Dirk Bäumer)](https://github.com/dbaeumer) + ## Version 1.26.3: June 24, 2025 ### New Feature * Improve the context provided for C++ Copilot suggestions. diff --git a/Extension/package.json b/Extension/package.json index be894dc79..5cf239c94 100644 --- a/Extension/package.json +++ b/Extension/package.json @@ -2,7 +2,7 @@ "name": "cpptools", "displayName": "C/C++", "description": "C/C++ IntelliSense, debugging, and code browsing.", - "version": "1.26.3-main", + "version": "1.26.4-main", "publisher": "ms-vscode", "icon": "LanguageCCPP_color_128x.png", "readme": "README.md", From f4f3d8ca8b29bb05d68d4d6ac342f67488b2e5aa Mon Sep 17 00:00:00 2001 From: Sean McManus Date: Tue, 2 Sep 2025 13:07:54 -0700 Subject: [PATCH 3/4] Add more state info to cpptools crash logging. (#13888) * Add more state info to cpptools crash logging. --- Extension/src/LanguageServer/client.ts | 127 +++++++++++++--------- Extension/src/LanguageServer/extension.ts | 3 + 2 files changed, 79 insertions(+), 51 deletions(-) diff --git a/Extension/src/LanguageServer/client.ts b/Extension/src/LanguageServer/client.ts index 1b33615b0..b3a8d56c9 100644 --- a/Extension/src/LanguageServer/client.ts +++ b/Extension/src/LanguageServer/client.ts @@ -59,7 +59,7 @@ import { CopilotCompletionContextFeatures, CopilotCompletionContextProvider } fr import { CustomConfigurationProvider1, getCustomConfigProviders, isSameProviderExtensionId } from './customProviders'; import { DataBinding } from './dataBinding'; import { cachedEditorConfigSettings, getEditorConfigSettings } from './editorConfig'; -import { CppSourceStr, clients, configPrefix, initializeIntervalTimer, updateLanguageConfigurations, usesCrashHandler, watchForCrashes } from './extension'; +import { CppSourceStr, clients, configPrefix, initializeIntervalTimer, isWritingCrashCallStack, updateLanguageConfigurations, usesCrashHandler, watchForCrashes } from './extension'; import { LocalizeStringParams, getLocaleId, getLocalizedString } from './localization'; import { PersistentFolderState, PersistentState, PersistentWorkspaceState } from './persistentState'; import { RequestCancelled, ServerCancelled, createProtocolFilter } from './protocolFilter'; @@ -942,6 +942,8 @@ export class DefaultClient implements Client { public getShowConfigureIntelliSenseButton(): boolean { return this.showConfigureIntelliSenseButton; } public setShowConfigureIntelliSenseButton(show: boolean): void { this.showConfigureIntelliSenseButton = show; } + private lastInvokedLspMessage: string = ""; // e.g. cpptools/hover + /** * don't use this.rootFolder directly since it can be undefined */ @@ -1677,7 +1679,6 @@ export class DefaultClient implements Client { closed: () => { languageClientCrashTimes.push(Date.now()); languageClientCrashedNeedsRestart = true; - telemetry.logLanguageServerEvent("languageClientCrash"); let restart: boolean = true; if (languageClientCrashTimes.length < 5) { void clients.recreateClients(); @@ -1691,6 +1692,26 @@ export class DefaultClient implements Client { void clients.recreateClients(); } } + + // Wait 1 second to allow time for the file watcher to signal a crash call stack write has occurred. + setTimeout(() => { + telemetry.logLanguageServerEvent("languageClientCrash", + { + lastInvokedLspMessage: this.lastInvokedLspMessage + }, + { + restarting: Number(restart), + writingCrashCallStack: Number(isWritingCrashCallStack), + initializingWorkspace: Number(this.model.isInitializingWorkspace.Value), + indexingWorkspace: Number(this.model.isIndexingWorkspace.Value), + parsingWorkspace: Number(this.model.isParsingWorkspace.Value), + parsingFiles: Number(this.model.isParsingFiles.Value), + updatingIntelliSense: Number(this.model.isUpdatingIntelliSense.Value), + runningCodeAnalysis: Number(this.model.isRunningCodeAnalysis.Value) + } + ); + }, 1000); + const message: string = restart ? localize('server.crashed.restart', 'The language server crashed. Restarting...') : localize('server.crashed2', 'The language server crashed 5 times in the last 3 minutes. It will not be restarted.'); @@ -2747,55 +2768,59 @@ export class DefaultClient implements Client { const message: string = notificationBody.status; util.setProgress(util.getProgressExecutableSuccess()); const testHook: TestHook = getTestHook(); - if (message.endsWith("Idle")) { - const status: IntelliSenseStatus = { status: Status.Idle }; - testHook.updateStatus(status); - } else if (message.endsWith("Parsing")) { - this.model.isParsingWorkspace.Value = true; - this.model.isInitializingWorkspace.Value = false; - this.model.isIndexingWorkspace.Value = false; - const status: IntelliSenseStatus = { status: Status.TagParsingBegun }; - testHook.updateStatus(status); - } else if (message.endsWith("Initializing")) { - this.model.isInitializingWorkspace.Value = true; - this.model.isIndexingWorkspace.Value = false; - this.model.isParsingWorkspace.Value = false; - } else if (message.endsWith("Indexing")) { - this.model.isIndexingWorkspace.Value = true; - this.model.isInitializingWorkspace.Value = false; - this.model.isParsingWorkspace.Value = false; - } else if (message.endsWith("files")) { - this.model.isParsingFiles.Value = true; - } else if (message.endsWith("IntelliSense")) { - timeStamp = Date.now(); - this.model.isUpdatingIntelliSense.Value = true; - const status: IntelliSenseStatus = { status: Status.IntelliSenseCompiling }; - testHook.updateStatus(status); - } else if (message.endsWith("IntelliSense done")) { - getOutputChannelLogger().appendLineAtLevel(6, localize("update.intellisense.time", "Update IntelliSense time (sec): {0}", (Date.now() - timeStamp) / 1000)); - this.model.isUpdatingIntelliSense.Value = false; - const status: IntelliSenseStatus = { status: Status.IntelliSenseReady }; - testHook.updateStatus(status); - } else if (message.endsWith("Parsing done")) { // Tag Parser Ready - this.model.isParsingWorkspace.Value = false; - const status: IntelliSenseStatus = { status: Status.TagParsingDone }; - testHook.updateStatus(status); - util.setProgress(util.getProgressParseRootSuccess()); - } else if (message.endsWith("files done")) { - this.model.isParsingFiles.Value = false; - } else if (message.endsWith("Analysis")) { - this.model.isRunningCodeAnalysis.Value = true; - this.model.codeAnalysisTotal.Value = 1; - this.model.codeAnalysisProcessed.Value = 0; - } else if (message.endsWith("Analysis done")) { - this.model.isRunningCodeAnalysis.Value = false; - } else if (message.includes("Squiggles Finished - File name:")) { - const index: number = message.lastIndexOf(":"); - const name: string = message.substring(index + 2); - const status: IntelliSenseStatus = { status: Status.IntelliSenseReady, filename: name }; - testHook.updateStatus(status); - } else if (message.endsWith("No Squiggles")) { - util.setIntelliSenseProgress(util.getProgressIntelliSenseNoSquiggles()); + if (message.startsWith("C_Cpp: ")) { + if (message.endsWith("Idle")) { + const status: IntelliSenseStatus = { status: Status.Idle }; + testHook.updateStatus(status); + } else if (message.endsWith("Parsing")) { + this.model.isParsingWorkspace.Value = true; + this.model.isInitializingWorkspace.Value = false; + this.model.isIndexingWorkspace.Value = false; + const status: IntelliSenseStatus = { status: Status.TagParsingBegun }; + testHook.updateStatus(status); + } else if (message.endsWith("Initializing")) { + this.model.isInitializingWorkspace.Value = true; + this.model.isIndexingWorkspace.Value = false; + this.model.isParsingWorkspace.Value = false; + } else if (message.endsWith("Indexing")) { + this.model.isIndexingWorkspace.Value = true; + this.model.isInitializingWorkspace.Value = false; + this.model.isParsingWorkspace.Value = false; + } else if (message.endsWith("files")) { + this.model.isParsingFiles.Value = true; + } else if (message.endsWith("IntelliSense")) { + timeStamp = Date.now(); + this.model.isUpdatingIntelliSense.Value = true; + const status: IntelliSenseStatus = { status: Status.IntelliSenseCompiling }; + testHook.updateStatus(status); + } else if (message.endsWith("IntelliSense done")) { + getOutputChannelLogger().appendLineAtLevel(6, localize("update.intellisense.time", "Update IntelliSense time (sec): {0}", (Date.now() - timeStamp) / 1000)); + this.model.isUpdatingIntelliSense.Value = false; + const status: IntelliSenseStatus = { status: Status.IntelliSenseReady }; + testHook.updateStatus(status); + } else if (message.endsWith("Parsing done")) { // Tag Parser Ready + this.model.isParsingWorkspace.Value = false; + const status: IntelliSenseStatus = { status: Status.TagParsingDone }; + testHook.updateStatus(status); + util.setProgress(util.getProgressParseRootSuccess()); + } else if (message.endsWith("files done")) { + this.model.isParsingFiles.Value = false; + } else if (message.endsWith("Analysis")) { + this.model.isRunningCodeAnalysis.Value = true; + this.model.codeAnalysisTotal.Value = 1; + this.model.codeAnalysisProcessed.Value = 0; + } else if (message.endsWith("Analysis done")) { + this.model.isRunningCodeAnalysis.Value = false; + } else if (message.includes("Squiggles Finished - File name:")) { + const index: number = message.lastIndexOf(":"); + const name: string = message.substring(index + 2); + const status: IntelliSenseStatus = { status: Status.IntelliSenseReady, filename: name }; + testHook.updateStatus(status); + } else if (message.endsWith("No Squiggles")) { + util.setIntelliSenseProgress(util.getProgressIntelliSenseNoSquiggles()); + } + } else if (message.includes("/")) { + this.lastInvokedLspMessage = message; } } diff --git a/Extension/src/LanguageServer/extension.ts b/Extension/src/LanguageServer/extension.ts index 826c18814..c664f896f 100644 --- a/Extension/src/LanguageServer/extension.ts +++ b/Extension/src/LanguageServer/extension.ts @@ -55,6 +55,7 @@ let languageConfigurations: vscode.Disposable[] = []; let intervalTimer: NodeJS.Timeout; let codeActionProvider: vscode.Disposable; export const intelliSenseDisabledError: string = "Do not activate the extension when IntelliSense is disabled."; +export let isWritingCrashCallStack: boolean = false; type VcpkgDatabase = Record; // Stored as
-> [] let vcpkgDbPromise: Promise; @@ -1023,9 +1024,11 @@ export function watchForCrashes(crashDirectory: string): void { return; } const crashDate: Date = new Date(); + isWritingCrashCallStack = true; // Wait 5 seconds to allow time for the crash log to finish being written. setTimeout(() => { + isWritingCrashCallStack = false; fs.readFile(path.resolve(crashDirectory, filename), 'utf8', (err, data) => { void handleCrashFileRead(crashDirectory, filename, crashDate, err, data); }); From b50bf604fd945f0ca1ef4016de7bd6f37010baa7 Mon Sep 17 00:00:00 2001 From: Sean McManus Date: Thu, 4 Sep 2025 13:21:00 -0700 Subject: [PATCH 4/4] Remove `/` from the message since it seems to cause it to be dropped. (#13899) --- Extension/src/LanguageServer/client.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Extension/src/LanguageServer/client.ts b/Extension/src/LanguageServer/client.ts index b3a8d56c9..314d7c4b9 100644 --- a/Extension/src/LanguageServer/client.ts +++ b/Extension/src/LanguageServer/client.ts @@ -1695,9 +1695,10 @@ export class DefaultClient implements Client { // Wait 1 second to allow time for the file watcher to signal a crash call stack write has occurred. setTimeout(() => { + const sanitizedLspMessage = this.lastInvokedLspMessage.replace('/', '.'); telemetry.logLanguageServerEvent("languageClientCrash", { - lastInvokedLspMessage: this.lastInvokedLspMessage + lastInvokedLspMessage: sanitizedLspMessage }, { restarting: Number(restart),