From 55fccf54f4df2c8e44d8429afb909f6c54de74e9 Mon Sep 17 00:00:00 2001 From: Carson Radtke Date: Mon, 16 Mar 2026 12:54:32 -0600 Subject: [PATCH 1/2] Add internal silent find-all-references command Introduce an internal C_Cpp.FindAllReferences command that issues the existing cpptools/findAllReferences request without joining the workspaceReferences single-flight cancellation path. Extract the shared request and confirmed-location mapping logic from FindAllReferencesProvider so the existing vscode.executeReferenceProvider flow and the new silent command use the same request translation and cancellation handling for server-side cancel responses. Keep the interactive provider behavior unchanged: user-invoked references still cancel prior work, reset reference progress state, and update the ReferencesManager UI. The new command resolves the owning client from the target URI and returns locations without progress UI, preview notifications, or references panel updates, enabling concurrent silent callers such as Copilot. --- .../Providers/findAllReferencesProvider.ts | 80 ++++++++++++------- Extension/src/LanguageServer/extension.ts | 17 ++++ 2 files changed, 68 insertions(+), 29 deletions(-) diff --git a/Extension/src/LanguageServer/Providers/findAllReferencesProvider.ts b/Extension/src/LanguageServer/Providers/findAllReferencesProvider.ts index ffdb1cf9e..664cf80c6 100644 --- a/Extension/src/LanguageServer/Providers/findAllReferencesProvider.ts +++ b/Extension/src/LanguageServer/Providers/findAllReferencesProvider.ts @@ -11,6 +11,50 @@ import { CancellationSender, ReferenceInfo, ReferenceType, ReferencesParams, Ref const FindAllReferencesRequest: RequestType = new RequestType('cpptools/findAllReferences'); +export interface FindAllReferencesResult { + referencesResult: ReferencesResult; + locations: vscode.Location[]; +} + +function convertConfirmedReferencesToLocations(referencesResult: ReferencesResult): vscode.Location[] { + const locationsResult: vscode.Location[] = []; + referencesResult.referenceInfos.forEach((referenceInfo: ReferenceInfo) => { + if (referenceInfo.type === ReferenceType.Confirmed) { + const uri: vscode.Uri = vscode.Uri.file(referenceInfo.file); + const range: vscode.Range = new vscode.Range(referenceInfo.position.line, referenceInfo.position.character, + referenceInfo.position.line, referenceInfo.position.character + referencesResult.text.length); + locationsResult.push(new vscode.Location(uri, range)); + } + }); + return locationsResult; +} + +export async function sendFindAllReferencesRequest(client: DefaultClient, uri: vscode.Uri, position: vscode.Position, token: vscode.CancellationToken): Promise { + const params: ReferencesParams = { + newName: "", + position: Position.create(position.line, position.character), + textDocument: { uri: uri.toString() } + }; + let response: ReferencesResult; + try { + response = await client.languageClient.sendRequest(FindAllReferencesRequest, params, token); + } catch (e: any) { + if (e instanceof ResponseError && (e.code === RequestCancelled || e.code === ServerCancelled)) { + return undefined; + } + throw e; + } + + if (token.isCancellationRequested || response.isCanceled) { + return undefined; + } + + return { + referencesResult: response, + locations: convertConfirmedReferencesToLocations(response) + }; +} + export class FindAllReferencesProvider implements vscode.ReferenceProvider { private client: DefaultClient; @@ -29,23 +73,10 @@ export class FindAllReferencesProvider implements vscode.ReferenceProvider { const requestCanceledListener: vscode.Disposable = workspaceReferences.onCancellationRequested(_sender => { cancelSource.cancel(); }); // Send the request to the language server. - const locationsResult: vscode.Location[] = []; - const params: ReferencesParams = { - newName: "", - position: Position.create(position.line, position.character), - textDocument: { uri: document.uri.toString() } - }; - let response: ReferencesResult | undefined; - let cancelled: boolean = false; + let result: FindAllReferencesResult | undefined; try { - response = await this.client.languageClient.sendRequest(FindAllReferencesRequest, params, cancelSource.token); - } catch (e: any) { - cancelled = e instanceof ResponseError && (e.code === RequestCancelled || e.code === ServerCancelled); - if (!cancelled) { - throw e; - } - } - finally { + result = await sendFindAllReferencesRequest(this.client, document.uri, position, cancelSource.token); + } finally { // Reset anything that can be cleared before processing the result. workspaceReferences.resetProgressBar(); cancellationTokenListener.dispose(); @@ -53,30 +84,21 @@ export class FindAllReferencesProvider implements vscode.ReferenceProvider { } // Process the result. - if (cancelSource.token.isCancellationRequested || cancelled || (response && response.isCanceled)) { + if (cancelSource.token.isCancellationRequested || !result) { // Return undefined instead of vscode.CancellationError to avoid the following error message from VS Code: // "Cannot destructure property 'range' of 'e.location' as it is undefined." // TODO: per issue https://github.com/microsoft/vscode/issues/169698 // vscode.CancellationError is expected, so when VS Code fixes the error use vscode.CancellationError again. workspaceReferences.resetReferences(); return undefined; - } else if (response && response.referenceInfos.length > 0) { - response.referenceInfos.forEach((referenceInfo: ReferenceInfo) => { - if (referenceInfo.type === ReferenceType.Confirmed) { - const uri: vscode.Uri = vscode.Uri.file(referenceInfo.file); - const range: vscode.Range = new vscode.Range(referenceInfo.position.line, referenceInfo.position.character, - referenceInfo.position.line, referenceInfo.position.character + response.text.length); - locationsResult.push(new vscode.Location(uri, range)); - } - }); - + } else if (result.referencesResult.referenceInfos.length > 0) { // Display other reference types in panel or channel view. // Note: ReferencesManager.resetReferences is called in ReferencesManager.showResultsInPanelView - workspaceReferences.showResultsInPanelView(response); + workspaceReferences.showResultsInPanelView(result.referencesResult); } else { workspaceReferences.resetReferences(); } - return locationsResult; + return result.locations; } } diff --git a/Extension/src/LanguageServer/extension.ts b/Extension/src/LanguageServer/extension.ts index f4d3417b2..7c4f4d8b9 100644 --- a/Extension/src/LanguageServer/extension.ts +++ b/Extension/src/LanguageServer/extension.ts @@ -23,6 +23,7 @@ import { getCrashCallStacksChannel } from '../logger'; import { PlatformInformation } from '../platform'; import * as telemetry from '../telemetry'; import { CopilotHoverProvider } from './Providers/CopilotHoverProvider'; +import { sendFindAllReferencesRequest } from './Providers/findAllReferencesProvider'; import { Client, DefaultClient, DoxygenCodeActionCommandArguments, openFileVersions } from './client'; import { ClientCollection } from './clientCollection'; import { CodeActionDiagnosticInfo, CodeAnalysisDiagnosticIdentifiersAndUri, codeAnalysisAllFixes, codeAnalysisCodeToFixes, codeAnalysisFileToCodeActions } from './codeAnalysis'; @@ -397,6 +398,7 @@ export async function registerCommands(enabled: boolean): Promise { commandDisposables.push(vscode.commands.registerCommand('C_Cpp.ShowActiveCodeAnalysisCommands', enabled ? onShowActiveCodeAnalysisCommands : onDisabledCommand)); commandDisposables.push(vscode.commands.registerCommand('C_Cpp.ShowIdleCodeAnalysisCommands', enabled ? onShowIdleCodeAnalysisCommands : onDisabledCommand)); commandDisposables.push(vscode.commands.registerCommand('C_Cpp.ShowReferencesProgress', enabled ? onShowReferencesProgress : onDisabledCommand)); + commandDisposables.push(vscode.commands.registerCommand('C_Cpp.FindAllReferences', enabled ? onFindAllReferences : onDisabledCommand)); commandDisposables.push(vscode.commands.registerCommand('C_Cpp.TakeSurvey', enabled ? onTakeSurvey : onDisabledCommand)); commandDisposables.push(vscode.commands.registerCommand('C_Cpp.LogDiagnostics', enabled ? onLogDiagnostics : onDisabledCommand)); commandDisposables.push(vscode.commands.registerCommand('C_Cpp.RescanWorkspace', enabled ? onRescanWorkspace : onDisabledCommand)); @@ -808,6 +810,21 @@ function onShowReferencesProgress(): void { void clients.ActiveClient.handleReferencesIcon().catch(logAndReturn.undefined); } +async function onFindAllReferences(uri: vscode.Uri, position: vscode.Position, token?: vscode.CancellationToken): Promise { + if (!uri || !position) { + throw new Error("C_Cpp.FindAllReferences requires both a uri and position."); + } + + const client: Client = clients.getClientFor(uri); + if (!(client instanceof DefaultClient)) { + return undefined; + } + + await client.ready; + const result = await sendFindAllReferencesRequest(client, uri, position, token ?? CancellationToken.None); + return result?.locations; +} + function onToggleRefGroupView(): void { // Set context to switch icons const client: Client = getActiveClient(); From dd5a028d5d647e8bd781dc3d5ea61315db59ed93 Mon Sep 17 00:00:00 2001 From: Carson Radtke Date: Mon, 6 Apr 2026 10:42:26 -0600 Subject: [PATCH 2/2] Add internal quiet definition and call hierarchy commands Align cpptools with the companion changes that now prefer internal C_Cpp.* navigation commands over the generic vscode.* provider commands when running extension-driven symbol queries. Add C_Cpp.GoToDefinition, C_Cpp.PrepareCallHierarchy, C_Cpp.CallHierarchyCallsTo, and C_Cpp.CallHierarchyCallsFrom as internal commands that resolve the owning DefaultClient from the target URI and send requests directly to cpptools without joining the workspaceReferences UI and single-flight cancellation path. Extract shared call hierarchy request and conversion logic from CallHierarchyProvider so the existing interactive provider flow and the new silent commands share the same request translation and server-cancellation handling. Add a dedicated go-to-definition helper that sends the standard definition request through the language client and normalizes both Location and DefinitionLink responses to Location[] so companion callers can consume a stable result shape. Keep interactive behavior unchanged: user-invoked providers continue to use the existing VS Code registrations, progress handling, and workspaceReferences-driven cancellation semantics, while extension callers such as the devtools companion can use the new internal command surface without canceling overlapping work. --- .../Providers/callHierarchyProvider.ts | 256 ++++++++++-------- .../Providers/goToDefinitionProvider.ts | 57 ++++ Extension/src/LanguageServer/extension.ts | 62 +++++ 3 files changed, 262 insertions(+), 113 deletions(-) create mode 100644 Extension/src/LanguageServer/Providers/goToDefinitionProvider.ts diff --git a/Extension/src/LanguageServer/Providers/callHierarchyProvider.ts b/Extension/src/LanguageServer/Providers/callHierarchyProvider.ts index 6c6d39f07..61b7b110b 100644 --- a/Extension/src/LanguageServer/Providers/callHierarchyProvider.ts +++ b/Extension/src/LanguageServer/Providers/callHierarchyProvider.ts @@ -88,6 +88,129 @@ const CallHierarchyCallsToRequest: RequestType = new RequestType('cpptools/callHierarchyCallsFrom'); +function makeVscodeCallHierarchyItem(client: DefaultClient, item: CallHierarchyItem): vscode.CallHierarchyItem { + const containerDetail: string = (item.detail !== "") ? `${item.detail} - ` : ""; + const itemUri: vscode.Uri = vscode.Uri.file(item.file); + + // Get file detail + const isInWorkspace: boolean = client.RootUri !== undefined && + itemUri.fsPath.startsWith(client.RootUri.fsPath); + const dirPath: string = isInWorkspace ? + path.relative(client.RootPath, path.dirname(item.file)) : path.dirname(item.file); + const fileDetail: string = dirPath.length === 0 ? + `${path.basename(item.file)}` : `${path.basename(item.file)} (${dirPath})`; + + return new vscode.CallHierarchyItem( + item.kind, + item.name, + containerDetail + fileDetail, + itemUri, + makeVscodeRange(item.range), + makeVscodeRange(item.selectionRange)); +} + +function createIncomingCalls(client: DefaultClient, calls: CallHierarchyCallsItem[]): vscode.CallHierarchyIncomingCall[] { + const result: vscode.CallHierarchyIncomingCall[] = []; + + for (const call of calls) { + const item: vscode.CallHierarchyItem = makeVscodeCallHierarchyItem(client, call.item); + const ranges: vscode.Range[] = []; + call.fromRanges.forEach(r => { + ranges.push(makeVscodeRange(r)); + }); + + const incomingCall: vscode.CallHierarchyIncomingCall = + new vscode.CallHierarchyIncomingCall(item, ranges); + result.push(incomingCall); + } + + return result; +} + +function createOutgoingCalls(client: DefaultClient, calls: CallHierarchyCallsItem[]): vscode.CallHierarchyOutgoingCall[] { + const result: vscode.CallHierarchyOutgoingCall[] = []; + + for (const call of calls) { + const item: vscode.CallHierarchyItem = makeVscodeCallHierarchyItem(client, call.item); + const ranges: vscode.Range[] = []; + call.fromRanges.forEach(r => { + ranges.push(makeVscodeRange(r)); + }); + + const outgoingCall: vscode.CallHierarchyOutgoingCall = + new vscode.CallHierarchyOutgoingCall(item, ranges); + result.push(outgoingCall); + } + + return result; +} + +export async function sendPrepareCallHierarchyRequest(client: DefaultClient, uri: vscode.Uri, position: vscode.Position, token: vscode.CancellationToken): Promise { + const params: CallHierarchyParams = { + textDocument: { uri: uri.toString() }, + position: Position.create(position.line, position.character) + }; + let response: CallHierarchyItemResult; + try { + response = await client.languageClient.sendRequest(CallHierarchyItemRequest, params, token); + } catch (e: any) { + if (e instanceof ResponseError && (e.code === RequestCancelled || e.code === ServerCancelled)) { + return undefined; + } + throw e; + } + + if (token.isCancellationRequested) { + return undefined; + } + + return response.item === undefined ? [] : [makeVscodeCallHierarchyItem(client, response.item)]; +} + +export async function sendCallHierarchyCallsToRequest(client: DefaultClient, item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise { + const params: CallHierarchyParams = { + textDocument: { uri: item.uri.toString() }, + position: Position.create(item.selectionRange.start.line, item.selectionRange.start.character) + }; + let response: CallHierarchyCallsItemResult; + try { + response = await client.languageClient.sendRequest(CallHierarchyCallsToRequest, params, token); + } catch (e: any) { + if (e instanceof ResponseError && (e.code === RequestCancelled || e.code === ServerCancelled)) { + return undefined; + } + throw e; + } + + if (token.isCancellationRequested) { + return undefined; + } + + return response.calls.length !== 0 ? createIncomingCalls(client, response.calls) : []; +} + +export async function sendCallHierarchyCallsFromRequest(client: DefaultClient, item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise { + const params: CallHierarchyParams = { + textDocument: { uri: item.uri.toString() }, + position: Position.create(item.selectionRange.start.line, item.selectionRange.start.character) + }; + let response: CallHierarchyCallsItemResult; + try { + response = await client.languageClient.sendRequest(CallHierarchyCallsFromRequest, params, token); + } catch (e: any) { + if (e instanceof ResponseError && (e.code === RequestCancelled || e.code === ServerCancelled)) { + return undefined; + } + throw e; + } + + if (token.isCancellationRequested) { + return undefined; + } + + return response.calls.length !== 0 ? createOutgoingCalls(client, response.calls) : []; +} + export class CallHierarchyProvider implements vscode.CallHierarchyProvider { // Indicates whether a request is from an entry root node (e.g. top function in the call tree). private isEntryRootNodeTelemetry: boolean = false; @@ -118,20 +241,10 @@ export class CallHierarchyProvider implements vscode.CallHierarchyProvider { cancelSource.cancel(); }); - const params: CallHierarchyParams = { - textDocument: { uri: document.uri.toString() }, - position: Position.create(position.line, position.character) - }; - let response: CallHierarchyItemResult; + let result: vscode.CallHierarchyItem[] | undefined; try { - response = await this.client.languageClient.sendRequest(CallHierarchyItemRequest, params, cancelSource.token); - } catch (e: any) { - if (e instanceof ResponseError && (e.code === RequestCancelled || e.code === ServerCancelled)) { - return undefined; - } - throw e; - } - finally { + result = await sendPrepareCallHierarchyRequest(this.client, document.uri, position, cancelSource.token); + } finally { cancellationTokenListener.dispose(); requestCanceledListener.dispose(); } @@ -139,12 +252,12 @@ export class CallHierarchyProvider implements vscode.CallHierarchyProvider { if (cancelSource.token.isCancellationRequested) { throw new vscode.CancellationError(); } - if (response.item === undefined) { + if (!result || result.length === 0) { return undefined; } this.isEntryRootNodeTelemetry = true; - return this.makeVscodeCallHierarchyItem(response.item); + return result[0]; } public async provideCallHierarchyIncomingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise { @@ -172,39 +285,28 @@ export class CallHierarchyProvider implements vscode.CallHierarchyProvider { // Send the request to the language server. let result: vscode.CallHierarchyIncomingCall[] | undefined; - const params: CallHierarchyParams = { - textDocument: { uri: item.uri.toString() }, - position: Position.create(item.selectionRange.start.line, item.selectionRange.start.character) - }; - let response: CallHierarchyCallsItemResult | undefined; - let cancelled: boolean = false; + let progressBarDuration: number | undefined; try { - response = await this.client.languageClient.sendRequest(CallHierarchyCallsToRequest, params, cancelSource.token); - } catch (e: any) { - cancelled = e instanceof ResponseError && (e.code === RequestCancelled || e.code === ServerCancelled); - if (!cancelled) { - throw e; - } + result = await sendCallHierarchyCallsToRequest(this.client, item, cancelSource.token); + } finally { + // Reset anything that can be cleared before processing the result. + progressBarDuration = workspaceReferences.getCallHierarchyProgressBarDuration(); + workspaceReferences.resetProgressBar(); + workspaceReferences.resetReferences(); + cancellationTokenListener.dispose(); + requestCanceledListener.dispose(); } - // Reset anything that can be cleared before processing the result. - const progressBarDuration: number | undefined = workspaceReferences.getCallHierarchyProgressBarDuration(); - workspaceReferences.resetProgressBar(); - workspaceReferences.resetReferences(); - cancellationTokenListener.dispose(); - requestCanceledListener.dispose(); // Process the result. - if (cancelSource.token.isCancellationRequested || cancelled || requestCanceled !== undefined) { + if (cancelSource.token.isCancellationRequested || result === undefined || requestCanceled !== undefined) { const requestStatus: CallHierarchyRequestStatus = requestCanceled === CancellationSender.User ? CallHierarchyRequestStatus.CanceledByUser : CallHierarchyRequestStatus.Canceled; this.logTelemetry(CallHierarchyCallsToEvent, requestStatus, progressBarDuration); throw new vscode.CancellationError(); - } else if (response && response.calls.length !== 0) { - result = this.createIncomingCalls(response.calls); } this.logTelemetry(CallHierarchyCallsToEvent, CallHierarchyRequestStatus.Succeeded, progressBarDuration); - return result; + return result.length !== 0 ? result : undefined; } public async provideCallHierarchyOutgoingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise { @@ -216,87 +318,15 @@ export class CallHierarchyProvider implements vscode.CallHierarchyProvider { await this.client.ready; - let result: vscode.CallHierarchyOutgoingCall[] | undefined; - const params: CallHierarchyParams = { - textDocument: { uri: item.uri.toString() }, - position: Position.create(item.selectionRange.start.line, item.selectionRange.start.character) - }; - let response: CallHierarchyCallsItemResult | undefined; - let cancelled: boolean = false; - try { - response = await this.client.languageClient.sendRequest(CallHierarchyCallsFromRequest, params, token); - } catch (e: any) { - cancelled = e instanceof ResponseError && (e.code === RequestCancelled || e.code === ServerCancelled); - if (!cancelled) { - throw e; - } - } - if (token.isCancellationRequested || cancelled) { + const result: vscode.CallHierarchyOutgoingCall[] | undefined = + await sendCallHierarchyCallsFromRequest(this.client, item, token); + if (token.isCancellationRequested || result === undefined) { this.logTelemetry(CallHierarchyCallsFromEvent, CallHierarchyRequestStatus.Canceled); throw new vscode.CancellationError(); - } else if (response && response.calls.length !== 0) { - result = this.createOutgoingCalls(response.calls); } this.logTelemetry(CallHierarchyCallsFromEvent, CallHierarchyRequestStatus.Succeeded); - return result; - } - - private makeVscodeCallHierarchyItem(item: CallHierarchyItem): vscode.CallHierarchyItem { - const containerDetail: string = (item.detail !== "") ? `${item.detail} - ` : ""; - const itemUri: vscode.Uri = vscode.Uri.file(item.file); - - // Get file detail - const isInWorkspace: boolean = this.client.RootUri !== undefined && - itemUri.fsPath.startsWith(this.client.RootUri?.fsPath); - const dirPath: string = isInWorkspace ? - path.relative(this.client.RootPath, path.dirname(item.file)) : path.dirname(item.file); - const fileDetail: string = dirPath.length === 0 ? - `${path.basename(item.file)}` : `${path.basename(item.file)} (${dirPath})`; - - return new vscode.CallHierarchyItem( - item.kind, - item.name, - containerDetail + fileDetail, - itemUri, - makeVscodeRange(item.range), - makeVscodeRange(item.selectionRange)); - } - - private createIncomingCalls(calls: CallHierarchyCallsItem[]): vscode.CallHierarchyIncomingCall[] { - const result: vscode.CallHierarchyIncomingCall[] = []; - - for (const call of calls) { - const item: vscode.CallHierarchyItem = this.makeVscodeCallHierarchyItem(call.item); - const ranges: vscode.Range[] = []; - call.fromRanges.forEach(r => { - ranges.push(makeVscodeRange(r)); - }); - - const incomingCall: vscode.CallHierarchyIncomingCall = - new vscode.CallHierarchyIncomingCall(item, ranges); - result.push(incomingCall); - } - - return result; - } - - private createOutgoingCalls(calls: CallHierarchyCallsItem[]): vscode.CallHierarchyOutgoingCall[] { - const result: vscode.CallHierarchyOutgoingCall[] = []; - - for (const call of calls) { - const item: vscode.CallHierarchyItem = this.makeVscodeCallHierarchyItem(call.item); - const ranges: vscode.Range[] = []; - call.fromRanges.forEach(r => { - ranges.push(makeVscodeRange(r)); - }); - - const outgoingCall: vscode.CallHierarchyOutgoingCall = - new vscode.CallHierarchyOutgoingCall(item, ranges); - result.push(outgoingCall); - } - - return result; + return result.length !== 0 ? result : undefined; } private logTelemetry(eventName: string, requestStatus: CallHierarchyRequestStatus, progressBarDuration?: number): void { diff --git a/Extension/src/LanguageServer/Providers/goToDefinitionProvider.ts b/Extension/src/LanguageServer/Providers/goToDefinitionProvider.ts new file mode 100644 index 000000000..80fb17c34 --- /dev/null +++ b/Extension/src/LanguageServer/Providers/goToDefinitionProvider.ts @@ -0,0 +1,57 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All Rights Reserved. + * See 'LICENSE' in the project root for license information. + * ------------------------------------------------------------------------------------------ */ +import * as vscode from 'vscode'; +import { Definition, DefinitionLink, DefinitionRequest, Position, ResponseError, TextDocumentPositionParams } from 'vscode-languageclient'; +import { DefaultClient } from '../client'; +import { RequestCancelled, ServerCancelled } from '../protocolFilter'; + +function convertDefinitionsToLocations(definitionsResult: vscode.Definition | vscode.DefinitionLink[] | undefined): vscode.Location[] { + if (!definitionsResult) { + return []; + } + + if (!Array.isArray(definitionsResult)) { + return [definitionsResult]; + } + + const result: vscode.Location[] = []; + for (const definition of definitionsResult) { + if (definition instanceof vscode.Location) { + result.push(definition); + } else { + result.push(new vscode.Location(definition.targetUri, definition.targetSelectionRange ?? definition.targetRange)); + } + } + + return result; +} + +export async function sendGoToDefinitionRequest(client: DefaultClient, uri: vscode.Uri, position: vscode.Position, token: vscode.CancellationToken): Promise { + const params: TextDocumentPositionParams = { + position: Position.create(position.line, position.character), + textDocument: { uri: uri.toString() } + }; + let response: Definition | DefinitionLink[] | null; + try { + response = await client.languageClient.sendRequest(DefinitionRequest.type, params, token); + } catch (e: any) { + if (e instanceof ResponseError && (e.code === RequestCancelled || e.code === ServerCancelled)) { + return undefined; + } + throw e; + } + + if (token.isCancellationRequested) { + return undefined; + } + + const result: vscode.Definition | vscode.DefinitionLink[] | undefined = + await client.languageClient.protocol2CodeConverter.asDefinitionResult(response, token); + if (token.isCancellationRequested) { + return undefined; + } + + return convertDefinitionsToLocations(result); +} diff --git a/Extension/src/LanguageServer/extension.ts b/Extension/src/LanguageServer/extension.ts index 7c4f4d8b9..fd8267b42 100644 --- a/Extension/src/LanguageServer/extension.ts +++ b/Extension/src/LanguageServer/extension.ts @@ -23,7 +23,9 @@ import { getCrashCallStacksChannel } from '../logger'; import { PlatformInformation } from '../platform'; import * as telemetry from '../telemetry'; import { CopilotHoverProvider } from './Providers/CopilotHoverProvider'; +import { sendCallHierarchyCallsFromRequest, sendCallHierarchyCallsToRequest, sendPrepareCallHierarchyRequest } from './Providers/callHierarchyProvider'; import { sendFindAllReferencesRequest } from './Providers/findAllReferencesProvider'; +import { sendGoToDefinitionRequest } from './Providers/goToDefinitionProvider'; import { Client, DefaultClient, DoxygenCodeActionCommandArguments, openFileVersions } from './client'; import { ClientCollection } from './clientCollection'; import { CodeActionDiagnosticInfo, CodeAnalysisDiagnosticIdentifiersAndUri, codeAnalysisAllFixes, codeAnalysisCodeToFixes, codeAnalysisFileToCodeActions } from './codeAnalysis'; @@ -399,6 +401,10 @@ export async function registerCommands(enabled: boolean): Promise { commandDisposables.push(vscode.commands.registerCommand('C_Cpp.ShowIdleCodeAnalysisCommands', enabled ? onShowIdleCodeAnalysisCommands : onDisabledCommand)); commandDisposables.push(vscode.commands.registerCommand('C_Cpp.ShowReferencesProgress', enabled ? onShowReferencesProgress : onDisabledCommand)); commandDisposables.push(vscode.commands.registerCommand('C_Cpp.FindAllReferences', enabled ? onFindAllReferences : onDisabledCommand)); + commandDisposables.push(vscode.commands.registerCommand('C_Cpp.GoToDefinition', enabled ? onGoToDefinition : onDisabledCommand)); + commandDisposables.push(vscode.commands.registerCommand('C_Cpp.PrepareCallHierarchy', enabled ? onPrepareCallHierarchy : onDisabledCommand)); + commandDisposables.push(vscode.commands.registerCommand('C_Cpp.CallHierarchyCallsTo', enabled ? onCallHierarchyCallsTo : onDisabledCommand)); + commandDisposables.push(vscode.commands.registerCommand('C_Cpp.CallHierarchyCallsFrom', enabled ? onCallHierarchyCallsFrom : onDisabledCommand)); commandDisposables.push(vscode.commands.registerCommand('C_Cpp.TakeSurvey', enabled ? onTakeSurvey : onDisabledCommand)); commandDisposables.push(vscode.commands.registerCommand('C_Cpp.LogDiagnostics', enabled ? onLogDiagnostics : onDisabledCommand)); commandDisposables.push(vscode.commands.registerCommand('C_Cpp.RescanWorkspace', enabled ? onRescanWorkspace : onDisabledCommand)); @@ -825,6 +831,62 @@ async function onFindAllReferences(uri: vscode.Uri, position: vscode.Position, t return result?.locations; } +async function onGoToDefinition(uri: vscode.Uri, position: vscode.Position, token?: vscode.CancellationToken): Promise { + if (!uri || !position) { + throw new Error("C_Cpp.GoToDefinition requires both a uri and position."); + } + + const client: Client = clients.getClientFor(uri); + if (!(client instanceof DefaultClient)) { + return undefined; + } + + await client.ready; + return sendGoToDefinitionRequest(client, uri, position, token ?? CancellationToken.None); +} + +async function onPrepareCallHierarchy(uri: vscode.Uri, position: vscode.Position, token?: vscode.CancellationToken): Promise { + if (!uri || !position) { + throw new Error("C_Cpp.PrepareCallHierarchy requires both a uri and position."); + } + + const client: Client = clients.getClientFor(uri); + if (!(client instanceof DefaultClient)) { + return undefined; + } + + await client.ready; + return sendPrepareCallHierarchyRequest(client, uri, position, token ?? CancellationToken.None); +} + +async function onCallHierarchyCallsTo(item: vscode.CallHierarchyItem, token?: vscode.CancellationToken): Promise { + if (!item) { + throw new Error("C_Cpp.CallHierarchyCallsTo requires an item."); + } + + const client: Client = clients.getClientFor(item.uri); + if (!(client instanceof DefaultClient)) { + return undefined; + } + + await client.ready; + return sendCallHierarchyCallsToRequest(client, item, token ?? CancellationToken.None); +} + +async function onCallHierarchyCallsFrom(item: vscode.CallHierarchyItem, token?: vscode.CancellationToken): Promise { + if (!item) { + throw new Error("C_Cpp.CallHierarchyCallsFrom requires an item."); + } + + const client: Client = clients.getClientFor(item.uri); + if (!(client instanceof DefaultClient)) { + return undefined; + } + + await client.ready; + return sendCallHierarchyCallsFromRequest(client, item, token ?? CancellationToken.None); +} + function onToggleRefGroupView(): void { // Set context to switch icons const client: Client = getActiveClient();