From 42b8b6c7e5abadd6122381c6b4ccc03b64cc47e0 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 2c072eff3..4c0ce4739 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 a05512868fbf48af92e43ca6b5591bf0e0d935ee Mon Sep 17 00:00:00 2001 From: Carson Radtke Date: Wed, 25 Mar 2026 16:09:47 -0600 Subject: [PATCH 2/2] Add silent document symbol and call hierarchy commands Introduce internal commands for document symbols and call hierarchy so silent callers can query cpptools without triggering the interactive provider side effects used by the VS Code UI. Extract the shared request/translation logic from the document symbol and call hierarchy providers so the existing provider implementations and the new command handlers reuse the same server request paths, cancellation handling, and result mapping. Keep the current interactive behavior intact for user-invoked provider flows, including reference view state management and call hierarchy telemetry, while exposing side-effect-free entry points for concurrent programmatic callers. --- .../Providers/callHierarchyProvider.ts | 291 ++++++++++-------- .../Providers/documentSymbolProvider.ts | 119 +++---- Extension/src/LanguageServer/extension.ts | 62 ++++ 3 files changed, 297 insertions(+), 175 deletions(-) diff --git a/Extension/src/LanguageServer/Providers/callHierarchyProvider.ts b/Extension/src/LanguageServer/Providers/callHierarchyProvider.ts index 6c6d39f07..550ff0cb6 100644 --- a/Extension/src/LanguageServer/Providers/callHierarchyProvider.ts +++ b/Extension/src/LanguageServer/Providers/callHierarchyProvider.ts @@ -88,6 +88,157 @@ const CallHierarchyCallsToRequest: RequestType = new RequestType('cpptools/callHierarchyCallsFrom'); +interface CallHierarchyRequestResult { + result?: T; + cancelled: boolean; +} + +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, document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise> { + const range: vscode.Range | undefined = document.getWordRangeAtPosition(position); + if (range === undefined) { + return { cancelled: false }; + } + + await client.ready; + + const params: CallHierarchyParams = { + textDocument: { uri: document.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 { cancelled: true }; + } + throw e; + } + + if (token.isCancellationRequested) { + return { cancelled: true }; + } + + return { + cancelled: false, + result: response.item ? makeVscodeCallHierarchyItem(client, response.item) : undefined + }; +} + +export async function sendCallHierarchyIncomingCallsRequest(client: DefaultClient, item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise> { + await client.ready; + + 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 { cancelled: true }; + } + throw e; + } + + if (token.isCancellationRequested) { + return { cancelled: true }; + } + + return { + cancelled: false, + result: response.calls.length !== 0 ? createIncomingCalls(client, response.calls) : undefined + }; +} + +export async function sendCallHierarchyOutgoingCallsRequest(client: DefaultClient, item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise> { + await client.ready; + + 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 { cancelled: true }; + } + throw e; + } + + if (token.isCancellationRequested) { + return { cancelled: true }; + } + + return { + cancelled: false, + result: response.calls.length !== 0 ? createOutgoingCalls(client, response.calls) : undefined + }; +} + 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; @@ -98,16 +249,9 @@ export class CallHierarchyProvider implements vscode.CallHierarchyProvider { } public async prepareCallHierarchy(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { - await this.client.ready; - workspaceReferences.cancelCurrentReferenceRequest(CancellationSender.NewRequest); workspaceReferences.clearViews(); - const range: vscode.Range | undefined = document.getWordRangeAtPosition(position); - if (range === undefined) { - return undefined; - } - // Listen to a cancellation for this request. When this request is cancelled, // use a local cancellation source to explicitly cancel a token. const cancelSource: vscode.CancellationTokenSource = new vscode.CancellationTokenSource(); @@ -118,37 +262,26 @@ 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: CallHierarchyRequestResult; 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, position, cancelSource.token); + } finally { cancellationTokenListener.dispose(); requestCanceledListener.dispose(); } - if (cancelSource.token.isCancellationRequested) { + if (cancelSource.token.isCancellationRequested || result.cancelled) { throw new vscode.CancellationError(); } - if (response.item === undefined) { + if (result.result === undefined) { return undefined; } this.isEntryRootNodeTelemetry = true; - return this.makeVscodeCallHierarchyItem(response.item); + return result.result; } public async provideCallHierarchyIncomingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise { - await this.client.ready; workspaceReferences.cancelCurrentReferenceRequest(CancellationSender.NewRequest); const CallHierarchyCallsToEvent: string = "CallHierarchyCallsTo"; @@ -171,40 +304,30 @@ 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 result: CallHierarchyRequestResult; 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 sendCallHierarchyIncomingCallsRequest(this.client, item, cancelSource.token); + } finally { + 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.cancelled || 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); } + const incomingCalls: vscode.CallHierarchyIncomingCall[] | undefined = result.result; this.logTelemetry(CallHierarchyCallsToEvent, CallHierarchyRequestStatus.Succeeded, progressBarDuration); - return result; + return incomingCalls; } public async provideCallHierarchyOutgoingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise { @@ -214,89 +337,15 @@ export class CallHierarchyProvider implements vscode.CallHierarchyProvider { return undefined; } - 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: CallHierarchyRequestResult = + await sendCallHierarchyOutgoingCallsRequest(this.client, item, token); + if (token.isCancellationRequested || result.cancelled) { 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.result; } private logTelemetry(eventName: string, requestStatus: CallHierarchyRequestStatus, progressBarDuration?: number): void { diff --git a/Extension/src/LanguageServer/Providers/documentSymbolProvider.ts b/Extension/src/LanguageServer/Providers/documentSymbolProvider.ts index 4d4c3019d..07778524f 100644 --- a/Extension/src/LanguageServer/Providers/documentSymbolProvider.ts +++ b/Extension/src/LanguageServer/Providers/documentSymbolProvider.ts @@ -10,72 +10,83 @@ import { getLocalizedString, getLocalizedSymbolScope } from '../localization'; import { RequestCancelled, ServerCancelled } from '../protocolFilter'; import { makeVscodeRange } from '../utils'; -export class DocumentSymbolProvider implements vscode.DocumentSymbolProvider { - private getChildrenSymbols(symbols: LocalizeDocumentSymbol[]): vscode.DocumentSymbol[] { - const documentSymbols: vscode.DocumentSymbol[] = []; - if (symbols) { - symbols.forEach((symbol) => { - let detail: string = getLocalizedString(symbol.detail); - if (symbol.scope === SymbolScope.Private) { - if (detail.length === 0) { - detail = "private"; - } else { - detail = getLocalizedSymbolScope("private", detail); - } - } else if (symbol.scope === SymbolScope.Protected) { - if (detail.length === 0) { - detail = "protected"; - } else { - detail = getLocalizedSymbolScope("protected", detail); - } +function getChildrenSymbols(symbols: LocalizeDocumentSymbol[]): vscode.DocumentSymbol[] { + const documentSymbols: vscode.DocumentSymbol[] = []; + if (symbols) { + symbols.forEach((symbol) => { + let detail: string = getLocalizedString(symbol.detail); + if (symbol.scope === SymbolScope.Private) { + if (detail.length === 0) { + detail = "private"; + } else { + detail = getLocalizedSymbolScope("private", detail); } - - // Move the scope in the name to the detail. + } else if (symbol.scope === SymbolScope.Protected) { if (detail.length === 0) { - let offset_paren: number = symbol.name.indexOf("("); - if (offset_paren < 0) { - offset_paren = symbol.name.length; - } - const offset_scope: number = symbol.name.lastIndexOf("::", offset_paren - 2); - if (offset_scope > 0) { - detail = symbol.name.substring(0, offset_scope); - symbol.name = symbol.name.substring(offset_scope + 2); - } + detail = "protected"; + } else { + detail = getLocalizedSymbolScope("protected", detail); } + } - let r: vscode.Range = makeVscodeRange(symbol.range); - const sr: vscode.Range = makeVscodeRange(symbol.selectionRange); - if (!r.contains(sr)) { - r = sr; + // Move the scope in the name to the detail. + if (detail.length === 0) { + let offset_paren: number = symbol.name.indexOf("("); + if (offset_paren < 0) { + offset_paren = symbol.name.length; + } + const offset_scope: number = symbol.name.lastIndexOf("::", offset_paren - 2); + if (offset_scope > 0) { + detail = symbol.name.substring(0, offset_scope); + symbol.name = symbol.name.substring(offset_scope + 2); } - const vscodeSymbol: vscode.DocumentSymbol = new vscode.DocumentSymbol(symbol.name, detail, symbol.kind, r, sr); - vscodeSymbol.children = this.getChildrenSymbols(symbol.children); - documentSymbols.push(vscodeSymbol); - }); + } + + let r: vscode.Range = makeVscodeRange(symbol.range); + const sr: vscode.Range = makeVscodeRange(symbol.selectionRange); + if (!r.contains(sr)) { + r = sr; + } + const vscodeSymbol: vscode.DocumentSymbol = new vscode.DocumentSymbol(symbol.name, detail, symbol.kind, r, sr); + vscodeSymbol.children = getChildrenSymbols(symbol.children); + documentSymbols.push(vscodeSymbol); + }); + } + return documentSymbols; +} + +export async function sendDocumentSymbolRequest(client: DefaultClient, uri: vscode.Uri, token: vscode.CancellationToken): Promise { + await client.ready; + + const params: GetDocumentSymbolRequestParams = { + uri: uri.toString() + }; + + let response: GetDocumentSymbolResult; + try { + response = await client.languageClient.sendRequest(GetDocumentSymbolRequest, params, token); + } catch (e: any) { + if (e instanceof ResponseError && (e.code === RequestCancelled || e.code === ServerCancelled)) { + return undefined; } - return documentSymbols; + throw e; } + + if (token.isCancellationRequested) { + return undefined; + } + + return getChildrenSymbols(response.symbols); +} + +export class DocumentSymbolProvider implements vscode.DocumentSymbolProvider { public async provideDocumentSymbols(document: vscode.TextDocument, token: vscode.CancellationToken): Promise { const client: Client = clients.getClientFor(document.uri); if (client instanceof DefaultClient) { - const defaultClient: DefaultClient = client; - await client.ready; - const params: GetDocumentSymbolRequestParams = { - uri: document.uri.toString() - }; - let response: GetDocumentSymbolResult; - try { - response = await defaultClient.languageClient.sendRequest(GetDocumentSymbolRequest, params, token); - } catch (e: any) { - if (e instanceof ResponseError && (e.code === RequestCancelled || e.code === ServerCancelled)) { - throw new vscode.CancellationError(); - } - throw e; - } - if (token.isCancellationRequested) { + const resultSymbols: vscode.DocumentSymbol[] | undefined = await sendDocumentSymbolRequest(client, document.uri, token); + if (resultSymbols === undefined) { throw new vscode.CancellationError(); } - const resultSymbols: vscode.DocumentSymbol[] = this.getChildrenSymbols(response.symbols); return resultSymbols; } return []; diff --git a/Extension/src/LanguageServer/extension.ts b/Extension/src/LanguageServer/extension.ts index 4c0ce4739..47fe77183 100644 --- a/Extension/src/LanguageServer/extension.ts +++ b/Extension/src/LanguageServer/extension.ts @@ -23,6 +23,8 @@ import { getCrashCallStacksChannel } from '../logger'; import { PlatformInformation } from '../platform'; import * as telemetry from '../telemetry'; import { CopilotHoverProvider } from './Providers/CopilotHoverProvider'; +import { sendCallHierarchyIncomingCallsRequest, sendCallHierarchyOutgoingCallsRequest, sendPrepareCallHierarchyRequest } from './Providers/callHierarchyProvider'; +import { sendDocumentSymbolRequest } from './Providers/documentSymbolProvider'; import { sendFindAllReferencesRequest } from './Providers/findAllReferencesProvider'; import { Client, DefaultClient, DoxygenCodeActionCommandArguments, openFileVersions } from './client'; import { ClientCollection } from './clientCollection'; @@ -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.DocumentSymbols', enabled ? onDocumentSymbols : onDisabledCommand)); + commandDisposables.push(vscode.commands.registerCommand('C_Cpp.PrepareCallHierarchy', enabled ? onPrepareCallHierarchy : onDisabledCommand)); + commandDisposables.push(vscode.commands.registerCommand('C_Cpp.CallHierarchyIncomingCalls', enabled ? onCallHierarchyIncomingCalls : onDisabledCommand)); + commandDisposables.push(vscode.commands.registerCommand('C_Cpp.CallHierarchyOutgoingCalls', enabled ? onCallHierarchyOutgoingCalls : 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 onDocumentSymbols(uri: vscode.Uri, token?: vscode.CancellationToken): Promise { + if (!uri) { + throw new Error("C_Cpp.DocumentSymbols requires a uri."); + } + + const client: Client = clients.getClientFor(uri); + if (!(client instanceof DefaultClient)) { + return undefined; + } + + return sendDocumentSymbolRequest(client, uri, 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; + } + + const document: vscode.TextDocument = await vscode.workspace.openTextDocument(uri); + const result = await sendPrepareCallHierarchyRequest(client, document, position, token ?? CancellationToken.None); + return result.result; +} + +async function onCallHierarchyIncomingCalls(item: vscode.CallHierarchyItem, token?: vscode.CancellationToken): Promise { + if (!item) { + throw new Error("C_Cpp.CallHierarchyIncomingCalls requires a call hierarchy item."); + } + + const client: Client = clients.getClientFor(item.uri); + if (!(client instanceof DefaultClient)) { + return undefined; + } + + const result = await sendCallHierarchyIncomingCallsRequest(client, item, token ?? CancellationToken.None); + return result.result; +} + +async function onCallHierarchyOutgoingCalls(item: vscode.CallHierarchyItem, token?: vscode.CancellationToken): Promise { + if (!item) { + throw new Error("C_Cpp.CallHierarchyOutgoingCalls requires a call hierarchy item."); + } + + const client: Client = clients.getClientFor(item.uri); + if (!(client instanceof DefaultClient)) { + return undefined; + } + + const result = await sendCallHierarchyOutgoingCallsRequest(client, item, token ?? CancellationToken.None); + return result.result; +} + function onToggleRefGroupView(): void { // Set context to switch icons const client: Client = getActiveClient();