33 * See 'LICENSE' in the project root for license information.
44 * ------------------------------------------------------------------------------------------ */
55import * as vscode from 'vscode' ;
6+ import * as path from 'path' ;
67import * as Telemetry from '../../telemetry' ;
7- import { DefaultClient , CancelReferencesNotification , workspaceReferences } from '../client' ;
8+ import { DefaultClient , workspaceReferences } from '../client' ;
89import { processDelayedDidOpen } from '../extension' ;
9- import { CallHierarchyResultCallback } from '../references' ;
10+ import { CancellationSender } from '../references' ;
1011import { Position , Range , RequestType , TextDocumentIdentifier } from 'vscode-languageclient' ;
1112import { makeVscodeRange } from '../utils' ;
1213
@@ -53,7 +54,7 @@ interface CallHierarchyItemResult {
5354
5455 /**
5556 * If a request is cancelled, `succeeded` will be undefined to indicate no result was returned.
56- * Therfore , object is not defined as optional on the language server.
57+ * Therefore , object is not defined as optional on the language server.
5758 */
5859 succeeded : boolean ;
5960}
@@ -76,44 +77,68 @@ export interface CallHierarchyCallsItemResult {
7677 calls : CallHierarchyCallsItem [ ] ;
7778}
7879
79- export enum CallHierarchyRequestStatus {
80+ enum CallHierarchyRequestStatus {
8081 Unknown ,
8182 Succeeded ,
8283 Canceled ,
83- CaneledByUser ,
84+ CanceledByUser ,
8485 Failed
8586}
8687
8788const CallHierarchyItemRequest : RequestType < CallHierarchyParams , CallHierarchyItemResult , void > =
8889 new RequestType < CallHierarchyParams , CallHierarchyItemResult , void > ( 'cpptools/prepareCallHierarchy' ) ;
8990
91+ const CallHierarchyCallsToRequest : RequestType < CallHierarchyParams , CallHierarchyCallsItemResult , void > =
92+ new RequestType < CallHierarchyParams , CallHierarchyCallsItemResult , void > ( 'cpptools/callHierarchyCallsTo' ) ;
93+
9094const CallHierarchyCallsFromRequest : RequestType < CallHierarchyParams , CallHierarchyCallsItemResult , void > =
9195 new RequestType < CallHierarchyParams , CallHierarchyCallsItemResult , void > ( 'cpptools/callHierarchyCallsFrom' ) ;
9296
9397export class CallHierarchyProvider implements vscode . CallHierarchyProvider {
9498 // Indicates whether a request is from an entry root node (e.g. top function in the call tree).
9599 private isEntryRootNodeTelemetry : boolean = false ;
96100 private client : DefaultClient ;
101+
97102 constructor ( client : DefaultClient ) {
98103 this . client = client ;
99104 }
100105
101106 public async prepareCallHierarchy ( document : vscode . TextDocument , position : vscode . Position , token : vscode . CancellationToken ) :
102107 Promise < vscode . CallHierarchyItem | undefined > {
108+ await this . client . requestWhenReady ( ( ) => processDelayedDidOpen ( document ) ) ;
109+ workspaceReferences . cancelCurrentReferenceRequest ( CancellationSender . NewRequest ) ;
110+ workspaceReferences . clearViews ( ) ;
111+
103112 const range : vscode . Range | undefined = document . getWordRangeAtPosition ( position ) ;
104113 if ( range === undefined ) {
105114 return undefined ;
106115 }
107116
108- 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+ } ) ;
109126
110127 const params : CallHierarchyParams = {
111128 textDocument : { uri : document . uri . toString ( ) } ,
112129 position : Position . create ( position . line , position . character )
113130 } ;
114- const response : CallHierarchyItemResult = await this . client . languageClient . sendRequest ( CallHierarchyItemRequest , params , token ) ;
115- if ( token . isCancellationRequested || response . succeeded === undefined ) {
116- 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 ;
117142 } else if ( response . item === undefined ) {
118143 return undefined ;
119144 }
@@ -123,76 +148,56 @@ export class CallHierarchyProvider implements vscode.CallHierarchyProvider {
123148 }
124149
125150 public async provideCallHierarchyIncomingCalls ( item : vscode . CallHierarchyItem , token : vscode . CancellationToken ) :
126- Promise < vscode . CallHierarchyIncomingCall [ ] | undefined | any > {
127- return new Promise < vscode . CallHierarchyIncomingCall [ ] | undefined | any > ( ( resolve , reject ) => {
128- const CallHierarchyCallsToEvent : string = "CallHierarchyCallsTo" ;
129- if ( item === undefined ) {
130- this . logTelemetry ( CallHierarchyCallsToEvent , CallHierarchyRequestStatus . Failed ) ;
131- resolve ( undefined ) ;
132- return ;
133- }
134-
135- const callback : ( ) => Promise < void > = async ( ) => {
136- await this . client . awaitUntilLanguageClientReady ( ) ;
137- const params : CallHierarchyParams = {
138- textDocument : { uri : item . uri . toString ( ) } ,
139- position : Position . create ( item . range . start . line , item . range . start . character )
140- } ;
141- DefaultClient . referencesParams = params ;
142- // The current request is represented by referencesParams. If a request detects
143- // referencesParams does not match the object used when creating the request, abort it.
144- if ( params !== DefaultClient . referencesParams ) {
145- // Complete with nothing instead of rejecting, to avoid an error message from VS Code
146- resolve ( undefined ) ;
147- }
148- DefaultClient . referencesRequestPending = true ;
149-
150- // Register a single-fire handler for the reply.
151- const resultCallback : CallHierarchyResultCallback =
152- ( result : CallHierarchyCallsItemResult | null , canceledByUser : boolean , progressBarDuration ?: number ) => {
153- DefaultClient . referencesRequestPending = false ;
154- if ( result === null || result ?. calls === undefined ) {
155- const requestStatus : CallHierarchyRequestStatus = canceledByUser ? CallHierarchyRequestStatus . CaneledByUser : CallHierarchyRequestStatus . Canceled ;
156- this . logTelemetry ( CallHierarchyCallsToEvent , requestStatus , progressBarDuration ) ;
157- reject ( new vscode . CancellationError ( ) ) ;
158- } else {
159- this . logTelemetry ( CallHierarchyCallsToEvent , CallHierarchyRequestStatus . Succeeded , progressBarDuration ) ;
160- if ( result ?. calls . length === 0 ) {
161- resolve ( undefined ) ;
162- } else {
163- resolve ( this . createIncomingCalls ( result . calls ) ) ;
164- }
165- }
166-
167- this . client . clearPendingReferencesCancellations ( ) ;
168- } ;
169-
170- workspaceReferences . setCallHierarchyResultsCallback ( resultCallback ) ;
171- workspaceReferences . startCallHierarchyIncomingCalls ( params ) ;
172-
173- token . onCancellationRequested ( e => {
174- if ( params === DefaultClient . referencesParams ) {
175- this . client . cancelReferences ( ) ;
176- }
177- } ) ;
178- } ;
179-
180- if ( DefaultClient . referencesRequestPending || workspaceReferences . symbolSearchInProgress ) {
181- const cancelling : boolean = DefaultClient . referencesPendingCancellations . length > 0 ;
182- DefaultClient . referencesPendingCancellations . push ( {
183- reject : ( ) => {
184- // Complete with nothing instead of rejecting, to avoid an error message from VS Code
185- resolve ( undefined ) ;
186- } , callback
187- } ) ;
188- if ( ! cancelling ) {
189- workspaceReferences . referencesCanceled = true ;
190- this . client . languageClient . sendNotification ( CancelReferencesNotification ) ;
191- }
192- } else {
193- callback ( ) ;
194- }
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 ( ) ;
195168 } ) ;
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 ;
196201 }
197202
198203 public async provideCallHierarchyOutgoingCalls ( item : vscode . CallHierarchyItem , token : vscode . CancellationToken ) :
@@ -210,8 +215,8 @@ export class CallHierarchyProvider implements vscode.CallHierarchyProvider {
210215 textDocument : { uri : item . uri . toString ( ) } ,
211216 position : Position . create ( item . range . start . line , item . range . start . character )
212217 } ;
213-
214218 const response : CallHierarchyCallsItemResult = await this . client . languageClient . sendRequest ( CallHierarchyCallsFromRequest , params , token ) ;
219+
215220 if ( token . isCancellationRequested || response . calls === undefined ) {
216221 this . logTelemetry ( CallHierarchyCallsFromEvent , CallHierarchyRequestStatus . Canceled ) ;
217222 throw new vscode . CancellationError ( ) ;
@@ -224,8 +229,10 @@ export class CallHierarchyProvider implements vscode.CallHierarchyProvider {
224229 }
225230
226231 private makeVscodeCallHierarchyItem ( item : CallHierarchyItem ) : vscode . CallHierarchyItem {
232+ const containerDetail : string = ( item . detail !== "" ) ? `${ item . detail } - ` : "" ;
233+ const fileDetail : string = `${ path . basename ( item . uri ) } (${ path . dirname ( item . uri ) } )` ;
227234 return new vscode . CallHierarchyItem (
228- item . kind , item . name , item . detail ,
235+ item . kind , item . name , containerDetail + fileDetail ,
229236 vscode . Uri . file ( item . uri ) ,
230237 makeVscodeRange ( item . range ) ,
231238 makeVscodeRange ( item . selectionRange ) ) ;
@@ -276,7 +283,7 @@ export class CallHierarchyProvider implements vscode.CallHierarchyProvider {
276283 case CallHierarchyRequestStatus . Unknown : status = "Unknown" ; break ;
277284 case CallHierarchyRequestStatus . Succeeded : status = "Succeeded" ; break ;
278285 case CallHierarchyRequestStatus . Canceled : status = "Canceled" ; break ;
279- case CallHierarchyRequestStatus . CaneledByUser : status = "CaneledByUser " ; break ;
286+ case CallHierarchyRequestStatus . CanceledByUser : status = "CanceledByUser " ; break ;
280287 case CallHierarchyRequestStatus . Failed : status = "Failed" ; break ;
281288 }
282289
0 commit comments