From f4851caac642c8873a3481606a1985a7bfaf076f Mon Sep 17 00:00:00 2001 From: Sean McManus Date: Fri, 14 Aug 2026 04:16:34 +0000 Subject: [PATCH] Add select translation unit command --- Extension/package.json | 9 +++ Extension/package.nls.json | 1 + Extension/src/LanguageServer/client.ts | 32 +++++++++++ Extension/src/LanguageServer/extension.ts | 67 +++++++++++++++++++---- 4 files changed, 99 insertions(+), 10 deletions(-) diff --git a/Extension/package.json b/Extension/package.json index e4f44941d..2b515f160 100644 --- a/Extension/package.json +++ b/Extension/package.json @@ -3989,6 +3989,11 @@ "title": "%c_cpp.command.switchHeaderSource.title%", "category": "C/C++" }, + { + "command": "C_Cpp.SelectTranslationUnit", + "title": "%c_cpp.command.selectTranslationUnit.title%", + "category": "C/C++" + }, { "command": "C_Cpp.EnableErrorSquiggles", "title": "%c_cpp.command.enableErrorSquiggles.title%", @@ -6536,6 +6541,10 @@ "command": "C_Cpp.SwitchHeaderSource", "when": "editorLangId =~ /^(c|(cuda-)?cpp)$/ && !(config.C_Cpp.intelliSenseEngine =~ /^[dD]isabled$/)" }, + { + "command": "C_Cpp.SelectTranslationUnit", + "when": "editorLangId =~ /^(c|(cuda-)?cpp)$/ && config.C_Cpp.intelliSenseEngine =~ /^[dD]efault$/" + }, { "command": "C_Cpp.EnableErrorSquiggles", "when": "config.C_Cpp.intelliSenseEngine =~ /^[dD]efault$/" diff --git a/Extension/package.nls.json b/Extension/package.nls.json index 686234a1f..ea6352bef 100644 --- a/Extension/package.nls.json +++ b/Extension/package.nls.json @@ -15,6 +15,7 @@ "c_cpp.command.installCompiler.title": "Install a C++ Compiler", "c_cpp.command.rescanCompilers.title": "Rescan for Compilers", "c_cpp.command.switchHeaderSource.title": "Switch Header/Source", + "c_cpp.command.selectTranslationUnit.title": "Select a Translation Unit...", "c_cpp.command.enableErrorSquiggles.title": "Enable Error Squiggles", "c_cpp.command.disableErrorSquiggles.title": "Disable Error Squiggles", "c_cpp.command.toggleDimInactiveRegions.title": "Toggle Inactive Region Colorization", diff --git a/Extension/src/LanguageServer/client.ts b/Extension/src/LanguageServer/client.ts index 4cfa24c0f..a43c72413 100644 --- a/Extension/src/LanguageServer/client.ts +++ b/Extension/src/LanguageServer/client.ts @@ -217,6 +217,16 @@ interface SwitchHeaderSourceParams extends WorkspaceFolderParams { switchHeaderSourceFileName: string; } +interface GetTranslationUnitSourceCandidatesResult { + candidates: string[]; + currentTranslationUnit: string; +} + +interface SelectTranslationUnitParams { + uri: string; + translationUnit: string; +} + interface FileChangedParams extends WorkspaceFolderParams { uri: string; } @@ -625,6 +635,7 @@ const PreInitializationRequest: RequestType = new RequestTyp const InitializationRequest: RequestType = new RequestType('cpptools/initialize'); const QueryCompilerDefaultsRequest: RequestType = new RequestType('cpptools/queryCompilerDefaults'); const SwitchHeaderSourceRequest: RequestType = new RequestType('cpptools/didSwitchHeaderSource'); +const GetTranslationUnitSourceCandidatesRequest: RequestType = new RequestType('cpptools/getTranslationUnitSourceCandidates'); const GetDiagnosticsRequest: RequestType = new RequestType('cpptools/getDiagnostics'); export const GetDocumentSymbolRequest: RequestType = new RequestType('cpptools/getDocumentSymbols'); export const GetSymbolInfoRequest: RequestType = new RequestType('cpptools/getWorkspaceSymbols'); @@ -653,6 +664,7 @@ const PauseParsingNotification: NotificationType = new NotificationType = new NotificationType('cpptools/resumeParsing'); const DidChangeActiveEditorNotification: NotificationType = new NotificationType('cpptools/didChangeActiveEditor'); const RestartIntelliSenseForFileNotification: NotificationType = new NotificationType('cpptools/restartIntelliSenseForFile'); +const SelectTranslationUnitNotification: NotificationType = new NotificationType('cpptools/selectTranslationUnit'); const DidChangeTextEditorSelectionNotification: NotificationType = new NotificationType('cpptools/didChangeTextEditorSelection'); const ChangeCompileCommandsNotification: NotificationType = new NotificationType('cpptools/didChangeCompileCommands'); const ChangeSelectedSettingNotification: NotificationType = new NotificationType('cpptools/didChangeSelectedSetting'); @@ -832,6 +844,8 @@ export interface Client { takeOwnership(document: vscode.TextDocument): void; sendDidOpen(document: vscode.TextDocument): Promise; requestSwitchHeaderSource(rootUri: vscode.Uri, fileName: string, token: vscode.CancellationToken): Thenable; + getTranslationUnitSourceCandidates(uri: vscode.Uri): Promise; + selectTranslationUnit(uri: vscode.Uri, translationUnit: string): Promise; updateActiveDocumentTextOptions(): void; didChangeActiveEditor(editor?: vscode.TextEditor, selection?: Range): Promise; restartIntelliSenseForFile(document: vscode.TextDocument): Promise; @@ -3164,6 +3178,20 @@ export class DefaultClient implements Client { }); } + public async getTranslationUnitSourceCandidates(uri: vscode.Uri): Promise { + const params: TextDocumentIdentifier = { uri: uri.toString() }; + await this.ready; + return this.languageClient.sendRequest( + GetTranslationUnitSourceCandidatesRequest, + params); + } + + public async selectTranslationUnit(uri: vscode.Uri, translationUnit: string): Promise { + const params: SelectTranslationUnitParams = { uri: uri.toString(), translationUnit }; + await this.ready; + return this.languageClient.sendNotification(SelectTranslationUnitNotification, params).catch(logAndReturn.undefined); + } + public async requestCompiler(newCompilerPath?: string): Promise { const params: QueryDefaultCompilerParams = { newTrustedCompilerPath: newCompilerPath ?? "" @@ -4560,6 +4588,10 @@ class NullClient implements Client { takeOwnership(document: vscode.TextDocument): void { } sendDidOpen(document: vscode.TextDocument): Promise { return Promise.resolve(); } requestSwitchHeaderSource(rootUri: vscode.Uri, fileName: string, token: vscode.CancellationToken): Thenable { return Promise.resolve(""); } + getTranslationUnitSourceCandidates(uri: vscode.Uri): Promise { + return Promise.resolve({ candidates: [], currentTranslationUnit: "" }); + } + selectTranslationUnit(uri: vscode.Uri, translationUnit: string): Promise { return Promise.resolve(); } updateActiveDocumentTextOptions(): void { } didChangeActiveEditor(editor?: vscode.TextEditor): Promise { return Promise.resolve(); } restartIntelliSenseForFile(document: vscode.TextDocument): Promise { return Promise.resolve(); } diff --git a/Extension/src/LanguageServer/extension.ts b/Extension/src/LanguageServer/extension.ts index e1857ba9e..05bac77b3 100644 --- a/Extension/src/LanguageServer/extension.ts +++ b/Extension/src/LanguageServer/extension.ts @@ -383,6 +383,7 @@ export async function registerCommands(enabled: boolean): Promise { commandDisposables.forEach(d => d.dispose()); commandDisposables.length = 0; commandDisposables.push(vscode.commands.registerCommand('C_Cpp.SwitchHeaderSource', enabled ? onSwitchHeaderSource : onDisabledCommand)); + commandDisposables.push(vscode.commands.registerCommand('C_Cpp.SelectTranslationUnit', enabled ? onSelectTranslationUnit : onDisabledCommand)); commandDisposables.push(vscode.commands.registerCommand('C_Cpp.ResetDatabase', enabled ? onResetDatabase : onDisabledCommand)); commandDisposables.push(vscode.commands.registerCommand('C_Cpp.SelectIntelliSenseConfiguration', enabled ? selectIntelliSenseConfiguration : onDisabledCommand)); commandDisposables.push(vscode.commands.registerCommand('C_Cpp.InstallCompiler', enabled ? installCompiler : onDisabledCommand)); @@ -468,6 +469,61 @@ async function onRestartIntelliSenseForFile() { return clients.ActiveClient.restartIntelliSenseForFile(activeEditor.document); } +function getEditorPath(fileName: string): string { + let bestRealRoot: string | undefined; + let bestRootPath: string | undefined; + clients.forEach(client => { + if (!client.RootRealPath) { + return; + } + const relativePath: string = path.relative(client.RootRealPath, fileName); + const isUnderRoot: boolean = relativePath === "" || + (relativePath !== ".." && !relativePath.startsWith(`..${path.sep}`) && !path.isAbsolute(relativePath)); + if (isUnderRoot && (!bestRealRoot || client.RootRealPath.length > bestRealRoot.length)) { + bestRealRoot = client.RootRealPath; + bestRootPath = client.RootPath; + } + }); + return bestRealRoot && bestRootPath ? path.join(bestRootPath, path.relative(bestRealRoot, fileName)) : fileName; +} + +interface TranslationUnitQuickPickItem extends vscode.QuickPickItem { + translationUnit: string; +} + +async function onSelectTranslationUnit(): Promise { + const activeEditor: vscode.TextEditor | undefined = vscode.window.activeTextEditor; + if (!activeEditor || !util.isCpp(activeEditor.document)) { + return; + } + + const client: Client = clients.ActiveClient; + const result = await client.getTranslationUnitSourceCandidates(activeEditor.document.uri); + const currentTranslationUnit: string = getEditorPath(result.currentTranslationUnit); + const items: TranslationUnitQuickPickItem[] = result.candidates.map(candidate => { + const editorPath: string = getEditorPath(candidate); + const relativePath: string = vscode.workspace.asRelativePath(vscode.Uri.file(editorPath)); + const directory: string = path.dirname(relativePath); + const isCurrent: boolean = editorPath === currentTranslationUnit; + return { + label: `${isCurrent ? "$(check) " : ""}${path.basename(relativePath)}`, + description: isCurrent ? localize('current.translation.unit', 'Current translation unit') : + directory === "." ? undefined : directory, + detail: editorPath, + translationUnit: editorPath + }; + }); + const selection: TranslationUnitQuickPickItem | undefined = await vscode.window.showQuickPick(items, { + title: localize('select.translation.unit', 'Select a Translation Unit'), + placeHolder: localize('select.translation.unit.placeholder', 'Select a source file to use as the translation unit'), + matchOnDescription: true, + matchOnDetail: true + }); + if (selection !== undefined) { + await client.selectTranslationUnit(activeEditor.document.uri, selection.translationUnit); + } +} + async function onSwitchHeaderSource(): Promise { const activeEditor: vscode.TextEditor | undefined = vscode.window.activeTextEditor; if (!activeEditor || !util.isCpp(activeEditor.document)) { @@ -487,16 +543,7 @@ async function onSwitchHeaderSource(): Promise { if (!targetFileName) { return; } - // If the targetFileName has a path that is a symlink target of a workspace folder, - // then replace the RootRealPath with the RootPath (the symlink path). - let targetFileNameReplaced: boolean = false; - clients.forEach(client => { - if (!targetFileNameReplaced && client.RootRealPath && client.RootPath !== client.RootRealPath - && targetFileName.startsWith(client.RootRealPath)) { - targetFileName = client.RootPath + targetFileName.substring(client.RootRealPath.length); - targetFileNameReplaced = true; - } - }); + targetFileName = getEditorPath(targetFileName); const document: vscode.TextDocument = await vscode.workspace.openTextDocument(targetFileName); await vscode.window.showTextDocument(document).then(undefined, logAndReturn.undefined); } catch (e) {