Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 32 additions & 11 deletions Extension/src/LanguageServer/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -804,9 +804,9 @@ export interface Client {
getShowConfigureIntelliSenseButton(): boolean;
setShowConfigureIntelliSenseButton(show: boolean): void;
addTrustedCompiler(path: string): Promise<void>;
getIncludes(maxDepth: number, token: vscode.CancellationToken): Promise<GetIncludesResult>;
getIncludes(maxDepth: number): Promise<GetIncludesResult>;
getChatContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise<ChatContextResult>;
getProjectContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise<ProjectContextResult>;
getProjectContext(uri: vscode.Uri): Promise<ProjectContextResult>;
}

export function createClient(workspaceFolder?: vscode.WorkspaceFolder): Client {
Expand Down Expand Up @@ -2228,11 +2228,11 @@ export class DefaultClient implements Client {
await this.languageClient.sendNotification(DidOpenNotification, params);
}

public async getIncludes(maxDepth: number, token: vscode.CancellationToken): Promise<GetIncludesResult> {
public async getIncludes(maxDepth: number): Promise<GetIncludesResult> {
const params: GetIncludesParams = { maxDepth: maxDepth };
await this.ready;
return DefaultClient.withLspCancellationHandling(
() => this.languageClient.sendRequest(IncludesRequest, params, token), token);
return DefaultClient.withoutLspCancellationHandling(
() => this.languageClient.sendRequest(IncludesRequest, params));
}

public async getChatContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise<ChatContextResult> {
Expand All @@ -2242,11 +2242,11 @@ export class DefaultClient implements Client {
() => this.languageClient.sendRequest(CppContextRequest, params, token), token);
}

public async getProjectContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise<ProjectContextResult> {
public async getProjectContext(uri: vscode.Uri): Promise<ProjectContextResult> {
const params: TextDocumentIdentifier = { uri: uri.toString() };
await withCancellation(this.ready, token);
return DefaultClient.withLspCancellationHandling(
() => this.languageClient.sendRequest(ProjectContextRequest, params, token), token);
await this.ready;
return DefaultClient.withoutLspCancellationHandling(
() => this.languageClient.sendRequest(ProjectContextRequest, params));
}

/**
Expand Down Expand Up @@ -2348,6 +2348,27 @@ export class DefaultClient implements Client {
return result;
}

// Copilot completions will cancel the current request if it 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.
private static async withoutLspCancellationHandling<T>(task: () => Promise<T>): Promise<T> {
let result: T;

try {
result = await task();
} catch (e: any) {
if (e instanceof ResponseError && e.code === ServerCancelled) {
// This can occur if the user switches files and makes edits fast enough.
// It doesn't seem like a very important scenario, so cancelling seems fine, even though
// not cancelling would allow the value to be cached if the user switches back to that file.
throw new vscode.CancellationError();
} else {
throw e;
}
}

return result;
}

private callTaskWithTimeout<T>(task: () => Thenable<T>, ms: number, cancelToken?: vscode.CancellationTokenSource): Promise<T> {
let timer: NodeJS.Timeout;

Expand Down Expand Up @@ -4151,7 +4172,7 @@ class NullClient implements Client {
getShowConfigureIntelliSenseButton(): boolean { return false; }
setShowConfigureIntelliSenseButton(show: boolean): void { }
addTrustedCompiler(path: string): Promise<void> { return Promise.resolve(); }
getIncludes(maxDepth: number, token: vscode.CancellationToken): Promise<GetIncludesResult> { return Promise.resolve({} as GetIncludesResult); }
getIncludes(maxDepth: number): Promise<GetIncludesResult> { return Promise.resolve({} as GetIncludesResult); }
getChatContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise<ChatContextResult> { return Promise.resolve({} as ChatContextResult); }
getProjectContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise<ProjectContextResult> { return Promise.resolve({} as ProjectContextResult); }
getProjectContext(uri: vscode.Uri): Promise<ProjectContextResult> { return Promise.resolve({} as ProjectContextResult); }
}
10 changes: 5 additions & 5 deletions Extension/src/LanguageServer/copilotProviders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ export async function registerRelatedFilesProvider(): Promise<void> {
for (const languageId of ['c', 'cpp', 'cuda-cpp']) {
api.registerRelatedFilesProvider(
{ extensionId: util.extensionContext.extension.id, languageId },
async (uri: vscode.Uri, context: { flags: Record<string, unknown> }, token: vscode.CancellationToken) => {
async (uri: vscode.Uri, context: { flags: Record<string, unknown> }) => {
const start = performance.now();
const telemetryProperties: Record<string, string> = {};
const telemetryMetrics: Record<string, number> = {};
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;
Expand Down Expand Up @@ -154,9 +154,9 @@ export async function registerRelatedFilesProvider(): Promise<void> {
}
}

async function getIncludesWithCancellation(maxDepth: number, token: vscode.CancellationToken): Promise<GetIncludesResult> {
async function getIncludes(maxDepth: number): Promise<GetIncludesResult> {
const activeClient = getActiveClient();
const includes = await activeClient.getIncludes(maxDepth, token);
const includes = await activeClient.getIncludes(maxDepth);
const wksFolder = activeClient.RootUri?.toString();

if (!wksFolder) {
Expand Down
4 changes: 2 additions & 2 deletions Extension/src/LanguageServer/lmTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,11 @@ function filterCompilerArguments(compiler: string, compilerArguments: string[],
return result;
}

export async function getProjectContext(uri: vscode.Uri, context: { flags: Record<string, unknown> }, token: vscode.CancellationToken): Promise<ProjectContext | undefined> {
export async function getProjectContext(uri: vscode.Uri, context: { flags: Record<string, unknown> }): Promise<ProjectContext | undefined> {
const telemetryProperties: Record<string, string> = {};
const telemetryMetrics: Record<string, number> = {};
try {
const projectContext = await checkDuration<ProjectContextResult | undefined>(async () => await getClients()?.ActiveClient?.getProjectContext(uri, token) ?? undefined);
const projectContext = await checkDuration<ProjectContextResult | undefined>(async () => await getClients()?.ActiveClient?.getProjectContext(uri) ?? undefined);
telemetryMetrics["duration"] = projectContext.duration;
if (!projectContext.result) {
return undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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++');
Expand Down Expand Up @@ -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({
Expand Down
Loading