Skip to content

Commit 109edd0

Browse files
authored
Add Select a Translation Unit command (#14688)
* Add select translation unit command * Add translation unit fallback warnings * Improve translation unit selection feedback * Apply TypeScript formatting * Revert unrelated TypeScript formatting * Clean up unrelated TypeScript formatting
1 parent 4121dd9 commit 109edd0

5 files changed

Lines changed: 152 additions & 10 deletions

File tree

‎Extension/package.json‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3990,6 +3990,11 @@
39903990
"title": "%c_cpp.command.switchHeaderSource.title%",
39913991
"category": "C/C++"
39923992
},
3993+
{
3994+
"command": "C_Cpp.SelectTranslationUnit",
3995+
"title": "%c_cpp.command.selectTranslationUnit.title%",
3996+
"category": "C/C++"
3997+
},
39933998
{
39943999
"command": "C_Cpp.EnableErrorSquiggles",
39954000
"title": "%c_cpp.command.enableErrorSquiggles.title%",
@@ -6542,6 +6547,10 @@
65426547
"command": "C_Cpp.SwitchHeaderSource",
65436548
"when": "editorLangId =~ /^(c|(cuda-)?cpp)$/ && !(config.C_Cpp.intelliSenseEngine =~ /^[dD]isabled$/)"
65446549
},
6550+
{
6551+
"command": "C_Cpp.SelectTranslationUnit",
6552+
"when": "editorLangId =~ /^(c|(cuda-)?cpp)$/ && config.C_Cpp.intelliSenseEngine =~ /^[dD]efault$/"
6553+
},
65456554
{
65466555
"command": "C_Cpp.EnableErrorSquiggles",
65476556
"when": "config.C_Cpp.intelliSenseEngine =~ /^[dD]efault$/"

‎Extension/package.nls.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"c_cpp.command.installCompiler.title": "Install a C++ Compiler",
1616
"c_cpp.command.rescanCompilers.title": "Rescan for Compilers",
1717
"c_cpp.command.switchHeaderSource.title": "Switch Header/Source",
18+
"c_cpp.command.selectTranslationUnit.title": "Select a Translation Unit...",
1819
"c_cpp.command.enableErrorSquiggles.title": "Enable Error Squiggles",
1920
"c_cpp.command.disableErrorSquiggles.title": "Disable Error Squiggles",
2021
"c_cpp.command.toggleDimInactiveRegions.title": "Toggle Inactive Region Colorization",

‎Extension/src/LanguageServer/client.ts‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,16 @@ interface SwitchHeaderSourceParams extends WorkspaceFolderParams {
216216
switchHeaderSourceFileName: string;
217217
}
218218

219+
interface GetTranslationUnitSourceCandidatesResult {
220+
candidates: string[];
221+
currentTranslationUnit: string;
222+
}
223+
224+
interface SelectTranslationUnitParams {
225+
uri: string;
226+
translationUnit: string;
227+
}
228+
219229
interface FileChangedParams extends WorkspaceFolderParams {
220230
uri: string;
221231
}
@@ -625,6 +635,7 @@ const PreInitializationRequest: RequestType<void, string, void> = new RequestTyp
625635
const InitializationRequest: RequestType<CppInitializationParams, CppInitializationResult, void> = new RequestType<CppInitializationParams, CppInitializationResult, void>('cpptools/initialize');
626636
const QueryCompilerDefaultsRequest: RequestType<QueryDefaultCompilerParams, configs.CompilerDefaults, void> = new RequestType<QueryDefaultCompilerParams, configs.CompilerDefaults, void>('cpptools/queryCompilerDefaults');
627637
const SwitchHeaderSourceRequest: RequestType<SwitchHeaderSourceParams, string, void> = new RequestType<SwitchHeaderSourceParams, string, void>('cpptools/didSwitchHeaderSource');
638+
const GetTranslationUnitSourceCandidatesRequest: RequestType<TextDocumentIdentifier, GetTranslationUnitSourceCandidatesResult, void> = new RequestType<TextDocumentIdentifier, GetTranslationUnitSourceCandidatesResult, void>('cpptools/getTranslationUnitSourceCandidates');
628639
const GetDiagnosticsRequest: RequestType<void, GetDiagnosticsResult, void> = new RequestType<void, GetDiagnosticsResult, void>('cpptools/getDiagnostics');
629640
export const GetDocumentSymbolRequest: RequestType<GetDocumentSymbolRequestParams, GetDocumentSymbolResult, void> = new RequestType<GetDocumentSymbolRequestParams, GetDocumentSymbolResult, void>('cpptools/getDocumentSymbols');
630641
export const GetSymbolInfoRequest: RequestType<WorkspaceSymbolParams, LocalizeSymbolInformation[], void> = new RequestType<WorkspaceSymbolParams, LocalizeSymbolInformation[], void>('cpptools/getWorkspaceSymbols');
@@ -653,6 +664,7 @@ const PauseParsingNotification: NotificationType<void> = new NotificationType<vo
653664
const ResumeParsingNotification: NotificationType<void> = new NotificationType<void>('cpptools/resumeParsing');
654665
const DidChangeActiveEditorNotification: NotificationType<DidChangeActiveEditorParams> = new NotificationType<DidChangeActiveEditorParams>('cpptools/didChangeActiveEditor');
655666
const RestartIntelliSenseForFileNotification: NotificationType<TextDocumentIdentifier> = new NotificationType<TextDocumentIdentifier>('cpptools/restartIntelliSenseForFile');
667+
const SelectTranslationUnitNotification: NotificationType<SelectTranslationUnitParams> = new NotificationType<SelectTranslationUnitParams>('cpptools/selectTranslationUnit');
656668
const DidChangeTextEditorSelectionNotification: NotificationType<Range> = new NotificationType<Range>('cpptools/didChangeTextEditorSelection');
657669
const ChangeCompileCommandsNotification: NotificationType<FileChangedParams> = new NotificationType<FileChangedParams>('cpptools/didChangeCompileCommands');
658670
const ChangeSelectedSettingNotification: NotificationType<FolderSelectedSettingParams> = new NotificationType<FolderSelectedSettingParams>('cpptools/didChangeSelectedSetting');
@@ -832,6 +844,8 @@ export interface Client {
832844
takeOwnership(document: vscode.TextDocument): void;
833845
sendDidOpen(document: vscode.TextDocument): Promise<void>;
834846
requestSwitchHeaderSource(rootUri: vscode.Uri, fileName: string, token: vscode.CancellationToken): Thenable<string>;
847+
getTranslationUnitSourceCandidates(uri: vscode.Uri, token: vscode.CancellationToken): Promise<GetTranslationUnitSourceCandidatesResult>;
848+
selectTranslationUnit(uri: vscode.Uri, translationUnit: string): Promise<void>;
835849
updateActiveDocumentTextOptions(): void;
836850
didChangeActiveEditor(editor?: vscode.TextEditor, selection?: Range): Promise<void>;
837851
restartIntelliSenseForFile(document: vscode.TextDocument): Promise<void>;
@@ -3098,6 +3112,19 @@ export class DefaultClient implements Client {
30983112
}
30993113
}
31003114

3115+
public async getTranslationUnitSourceCandidates(uri: vscode.Uri, token: vscode.CancellationToken): Promise<GetTranslationUnitSourceCandidatesResult> {
3116+
const params: TextDocumentIdentifier = { uri: uri.toString() };
3117+
await withCancellation(this.ready, token);
3118+
return DefaultClient.withLspCancellationHandling(
3119+
() => this.languageClient.sendRequest(GetTranslationUnitSourceCandidatesRequest, params, token), token);
3120+
}
3121+
3122+
public async selectTranslationUnit(uri: vscode.Uri, translationUnit: string): Promise<void> {
3123+
const params: SelectTranslationUnitParams = { uri: uri.toString(), translationUnit };
3124+
await this.ready;
3125+
return this.languageClient.sendNotification(SelectTranslationUnitNotification, params).catch(logAndReturn.undefined);
3126+
}
3127+
31013128
public async requestCompiler(newCompilerPath?: string): Promise<configs.CompilerDefaults> {
31023129
const params: QueryDefaultCompilerParams = {
31033130
newTrustedCompilerPath: newCompilerPath ?? ""
@@ -4470,6 +4497,10 @@ class NullClient implements Client {
44704497
takeOwnership(document: vscode.TextDocument): void { }
44714498
sendDidOpen(document: vscode.TextDocument): Promise<void> { return Promise.resolve(); }
44724499
requestSwitchHeaderSource(rootUri: vscode.Uri, fileName: string, token: vscode.CancellationToken): Thenable<string> { return Promise.resolve(""); }
4500+
getTranslationUnitSourceCandidates(uri: vscode.Uri, token: vscode.CancellationToken): Promise<GetTranslationUnitSourceCandidatesResult> {
4501+
return Promise.resolve({ candidates: [], currentTranslationUnit: "" });
4502+
}
4503+
selectTranslationUnit(uri: vscode.Uri, translationUnit: string): Promise<void> { return Promise.resolve(); }
44734504
updateActiveDocumentTextOptions(): void { }
44744505
didChangeActiveEditor(editor?: vscode.TextEditor): Promise<void> { return Promise.resolve(); }
44754506
restartIntelliSenseForFile(document: vscode.TextDocument): Promise<void> { return Promise.resolve(); }

‎Extension/src/LanguageServer/extension.ts‎

Lines changed: 103 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@ export async function registerCommands(enabled: boolean): Promise<void> {
383383
commandDisposables.forEach(d => d.dispose());
384384
commandDisposables.length = 0;
385385
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.SwitchHeaderSource', enabled ? onSwitchHeaderSource : onDisabledCommand));
386+
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.SelectTranslationUnit', enabled ? onSelectTranslationUnit : onDisabledCommand));
386387
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.ResetDatabase', enabled ? onResetDatabase : onDisabledCommand));
387388
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.SelectIntelliSenseConfiguration', enabled ? selectIntelliSenseConfiguration : onDisabledCommand));
388389
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.InstallCompiler', enabled ? installCompiler : onDisabledCommand));
@@ -468,6 +469,107 @@ async function onRestartIntelliSenseForFile() {
468469
return clients.ActiveClient.restartIntelliSenseForFile(activeEditor.document);
469470
}
470471

472+
function getEditorPath(fileName: string): string {
473+
let bestRealRoot: string | undefined;
474+
let bestRootPath: string | undefined;
475+
clients.forEach(client => {
476+
if (!client.RootRealPath) {
477+
return;
478+
}
479+
const relativePath: string = path.relative(client.RootRealPath, fileName);
480+
const isUnderRoot: boolean = relativePath === "" ||
481+
(relativePath !== ".." && !relativePath.startsWith(`..${path.sep}`) && !path.isAbsolute(relativePath));
482+
if (isUnderRoot && (!bestRealRoot || client.RootRealPath.length > bestRealRoot.length)) {
483+
bestRealRoot = client.RootRealPath;
484+
bestRootPath = client.RootPath;
485+
}
486+
});
487+
return bestRealRoot && bestRootPath ? path.join(bestRootPath, path.relative(bestRealRoot, fileName)) : fileName;
488+
}
489+
490+
interface TranslationUnitQuickPickItem extends vscode.QuickPickItem {
491+
translationUnit: string;
492+
}
493+
494+
async function onSelectTranslationUnit(): Promise<void> {
495+
const activeEditor: vscode.TextEditor | undefined = vscode.window.activeTextEditor;
496+
if (!activeEditor || !util.isCpp(activeEditor.document)) {
497+
return;
498+
}
499+
500+
const client: Client = clients.ActiveClient;
501+
const result = await (async () => {
502+
const tokenSource: vscode.CancellationTokenSource = new vscode.CancellationTokenSource();
503+
try {
504+
const candidatesPromise = client.getTranslationUnitSourceCandidates(activeEditor.document.uri, tokenSource.token);
505+
const showProgress: boolean = await new Promise<boolean>((resolve, reject) => {
506+
const timer: NodeJS.Timeout = global.setTimeout(() => resolve(true), 2000);
507+
void candidatesPromise.then(() => {
508+
clearTimeout(timer);
509+
resolve(false);
510+
}, (e) => {
511+
clearTimeout(timer);
512+
reject(e);
513+
});
514+
});
515+
516+
if (!showProgress) {
517+
return await candidatesPromise;
518+
}
519+
520+
return await vscode.window.withProgress({
521+
location: vscode.ProgressLocation.Notification,
522+
title: localize('find.translation.units', 'Finding Translation Units...'),
523+
cancellable: true
524+
}, async (_progress, token) => {
525+
const cancellationListener: vscode.Disposable = token.onCancellationRequested(() => tokenSource.cancel());
526+
try {
527+
return await candidatesPromise;
528+
} finally {
529+
cancellationListener.dispose();
530+
}
531+
});
532+
} catch (e) {
533+
if (e instanceof vscode.CancellationError) {
534+
return undefined;
535+
}
536+
throw e;
537+
} finally {
538+
tokenSource.dispose();
539+
}
540+
})();
541+
if (result === undefined) {
542+
return;
543+
}
544+
if (result.candidates.length === 0) {
545+
void vscode.window.showInformationMessage(localize('no.translation.units.found', 'No translation units were found for the active file.'));
546+
return;
547+
}
548+
const currentTranslationUnit: string = getEditorPath(result.currentTranslationUnit);
549+
const items: TranslationUnitQuickPickItem[] = result.candidates.map(candidate => {
550+
const editorPath: string = getEditorPath(candidate);
551+
const relativePath: string = vscode.workspace.asRelativePath(vscode.Uri.file(editorPath));
552+
const directory: string = path.dirname(relativePath);
553+
const isCurrent: boolean = editorPath === currentTranslationUnit;
554+
return {
555+
label: `${isCurrent ? "$(check) " : ""}${path.basename(relativePath)}`,
556+
description: isCurrent ? localize('current.translation.unit', 'Current translation unit') :
557+
directory === "." ? undefined : directory,
558+
detail: editorPath,
559+
translationUnit: editorPath
560+
};
561+
});
562+
const selection: TranslationUnitQuickPickItem | undefined = await vscode.window.showQuickPick(items, {
563+
title: localize('select.translation.unit', 'Select a Translation Unit'),
564+
placeHolder: localize('select.translation.unit.placeholder', 'Select a source file to use as the translation unit'),
565+
matchOnDescription: true,
566+
matchOnDetail: true
567+
});
568+
if (selection !== undefined) {
569+
await client.selectTranslationUnit(activeEditor.document.uri, selection.translationUnit);
570+
}
571+
}
572+
471573
async function onSwitchHeaderSource(): Promise<void> {
472574
const activeEditor: vscode.TextEditor | undefined = vscode.window.activeTextEditor;
473575
if (!activeEditor || !util.isCpp(activeEditor.document)) {
@@ -487,16 +589,7 @@ async function onSwitchHeaderSource(): Promise<void> {
487589
if (!targetFileName) {
488590
return;
489591
}
490-
// If the targetFileName has a path that is a symlink target of a workspace folder,
491-
// then replace the RootRealPath with the RootPath (the symlink path).
492-
let targetFileNameReplaced: boolean = false;
493-
clients.forEach(client => {
494-
if (!targetFileNameReplaced && client.RootRealPath && client.RootPath !== client.RootRealPath
495-
&& targetFileName.startsWith(client.RootRealPath)) {
496-
targetFileName = client.RootPath + targetFileName.substring(client.RootRealPath.length);
497-
targetFileNameReplaced = true;
498-
}
499-
});
592+
targetFileName = getEditorPath(targetFileName);
500593
const document: vscode.TextDocument = await vscode.workspace.openTextDocument(targetFileName);
501594
await vscode.window.showTextDocument(document).then(undefined, logAndReturn.undefined);
502595
} catch (e) {

‎Extension/src/nativeStrings.json‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,5 +777,13 @@
777777
"browse_database_disabled_incompatible_storage": {
778778
"text": "The browse database was disabled because its storage location does not support SQLite WAL shared memory. Set browse.databaseFilename to a local path.",
779779
"hint": "browse.databaseFilename is a setting name. {Locked=\"SQLite WAL\"} {Locked=\"browse.databaseFilename\"}"
780+
},
781+
"selected_translation_unit_not_include_file_previous": {
782+
"text": "The selected translation unit '{0}' does not include '{1}'. IntelliSense will restore the previous translation unit if it is still available; otherwise, it will use '{1}' as a header-only translation unit.",
783+
"hint": "{0} is the selected translation unit path. {1} is the active file path. {Locked=\"IntelliSense\"} {Locked=\"{0}\"} {Locked=\"{1}\"}"
784+
},
785+
"selected_translation_unit_not_include_file_header_only": {
786+
"text": "The selected translation unit '{0}' does not include '{1}'. No previous translation unit is available, so IntelliSense will use '{1}' as a header-only translation unit.",
787+
"hint": "{0} is the selected translation unit path. {1} is the active file path. {Locked=\"IntelliSense\"} {Locked=\"{0}\"} {Locked=\"{1}\"}"
780788
}
781789
}

0 commit comments

Comments
 (0)