Skip to content

Commit 491456e

Browse files
Fix references cancellation and preview (#11056)
1 parent 5b51db1 commit 491456e

File tree

7 files changed

+296
-519
lines changed

7 files changed

+296
-519
lines changed

Extension/src/LanguageServer/Providers/callHierarchyProvider.ts

Lines changed: 83 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
import * as vscode from 'vscode';
66
import * as path from 'path';
77
import * as Telemetry from '../../telemetry';
8-
import { DefaultClient, CancelReferencesNotification, workspaceReferences } from '../client';
8+
import { DefaultClient, workspaceReferences } from '../client';
99
import { processDelayedDidOpen } from '../extension';
10-
import { CallHierarchyResultCallback } from '../references';
10+
import { CancellationSender } from '../references';
1111
import { Position, Range, RequestType, TextDocumentIdentifier } from 'vscode-languageclient';
1212
import { makeVscodeRange } from '../utils';
1313

@@ -54,7 +54,7 @@ interface CallHierarchyItemResult {
5454

5555
/**
5656
* If a request is cancelled, `succeeded` will be undefined to indicate no result was returned.
57-
* Therfore, object is not defined as optional on the language server.
57+
* Therefore, object is not defined as optional on the language server.
5858
*/
5959
succeeded: boolean;
6060
}
@@ -77,45 +77,68 @@ export interface CallHierarchyCallsItemResult {
7777
calls: CallHierarchyCallsItem[];
7878
}
7979

80-
export enum CallHierarchyRequestStatus {
80+
enum CallHierarchyRequestStatus {
8181
Unknown,
8282
Succeeded,
8383
Canceled,
84-
CaneledByUser,
84+
CanceledByUser,
8585
Failed
8686
}
8787

8888
const CallHierarchyItemRequest: RequestType<CallHierarchyParams, CallHierarchyItemResult, void> =
8989
new RequestType<CallHierarchyParams, CallHierarchyItemResult, void>('cpptools/prepareCallHierarchy');
9090

91+
const CallHierarchyCallsToRequest: RequestType<CallHierarchyParams, CallHierarchyCallsItemResult, void> =
92+
new RequestType<CallHierarchyParams, CallHierarchyCallsItemResult, void>('cpptools/callHierarchyCallsTo');
93+
9194
const CallHierarchyCallsFromRequest: RequestType<CallHierarchyParams, CallHierarchyCallsItemResult, void> =
9295
new RequestType<CallHierarchyParams, CallHierarchyCallsItemResult, void>('cpptools/callHierarchyCallsFrom');
9396

9497
export class CallHierarchyProvider implements vscode.CallHierarchyProvider {
9598
// Indicates whether a request is from an entry root node (e.g. top function in the call tree).
9699
private isEntryRootNodeTelemetry: boolean = false;
97100
private client: DefaultClient;
101+
98102
constructor(client: DefaultClient) {
99103
this.client = client;
100104
}
101105

102106
public async prepareCallHierarchy(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken):
103107
Promise<vscode.CallHierarchyItem | undefined> {
108+
await this.client.requestWhenReady(() => processDelayedDidOpen(document));
109+
workspaceReferences.cancelCurrentReferenceRequest(CancellationSender.NewRequest);
104110
workspaceReferences.clearViews();
111+
105112
const range: vscode.Range | undefined = document.getWordRangeAtPosition(position);
106113
if (range === undefined) {
107114
return undefined;
108115
}
109116

110-
await this.client.requestWhenReady(() => processDelayedDidOpen(document));
117+
// Listen to a cancellation for this request. When this request is cancelled,
118+
// use a local cancellation source to explicitly cancel a token.
119+
const cancelSource: vscode.CancellationTokenSource = new vscode.CancellationTokenSource();
120+
const cancellationTokenListener: vscode.Disposable = token.onCancellationRequested(() => {
121+
cancelSource.cancel();
122+
});
123+
const requestCanceledListener: vscode.Disposable = workspaceReferences.onCancellationRequested(sender => {
124+
cancelSource.cancel();
125+
});
111126

112127
const params: CallHierarchyParams = {
113128
textDocument: { uri: document.uri.toString() },
114129
position: Position.create(position.line, position.character)
115130
};
116-
const response: CallHierarchyItemResult = await this.client.languageClient.sendRequest(CallHierarchyItemRequest, params, token);
117-
if (token.isCancellationRequested || response.succeeded === undefined) {
118-
throw new vscode.CancellationError();
131+
const response: CallHierarchyItemResult = await this.client.languageClient.sendRequest(CallHierarchyItemRequest, params, cancelSource.token);
132+
133+
cancellationTokenListener.dispose();
134+
requestCanceledListener.dispose();
135+
136+
if (cancelSource.token.isCancellationRequested || response.succeeded === undefined) {
137+
// Return undefined instead of vscode.CancellationError to avoid the following error message from VS Code:
138+
// "MISSING provider."
139+
// TODO: per issue https://github.com/microsoft/vscode/issues/169698 vscode.CancellationError is expected,
140+
// so when VS Code fixes the error use vscode.CancellationError again.
141+
return undefined;
119142
} else if (response.item === undefined) {
120143
return undefined;
121144
}
@@ -125,76 +148,56 @@ export class CallHierarchyProvider implements vscode.CallHierarchyProvider {
125148
}
126149

127150
public async provideCallHierarchyIncomingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken):
128-
Promise<vscode.CallHierarchyIncomingCall[] | undefined | any> {
129-
return new Promise<vscode.CallHierarchyIncomingCall[] | undefined | any>((resolve, reject) => {
130-
const CallHierarchyCallsToEvent: string = "CallHierarchyCallsTo";
131-
if (item === undefined) {
132-
this.logTelemetry(CallHierarchyCallsToEvent, CallHierarchyRequestStatus.Failed);
133-
resolve(undefined);
134-
return;
135-
}
136-
137-
const callback: () => Promise<void> = async () => {
138-
await this.client.awaitUntilLanguageClientReady();
139-
const params: CallHierarchyParams = {
140-
textDocument: { uri: item.uri.toString() },
141-
position: Position.create(item.range.start.line, item.range.start.character)
142-
};
143-
DefaultClient.referencesParams = params;
144-
// The current request is represented by referencesParams. If a request detects
145-
// referencesParams does not match the object used when creating the request, abort it.
146-
if (params !== DefaultClient.referencesParams) {
147-
// Complete with nothing instead of rejecting, to avoid an error message from VS Code
148-
resolve(undefined);
149-
}
150-
DefaultClient.referencesRequestPending = true;
151-
152-
// Register a single-fire handler for the reply.
153-
const resultCallback: CallHierarchyResultCallback =
154-
(result: CallHierarchyCallsItemResult | null, canceledByUser: boolean, progressBarDuration?: number) => {
155-
DefaultClient.referencesRequestPending = false;
156-
if (result === null || result?.calls === undefined) {
157-
const requestStatus: CallHierarchyRequestStatus = canceledByUser ? CallHierarchyRequestStatus.CaneledByUser : CallHierarchyRequestStatus.Canceled;
158-
this.logTelemetry(CallHierarchyCallsToEvent, requestStatus, progressBarDuration);
159-
reject(new vscode.CancellationError());
160-
} else {
161-
this.logTelemetry(CallHierarchyCallsToEvent, CallHierarchyRequestStatus.Succeeded, progressBarDuration);
162-
if (result?.calls.length === 0) {
163-
resolve(undefined);
164-
} else {
165-
resolve(this.createIncomingCalls(result.calls));
166-
}
167-
}
168-
169-
this.client.clearPendingReferencesCancellations();
170-
};
171-
172-
workspaceReferences.setCallHierarchyResultsCallback(resultCallback);
173-
workspaceReferences.startCallHierarchyIncomingCalls(params);
174-
175-
token.onCancellationRequested(e => {
176-
if (params === DefaultClient.referencesParams) {
177-
this.client.cancelReferences();
178-
}
179-
});
180-
};
181-
182-
if (DefaultClient.referencesRequestPending || workspaceReferences.symbolSearchInProgress) {
183-
const cancelling: boolean = DefaultClient.referencesPendingCancellations.length > 0;
184-
DefaultClient.referencesPendingCancellations.push({
185-
reject: () => {
186-
// Complete with nothing instead of rejecting, to avoid an error message from VS Code
187-
resolve(undefined);
188-
}, callback
189-
});
190-
if (!cancelling) {
191-
workspaceReferences.referencesCanceled = true;
192-
this.client.languageClient.sendNotification(CancelReferencesNotification);
193-
}
194-
} else {
195-
callback();
196-
}
151+
Promise<vscode.CallHierarchyIncomingCall[] | undefined> {
152+
await this.client.awaitUntilLanguageClientReady();
153+
workspaceReferences.cancelCurrentReferenceRequest(CancellationSender.NewRequest);
154+
155+
const CallHierarchyCallsToEvent: string = "CallHierarchyCallsTo";
156+
if (item === undefined) {
157+
this.logTelemetry(CallHierarchyCallsToEvent, CallHierarchyRequestStatus.Failed);
158+
return undefined;
159+
}
160+
161+
// Listen to a cancellation for this request. When this request is cancelled,
162+
// use a local cancellation source to explicitly cancel a token.
163+
let requestCanceled: CancellationSender | undefined;
164+
const cancelSource: vscode.CancellationTokenSource = new vscode.CancellationTokenSource();
165+
const cancellationTokenListener: vscode.Disposable = token.onCancellationRequested(() => {
166+
requestCanceled = CancellationSender.ProviderToken;
167+
cancelSource.cancel();
197168
});
169+
const requestCanceledListener: vscode.Disposable = workspaceReferences.onCancellationRequested(sender => {
170+
requestCanceled = sender;
171+
cancelSource.cancel();
172+
});
173+
174+
// Send the request to the language server.
175+
let result: vscode.CallHierarchyIncomingCall[] | undefined;
176+
const params: CallHierarchyParams = {
177+
textDocument: { uri: item.uri.toString() },
178+
position: Position.create(item.range.start.line, item.range.start.character)
179+
};
180+
const response: CallHierarchyCallsItemResult = await this.client.languageClient.sendRequest(CallHierarchyCallsToRequest, params, cancelSource.token);
181+
182+
// Reset anything that can be cleared before processing the result.
183+
const progressBarDuration: number | undefined = workspaceReferences.getCallHierarchyProgressBarDuration();
184+
workspaceReferences.resetProgressBar();
185+
workspaceReferences.resetReferences();
186+
cancellationTokenListener.dispose();
187+
requestCanceledListener.dispose();
188+
189+
// Process the result.
190+
if (cancelSource.token.isCancellationRequested || response.calls === undefined || requestCanceled !== undefined) {
191+
const requestStatus: CallHierarchyRequestStatus = requestCanceled === CancellationSender.User ?
192+
CallHierarchyRequestStatus.CanceledByUser : CallHierarchyRequestStatus.Canceled;
193+
this.logTelemetry(CallHierarchyCallsToEvent, requestStatus, progressBarDuration);
194+
throw new vscode.CancellationError();
195+
} else if (response.calls.length !== 0) {
196+
result = this.createIncomingCalls(response.calls);
197+
}
198+
199+
this.logTelemetry(CallHierarchyCallsToEvent, CallHierarchyRequestStatus.Succeeded, progressBarDuration);
200+
return result;
198201
}
199202

200203
public async provideCallHierarchyOutgoingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken):
@@ -212,8 +215,8 @@ export class CallHierarchyProvider implements vscode.CallHierarchyProvider {
212215
textDocument: { uri: item.uri.toString() },
213216
position: Position.create(item.range.start.line, item.range.start.character)
214217
};
215-
216218
const response: CallHierarchyCallsItemResult = await this.client.languageClient.sendRequest(CallHierarchyCallsFromRequest, params, token);
219+
217220
if (token.isCancellationRequested || response.calls === undefined) {
218221
this.logTelemetry(CallHierarchyCallsFromEvent, CallHierarchyRequestStatus.Canceled);
219222
throw new vscode.CancellationError();
@@ -280,7 +283,7 @@ export class CallHierarchyProvider implements vscode.CallHierarchyProvider {
280283
case CallHierarchyRequestStatus.Unknown: status = "Unknown"; break;
281284
case CallHierarchyRequestStatus.Succeeded: status = "Succeeded"; break;
282285
case CallHierarchyRequestStatus.Canceled: status = "Canceled"; break;
283-
case CallHierarchyRequestStatus.CaneledByUser: status = "CaneledByUser"; break;
286+
case CallHierarchyRequestStatus.CanceledByUser: status = "CanceledByUser"; break;
284287
case CallHierarchyRequestStatus.Failed: status = "Failed"; break;
285288
}
286289

0 commit comments

Comments
 (0)