diff --git a/Extension/src/LanguageServer/client.ts b/Extension/src/LanguageServer/client.ts index ba353a858..d937cff04 100644 --- a/Extension/src/LanguageServer/client.ts +++ b/Extension/src/LanguageServer/client.ts @@ -804,9 +804,9 @@ export interface Client { getShowConfigureIntelliSenseButton(): boolean; setShowConfigureIntelliSenseButton(show: boolean): void; addTrustedCompiler(path: string): Promise; - getIncludes(maxDepth: number, token: vscode.CancellationToken): Promise; + getIncludes(maxDepth: number): Promise; getChatContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise; - getProjectContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise; + getProjectContext(uri: vscode.Uri): Promise; } export function createClient(workspaceFolder?: vscode.WorkspaceFolder): Client { @@ -2228,25 +2228,31 @@ export class DefaultClient implements Client { await this.languageClient.sendNotification(DidOpenNotification, params); } - public async getIncludes(maxDepth: number, token: vscode.CancellationToken): Promise { + /** + * Copilot completion-related requests (e.g. getIncludes and getProjectContext) will have their cancellation tokens cancelled + * if the current request times out (showing the user completion results without context info), + * but the results can still be used for future requests (due to caching) so it's better to return results instead of cancelling. + * This is different behavior from the getChatContext, which does handle cancel requests, since the request blocks + * the UI results and always re-requests (no caching). + */ + + public async getIncludes(maxDepth: number): Promise { const params: GetIncludesParams = { maxDepth: maxDepth }; await this.ready; - return DefaultClient.withLspCancellationHandling( - () => this.languageClient.sendRequest(IncludesRequest, params, token), token); + return this.languageClient.sendRequest(IncludesRequest, params); } - public async getChatContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise { + public async getProjectContext(uri: vscode.Uri): Promise { const params: TextDocumentIdentifier = { uri: uri.toString() }; - await withCancellation(this.ready, token); - return DefaultClient.withLspCancellationHandling( - () => this.languageClient.sendRequest(CppContextRequest, params, token), token); + await this.ready; + return this.languageClient.sendRequest(ProjectContextRequest, params); } - public async getProjectContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise { + public async getChatContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise { const params: TextDocumentIdentifier = { uri: uri.toString() }; await withCancellation(this.ready, token); return DefaultClient.withLspCancellationHandling( - () => this.languageClient.sendRequest(ProjectContextRequest, params, token), token); + () => this.languageClient.sendRequest(CppContextRequest, params, token), token); } /** @@ -2340,7 +2346,6 @@ export class DefaultClient implements Client { throw e; } } - if (token.isCancellationRequested) { throw new vscode.CancellationError(); } @@ -4151,7 +4156,7 @@ class NullClient implements Client { getShowConfigureIntelliSenseButton(): boolean { return false; } setShowConfigureIntelliSenseButton(show: boolean): void { } addTrustedCompiler(path: string): Promise { return Promise.resolve(); } - getIncludes(maxDepth: number, token: vscode.CancellationToken): Promise { return Promise.resolve({} as GetIncludesResult); } + getIncludes(maxDepth: number): Promise { return Promise.resolve({} as GetIncludesResult); } getChatContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise { return Promise.resolve({} as ChatContextResult); } - getProjectContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise { return Promise.resolve({} as ProjectContextResult); } + getProjectContext(uri: vscode.Uri): Promise { return Promise.resolve({} as ProjectContextResult); } } diff --git a/Extension/src/LanguageServer/copilotProviders.ts b/Extension/src/LanguageServer/copilotProviders.ts index 2433e1695..a78fdfa5b 100644 --- a/Extension/src/LanguageServer/copilotProviders.ts +++ b/Extension/src/LanguageServer/copilotProviders.ts @@ -5,7 +5,7 @@ 'use strict'; import * as vscode from 'vscode'; -import { localize } from 'vscode-nls'; +import * as nls from 'vscode-nls'; import * as util from '../common'; import * as logger from '../logger'; import * as telemetry from '../telemetry'; @@ -13,6 +13,9 @@ import { GetIncludesResult } from './client'; import { getActiveClient } from './extension'; import { getCompilerArgumentFilterMap, getProjectContext } from './lmTool'; +nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })(); +const localize: nls.LocalizeFunc = nls.loadMessageBundle(); + export interface CopilotTrait { name: string; value: string; @@ -38,14 +41,14 @@ export async function registerRelatedFilesProvider(): Promise { for (const languageId of ['c', 'cpp', 'cuda-cpp']) { api.registerRelatedFilesProvider( { extensionId: util.extensionContext.extension.id, languageId }, - async (uri: vscode.Uri, context: { flags: Record }, token: vscode.CancellationToken) => { + async (uri: vscode.Uri, context: { flags: Record }) => { const start = performance.now(); const telemetryProperties: Record = {}; const telemetryMetrics: Record = {}; try { - const getIncludesHandler = async () => (await getIncludesWithCancellation(1, token))?.includedFiles.map(file => vscode.Uri.file(file)) ?? []; + const getIncludesHandler = async () => (await getIncludes(1))?.includedFiles.map(file => vscode.Uri.file(file)) ?? []; const getTraitsHandler = async () => { - const projectContext = await getProjectContext(uri, context, token); + const projectContext = await getProjectContext(uri, context); if (!projectContext) { return undefined; @@ -154,9 +157,9 @@ export async function registerRelatedFilesProvider(): Promise { } } -async function getIncludesWithCancellation(maxDepth: number, token: vscode.CancellationToken): Promise { +async function getIncludes(maxDepth: number): Promise { const activeClient = getActiveClient(); - const includes = await activeClient.getIncludes(maxDepth, token); + const includes = await activeClient.getIncludes(maxDepth); const wksFolder = activeClient.RootUri?.toString(); if (!wksFolder) { diff --git a/Extension/src/LanguageServer/lmTool.ts b/Extension/src/LanguageServer/lmTool.ts index 746ff3829..50240fdd7 100644 --- a/Extension/src/LanguageServer/lmTool.ts +++ b/Extension/src/LanguageServer/lmTool.ts @@ -5,7 +5,7 @@ 'use strict'; import * as vscode from 'vscode'; -import { localize } from 'vscode-nls'; +import * as nls from 'vscode-nls'; import * as util from '../common'; import * as logger from '../logger'; import * as telemetry from '../telemetry'; @@ -13,6 +13,9 @@ import { ChatContextResult, ProjectContextResult } from './client'; import { getClients } from './extension'; import { checkDuration } from './utils'; +nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })(); +const localize: nls.LocalizeFunc = nls.loadMessageBundle(); + const MSVC: string = 'MSVC'; const Clang: string = 'Clang'; const GCC: string = 'GCC'; @@ -127,11 +130,11 @@ function filterCompilerArguments(compiler: string, compilerArguments: string[], return result; } -export async function getProjectContext(uri: vscode.Uri, context: { flags: Record }, token: vscode.CancellationToken): Promise { +export async function getProjectContext(uri: vscode.Uri, context: { flags: Record }): Promise { const telemetryProperties: Record = {}; const telemetryMetrics: Record = {}; try { - const projectContext = await checkDuration(async () => await getClients()?.ActiveClient?.getProjectContext(uri, token) ?? undefined); + const projectContext = await checkDuration(async () => await getClients()?.ActiveClient?.getProjectContext(uri) ?? undefined); telemetryMetrics["duration"] = projectContext.duration; if (!projectContext.result) { return undefined; @@ -177,7 +180,7 @@ export async function getProjectContext(uri: vscode.Uri, context: { flags: Recor // Intentionally swallow any exception. } telemetryProperties["error"] = "true"; - return undefined; + throw exception; // Throw the exception for auto-retry. } finally { telemetry.logCopilotEvent('ProjectContext', telemetryProperties, telemetryMetrics); } diff --git a/Extension/test/scenarios/SingleRootProject/tests/lmTool.test.ts b/Extension/test/scenarios/SingleRootProject/tests/lmTool.test.ts index f11c048e1..afcf90366 100644 --- a/Extension/test/scenarios/SingleRootProject/tests/lmTool.test.ts +++ b/Extension/test/scenarios/SingleRootProject/tests/lmTool.test.ts @@ -200,7 +200,7 @@ describe('CppConfigurationLanguageModelTool Tests', () => { } }); - const result = await getProjectContext(mockTextDocumentStub.uri, context, new vscode.CancellationTokenSource().token); + const result = await getProjectContext(mockTextDocumentStub.uri, context); ok(result, 'result should not be undefined'); ok(result.language === 'C++'); @@ -364,7 +364,7 @@ describe('CppConfigurationLanguageModelTool Tests', () => { } }); - const result = await getProjectContext(mockTextDocumentStub.uri, { flags: {} }, new vscode.CancellationTokenSource().token); + const result = await getProjectContext(mockTextDocumentStub.uri, { flags: {} }); ok(telemetryStub.calledOnce, 'Telemetry should be called once'); ok(telemetryStub.calledWithMatch('ProjectContext', sinon.match({