Skip to content

Commit af326bd

Browse files
authored
Merge pull request #13900 from microsoft/seanmcm/1_26_4_release
1.26.4 release
2 parents e4f859d + b50bf60 commit af326bd

File tree

6 files changed

+166
-69
lines changed

6 files changed

+166
-69
lines changed

Extension/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# C/C++ for Visual Studio Code Changelog
22

3+
## Version 1.26.4: September 8, 2025
4+
* Update GitHub Copilot APIs. [PR #13877](https://github.com/microsoft/vscode-cpptools/pull/13877)
5+
* Thank you for the contribution. [@dbaeumer (Dirk Bäumer)](https://github.com/dbaeumer)
6+
37
## Version 1.26.3: June 24, 2025
48
### New Feature
59
* Improve the context provided for C++ Copilot suggestions.

Extension/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "cpptools",
33
"displayName": "C/C++",
44
"description": "C/C++ IntelliSense, debugging, and code browsing.",
5-
"version": "1.26.3-main",
5+
"version": "1.26.4-main",
66
"publisher": "ms-vscode",
77
"icon": "LanguageCCPP_color_128x.png",
88
"readme": "README.md",

Extension/src/LanguageServer/client.ts

Lines changed: 77 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ import { CopilotCompletionContextFeatures, CopilotCompletionContextProvider } fr
5959
import { CustomConfigurationProvider1, getCustomConfigProviders, isSameProviderExtensionId } from './customProviders';
6060
import { DataBinding } from './dataBinding';
6161
import { cachedEditorConfigSettings, getEditorConfigSettings } from './editorConfig';
62-
import { CppSourceStr, clients, configPrefix, initializeIntervalTimer, updateLanguageConfigurations, usesCrashHandler, watchForCrashes } from './extension';
62+
import { CppSourceStr, clients, configPrefix, initializeIntervalTimer, isWritingCrashCallStack, updateLanguageConfigurations, usesCrashHandler, watchForCrashes } from './extension';
6363
import { LocalizeStringParams, getLocaleId, getLocalizedString } from './localization';
6464
import { PersistentFolderState, PersistentState, PersistentWorkspaceState } from './persistentState';
6565
import { RequestCancelled, ServerCancelled, createProtocolFilter } from './protocolFilter';
@@ -942,6 +942,8 @@ export class DefaultClient implements Client {
942942
public getShowConfigureIntelliSenseButton(): boolean { return this.showConfigureIntelliSenseButton; }
943943
public setShowConfigureIntelliSenseButton(show: boolean): void { this.showConfigureIntelliSenseButton = show; }
944944

945+
private lastInvokedLspMessage: string = ""; // e.g. cpptools/hover
946+
945947
/**
946948
* don't use this.rootFolder directly since it can be undefined
947949
*/
@@ -1677,7 +1679,6 @@ export class DefaultClient implements Client {
16771679
closed: () => {
16781680
languageClientCrashTimes.push(Date.now());
16791681
languageClientCrashedNeedsRestart = true;
1680-
telemetry.logLanguageServerEvent("languageClientCrash");
16811682
let restart: boolean = true;
16821683
if (languageClientCrashTimes.length < 5) {
16831684
void clients.recreateClients();
@@ -1691,6 +1692,27 @@ export class DefaultClient implements Client {
16911692
void clients.recreateClients();
16921693
}
16931694
}
1695+
1696+
// Wait 1 second to allow time for the file watcher to signal a crash call stack write has occurred.
1697+
setTimeout(() => {
1698+
const sanitizedLspMessage = this.lastInvokedLspMessage.replace('/', '.');
1699+
telemetry.logLanguageServerEvent("languageClientCrash",
1700+
{
1701+
lastInvokedLspMessage: sanitizedLspMessage
1702+
},
1703+
{
1704+
restarting: Number(restart),
1705+
writingCrashCallStack: Number(isWritingCrashCallStack),
1706+
initializingWorkspace: Number(this.model.isInitializingWorkspace.Value),
1707+
indexingWorkspace: Number(this.model.isIndexingWorkspace.Value),
1708+
parsingWorkspace: Number(this.model.isParsingWorkspace.Value),
1709+
parsingFiles: Number(this.model.isParsingFiles.Value),
1710+
updatingIntelliSense: Number(this.model.isUpdatingIntelliSense.Value),
1711+
runningCodeAnalysis: Number(this.model.isRunningCodeAnalysis.Value)
1712+
}
1713+
);
1714+
}, 1000);
1715+
16941716
const message: string = restart ? localize('server.crashed.restart', 'The language server crashed. Restarting...')
16951717
: localize('server.crashed2', 'The language server crashed 5 times in the last 3 minutes. It will not be restarted.');
16961718

@@ -2747,55 +2769,59 @@ export class DefaultClient implements Client {
27472769
const message: string = notificationBody.status;
27482770
util.setProgress(util.getProgressExecutableSuccess());
27492771
const testHook: TestHook = getTestHook();
2750-
if (message.endsWith("Idle")) {
2751-
const status: IntelliSenseStatus = { status: Status.Idle };
2752-
testHook.updateStatus(status);
2753-
} else if (message.endsWith("Parsing")) {
2754-
this.model.isParsingWorkspace.Value = true;
2755-
this.model.isInitializingWorkspace.Value = false;
2756-
this.model.isIndexingWorkspace.Value = false;
2757-
const status: IntelliSenseStatus = { status: Status.TagParsingBegun };
2758-
testHook.updateStatus(status);
2759-
} else if (message.endsWith("Initializing")) {
2760-
this.model.isInitializingWorkspace.Value = true;
2761-
this.model.isIndexingWorkspace.Value = false;
2762-
this.model.isParsingWorkspace.Value = false;
2763-
} else if (message.endsWith("Indexing")) {
2764-
this.model.isIndexingWorkspace.Value = true;
2765-
this.model.isInitializingWorkspace.Value = false;
2766-
this.model.isParsingWorkspace.Value = false;
2767-
} else if (message.endsWith("files")) {
2768-
this.model.isParsingFiles.Value = true;
2769-
} else if (message.endsWith("IntelliSense")) {
2770-
timeStamp = Date.now();
2771-
this.model.isUpdatingIntelliSense.Value = true;
2772-
const status: IntelliSenseStatus = { status: Status.IntelliSenseCompiling };
2773-
testHook.updateStatus(status);
2774-
} else if (message.endsWith("IntelliSense done")) {
2775-
getOutputChannelLogger().appendLineAtLevel(6, localize("update.intellisense.time", "Update IntelliSense time (sec): {0}", (Date.now() - timeStamp) / 1000));
2776-
this.model.isUpdatingIntelliSense.Value = false;
2777-
const status: IntelliSenseStatus = { status: Status.IntelliSenseReady };
2778-
testHook.updateStatus(status);
2779-
} else if (message.endsWith("Parsing done")) { // Tag Parser Ready
2780-
this.model.isParsingWorkspace.Value = false;
2781-
const status: IntelliSenseStatus = { status: Status.TagParsingDone };
2782-
testHook.updateStatus(status);
2783-
util.setProgress(util.getProgressParseRootSuccess());
2784-
} else if (message.endsWith("files done")) {
2785-
this.model.isParsingFiles.Value = false;
2786-
} else if (message.endsWith("Analysis")) {
2787-
this.model.isRunningCodeAnalysis.Value = true;
2788-
this.model.codeAnalysisTotal.Value = 1;
2789-
this.model.codeAnalysisProcessed.Value = 0;
2790-
} else if (message.endsWith("Analysis done")) {
2791-
this.model.isRunningCodeAnalysis.Value = false;
2792-
} else if (message.includes("Squiggles Finished - File name:")) {
2793-
const index: number = message.lastIndexOf(":");
2794-
const name: string = message.substring(index + 2);
2795-
const status: IntelliSenseStatus = { status: Status.IntelliSenseReady, filename: name };
2796-
testHook.updateStatus(status);
2797-
} else if (message.endsWith("No Squiggles")) {
2798-
util.setIntelliSenseProgress(util.getProgressIntelliSenseNoSquiggles());
2772+
if (message.startsWith("C_Cpp: ")) {
2773+
if (message.endsWith("Idle")) {
2774+
const status: IntelliSenseStatus = { status: Status.Idle };
2775+
testHook.updateStatus(status);
2776+
} else if (message.endsWith("Parsing")) {
2777+
this.model.isParsingWorkspace.Value = true;
2778+
this.model.isInitializingWorkspace.Value = false;
2779+
this.model.isIndexingWorkspace.Value = false;
2780+
const status: IntelliSenseStatus = { status: Status.TagParsingBegun };
2781+
testHook.updateStatus(status);
2782+
} else if (message.endsWith("Initializing")) {
2783+
this.model.isInitializingWorkspace.Value = true;
2784+
this.model.isIndexingWorkspace.Value = false;
2785+
this.model.isParsingWorkspace.Value = false;
2786+
} else if (message.endsWith("Indexing")) {
2787+
this.model.isIndexingWorkspace.Value = true;
2788+
this.model.isInitializingWorkspace.Value = false;
2789+
this.model.isParsingWorkspace.Value = false;
2790+
} else if (message.endsWith("files")) {
2791+
this.model.isParsingFiles.Value = true;
2792+
} else if (message.endsWith("IntelliSense")) {
2793+
timeStamp = Date.now();
2794+
this.model.isUpdatingIntelliSense.Value = true;
2795+
const status: IntelliSenseStatus = { status: Status.IntelliSenseCompiling };
2796+
testHook.updateStatus(status);
2797+
} else if (message.endsWith("IntelliSense done")) {
2798+
getOutputChannelLogger().appendLineAtLevel(6, localize("update.intellisense.time", "Update IntelliSense time (sec): {0}", (Date.now() - timeStamp) / 1000));
2799+
this.model.isUpdatingIntelliSense.Value = false;
2800+
const status: IntelliSenseStatus = { status: Status.IntelliSenseReady };
2801+
testHook.updateStatus(status);
2802+
} else if (message.endsWith("Parsing done")) { // Tag Parser Ready
2803+
this.model.isParsingWorkspace.Value = false;
2804+
const status: IntelliSenseStatus = { status: Status.TagParsingDone };
2805+
testHook.updateStatus(status);
2806+
util.setProgress(util.getProgressParseRootSuccess());
2807+
} else if (message.endsWith("files done")) {
2808+
this.model.isParsingFiles.Value = false;
2809+
} else if (message.endsWith("Analysis")) {
2810+
this.model.isRunningCodeAnalysis.Value = true;
2811+
this.model.codeAnalysisTotal.Value = 1;
2812+
this.model.codeAnalysisProcessed.Value = 0;
2813+
} else if (message.endsWith("Analysis done")) {
2814+
this.model.isRunningCodeAnalysis.Value = false;
2815+
} else if (message.includes("Squiggles Finished - File name:")) {
2816+
const index: number = message.lastIndexOf(":");
2817+
const name: string = message.substring(index + 2);
2818+
const status: IntelliSenseStatus = { status: Status.IntelliSenseReady, filename: name };
2819+
testHook.updateStatus(status);
2820+
} else if (message.endsWith("No Squiggles")) {
2821+
util.setIntelliSenseProgress(util.getProgressIntelliSenseNoSquiggles());
2822+
}
2823+
} else if (message.includes("/")) {
2824+
this.lastInvokedLspMessage = message;
27992825
}
28002826
}
28012827

Extension/src/LanguageServer/copilotCompletionContextProvider.ts

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Copyright (c) Microsoft Corporation. All Rights Reserved.
33
* See 'LICENSE' in the project root for license information.
44
* ------------------------------------------------------------------------------------------ */
5-
import { ContextResolver, ResolveRequest, SupportedContextItem } from '@github/copilot-language-server';
5+
import { ContextResolver, ResolveRequest, SupportedContextItem, type ContextProvider } from '@github/copilot-language-server';
66
import { randomUUID } from 'crypto';
77
import * as vscode from 'vscode';
88
import { DocumentSelector } from 'vscode-languageserver-protocol';
@@ -11,7 +11,7 @@ import { getOutputChannelLogger, Logger } from '../logger';
1111
import * as telemetry from '../telemetry';
1212
import { CopilotCompletionContextResult } from './client';
1313
import { CopilotCompletionContextTelemetry } from './copilotCompletionContextTelemetry';
14-
import { getCopilotApi } from './copilotProviders';
14+
import { getCopilotChatApi, getCopilotClientApi, type CopilotContextProviderAPI } from './copilotProviders';
1515
import { clients } from './extension';
1616
import { CppSettings } from './settings';
1717

@@ -83,7 +83,7 @@ export class CopilotCompletionContextProvider implements ContextResolver<Support
8383
private static readonly defaultMaxSnippetLength = 3 * 1024;
8484
private static readonly defaultDoAggregateSnippets = true;
8585
private completionContextCancellation = new vscode.CancellationTokenSource();
86-
private contextProviderDisposable: vscode.Disposable | undefined;
86+
private contextProviderDisposables: vscode.Disposable[] | undefined;
8787
static readonly CppContextProviderEnabledFeatures = 'enabledFeatures';
8888
static readonly CppContextProviderTimeBudgetMs = 'timeBudgetMs';
8989
static readonly CppContextProviderMaxSnippetCount = 'maxSnippetCount';
@@ -312,7 +312,12 @@ export class CopilotCompletionContextProvider implements ContextResolver<Support
312312

313313
public dispose(): void {
314314
this.completionContextCancellation.cancel();
315-
this.contextProviderDisposable?.dispose();
315+
if (this.contextProviderDisposables) {
316+
for (const disposable of this.contextProviderDisposables) {
317+
disposable.dispose();
318+
}
319+
this.contextProviderDisposables = undefined;
320+
}
316321
}
317322

318323
public removeFile(fileUri: string): void {
@@ -444,18 +449,32 @@ ${copilotCompletionContext?.areSnippetsMissing ? "(missing code snippets)" : ""}
444449
const properties: Record<string, string> = {};
445450
const registerCopilotContextProvider = 'registerCopilotContextProvider';
446451
try {
447-
const copilotApi = await getCopilotApi();
448-
if (!copilotApi) { throw new CopilotContextProviderException("getCopilotApi() returned null, Copilot is missing or inactive."); }
449-
const hasGetContextProviderAPI = "getContextProviderAPI" in copilotApi;
450-
if (!hasGetContextProviderAPI) { throw new CopilotContextProviderException("getContextProviderAPI() is not available."); }
451-
const contextAPI = await copilotApi.getContextProviderAPI("v1");
452-
if (!contextAPI) { throw new CopilotContextProviderException("getContextProviderAPI(v1) returned null."); }
453-
this.contextProviderDisposable = contextAPI.registerContextProvider({
452+
const copilotApi = await getCopilotClientApi();
453+
const copilotChatApi = await getCopilotChatApi();
454+
if (!copilotApi && !copilotChatApi) { throw new CopilotContextProviderException("getCopilotApi() returned null, Copilot is missing or inactive."); }
455+
const contextProvider = {
454456
id: CopilotCompletionContextProvider.providerId,
455457
selector: CopilotCompletionContextProvider.defaultCppDocumentSelector,
456458
resolver: this
457-
});
458-
properties["cppCodeSnippetsProviderRegistered"] = "true";
459+
};
460+
type InstallSummary = { hasGetContextProviderAPI: boolean; hasAPI: boolean };
461+
const installSummary: { client?: InstallSummary; chat?: InstallSummary } = {};
462+
if (copilotApi) {
463+
installSummary.client = await this.installContextProvider(copilotApi, contextProvider);
464+
}
465+
if (copilotChatApi) {
466+
installSummary.chat = await this.installContextProvider(copilotChatApi, contextProvider);
467+
}
468+
if (installSummary.client?.hasAPI || installSummary.chat?.hasAPI) {
469+
properties["cppCodeSnippetsProviderRegistered"] = "true";
470+
} else {
471+
if (installSummary.client?.hasGetContextProviderAPI === false &&
472+
installSummary.chat?.hasGetContextProviderAPI === false) {
473+
throw new CopilotContextProviderException("getContextProviderAPI() is not available.");
474+
} else {
475+
throw new CopilotContextProviderException("getContextProviderAPI(v1) returned null.");
476+
}
477+
}
459478
} catch (e) {
460479
console.debug("Failed to register the Copilot Context Provider.");
461480
properties["error"] = "Failed to register the Copilot Context Provider";
@@ -466,4 +485,18 @@ ${copilotCompletionContext?.areSnippetsMissing ? "(missing code snippets)" : ""}
466485
telemetry.logCopilotEvent(registerCopilotContextProvider, { ...properties });
467486
}
468487
}
488+
489+
private async installContextProvider(copilotAPI: CopilotContextProviderAPI, contextProvider: ContextProvider<SupportedContextItem>): Promise<{ hasGetContextProviderAPI: boolean; hasAPI: boolean }> {
490+
const hasGetContextProviderAPI = typeof copilotAPI.getContextProviderAPI === 'function';
491+
if (hasGetContextProviderAPI) {
492+
const contextAPI = await copilotAPI.getContextProviderAPI("v1");
493+
if (contextAPI) {
494+
this.contextProviderDisposables = this.contextProviderDisposables ?? [];
495+
this.contextProviderDisposables.push(contextAPI.registerContextProvider(contextProvider));
496+
}
497+
return { hasGetContextProviderAPI, hasAPI: contextAPI !== undefined };
498+
} else {
499+
return { hasGetContextProviderAPI: false, hasAPI: false };
500+
}
501+
}
469502
}

Extension/src/LanguageServer/copilotProviders.ts

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ export interface CopilotTrait {
2424
promptTextOverride?: string;
2525
}
2626

27-
export interface CopilotApi {
27+
export interface CopilotContextProviderAPI {
28+
getContextProviderAPI(version: string): Promise<ContextProviderApiV1 | undefined>;
29+
}
30+
31+
export interface CopilotApi extends CopilotContextProviderAPI {
2832
registerRelatedFilesProvider(
2933
providerId: { extensionId: string; languageId: string },
3034
callback: (
@@ -33,11 +37,10 @@ export interface CopilotApi {
3337
cancellationToken: vscode.CancellationToken
3438
) => Promise<{ entries: vscode.Uri[]; traits?: CopilotTrait[] } | undefined>
3539
): Disposable;
36-
getContextProviderAPI(version: string): Promise<ContextProviderApiV1 | undefined>;
3740
}
3841

3942
export async function registerRelatedFilesProvider(): Promise<void> {
40-
const api = await getCopilotApi();
43+
const api = await getCopilotClientApi();
4144
if (util.extensionContext && api) {
4245
try {
4346
for (const languageId of ['c', 'cpp', 'cuda-cpp']) {
@@ -129,7 +132,7 @@ async function getIncludes(uri: vscode.Uri, maxDepth: number): Promise<GetInclud
129132
return includes;
130133
}
131134

132-
export async function getCopilotApi(): Promise<CopilotApi | undefined> {
135+
export async function getCopilotClientApi(): Promise<CopilotApi | undefined> {
133136
const copilotExtension = vscode.extensions.getExtension<CopilotApi>('github.copilot');
134137
if (!copilotExtension) {
135138
return undefined;
@@ -145,3 +148,31 @@ export async function getCopilotApi(): Promise<CopilotApi | undefined> {
145148
return copilotExtension.exports;
146149
}
147150
}
151+
152+
export async function getCopilotChatApi(): Promise<CopilotContextProviderAPI | undefined> {
153+
type CopilotChatApi = { getAPI?(version: number): CopilotContextProviderAPI | undefined };
154+
const copilotExtension = vscode.extensions.getExtension<CopilotChatApi>('github.copilot-chat');
155+
if (!copilotExtension) {
156+
return undefined;
157+
}
158+
159+
let exports: CopilotChatApi | undefined;
160+
if (!copilotExtension.isActive) {
161+
try {
162+
exports = await copilotExtension.activate();
163+
} catch {
164+
return undefined;
165+
}
166+
} else {
167+
exports = copilotExtension.exports;
168+
}
169+
if (!exports || typeof exports.getAPI !== 'function') {
170+
return undefined;
171+
}
172+
const result = exports.getAPI(1);
173+
return result;
174+
}
175+
176+
interface Disposable {
177+
dispose(): void;
178+
}

0 commit comments

Comments
 (0)