Skip to content

Commit 350e56a

Browse files
allow active project selection control intellisense (#4877)
Co-authored-by: Hannia Valera <[email protected]>
1 parent 69f2b2a commit 350e56a

4 files changed

Lines changed: 127 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ Improvements:
5151

5252
Bug Fixes:
5353
- Fix stale C/C++ custom-configuration entries persisting after reconfigure/preset switches, which could cause Go to Definition/IntelliSense to surface symbols from inactive sources in the same folder. [#4472](https://github.com/microsoft/vscode-cmake-tools/issues/4472)
54+
- Fix IntelliSense not updating when switching the active project in multi-project workspaces with multiple `cmake.sourceDirectory` entries. [#4390](https://github.com/microsoft/vscode-cmake-tools/issues/4390)
5455
- Fix `tasks.json` schema validation rejecting valid CMake task commands `package` and `workflow`. [#4167](https://github.com/microsoft/vscode-cmake-tools/issues/4167)
5556
- Import the `EXTERNAL_INCLUDE` environment variable from the VS developer environment so that MSVC's external-header diagnostic suppression works correctly. [#4217](https://github.com/microsoft/vscode-cmake-tools/issues/4217)
5657
- Fix test presets not automatically switching when the build preset changes in multi-config generator setups (e.g., Ninja Multi-Config). The extension now auto-selects a compatible test preset after a build preset change, and properly considers build type when guessing the test preset. [#4395](https://github.com/microsoft/vscode-cmake-tools/issues/4395)

src/cpptools.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,18 @@ export class CppConfigurationProvider implements cpptools.CustomConfigurationPro
363363
private getConfiguration(uri: vscode.Uri): cpptools.SourceFileConfigurationItem | undefined {
364364
const normalizedPath = util.platformNormalizePath(uri.fsPath);
365365
const configurations = this.fileIndex.get(normalizedPath);
366+
367+
// If we have an active folder, only provide configurations for files
368+
// that belong to that folder's project. This ensures IntelliSense
369+
// reflects the active project in multi-project workspaces.
370+
if (this.activeFolder) {
371+
const activeFolderFiles = this.fileIndexByFolder.get(this.activeFolder);
372+
if (!activeFolderFiles?.has(normalizedPath)) {
373+
// This file is not part of the active folder's project
374+
return undefined;
375+
}
376+
}
377+
366378
if (this.activeTarget && configurations?.has(this.activeTarget)) {
367379
return configurations!.get(this.activeTarget);
368380
} else {
@@ -441,6 +453,22 @@ export class CppConfigurationProvider implements cpptools.CustomConfigurationPro
441453
*/
442454
private activeTarget: string | null = null;
443455

456+
/**
457+
* The active folder path. When set, IntelliSense configurations from this folder
458+
* are preferred over configurations from other folders for shared source files.
459+
*/
460+
private activeFolder: string | null = null;
461+
462+
/**
463+
* Set the active folder for IntelliSense configuration resolution.
464+
* When a file exists in multiple project folders, configurations from
465+
* the active folder will be preferred.
466+
* @param folder The folder path, or null to clear
467+
*/
468+
setActiveFolder(folder: string | null) {
469+
this.activeFolder = folder ? util.platformNormalizePath(folder) : null;
470+
}
471+
444472
private activeBuildType: string | null = null;
445473
private buildTypesSeen = new Set<string>();
446474

src/extension.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,13 @@ export class ExtensionManager implements vscode.Disposable {
977977
this.onActiveProjectChangedEmitter.fire(vscode.Uri.file(activeProject.folderPath));
978978
const currentActiveFolderPath = this.activeFolderPath();
979979
await this.extensionContext.workspaceState.update('activeFolder', currentActiveFolderPath);
980+
981+
// Update IntelliSense to prefer configurations from the active project
982+
this.configProvider.setActiveFolder(activeProject.folderPath);
983+
if (this.cppToolsAPI && this.configProvider.ready) {
984+
this.cppToolsAPI.didChangeCustomBrowseConfiguration(this.configProvider);
985+
this.cppToolsAPI.didChangeCustomConfiguration(this.configProvider);
986+
}
980987
}
981988
}
982989

test/unit-tests/cpptools.test.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,4 +469,95 @@ suite('CppTools tests', () => {
469469
expect(activeConfigurations.length).to.eq(1);
470470
expect(activeConfigurations[0].configuration.defines).to.contain('NEW');
471471
});
472+
473+
test('Prefers configurations from active folder in multi-project workspace', async () => {
474+
const provider = new CppConfigurationProvider();
475+
const cache = await CMakeCache.fromPath(getTestResourceFilePath('TestCMakeCache.txt'));
476+
477+
// Set up two folders with a file that exists in both
478+
const folderA = here;
479+
const folderB = path.join(here, '../smoke');
480+
const sharedFileName = 'shared.cpp';
481+
const fileInFolderA = path.join(folderA, sharedFileName);
482+
const fileInFolderB = path.join(folderB, sharedFileName);
483+
484+
const codeModelA: codeModel.CodeModelContent = {
485+
configurations: [{
486+
name: 'Release',
487+
projects: [{
488+
name: 'projectA',
489+
sourceDirectory: folderA,
490+
targets: [{
491+
name: 'targetA',
492+
type: 'EXECUTABLE',
493+
fileGroups: [{
494+
sources: [fileInFolderA],
495+
isGenerated: false,
496+
defines: ['PROJECT_A'],
497+
compileCommandFragments: ['-DPROJECT_A'],
498+
language: 'CXX'
499+
}]
500+
}]
501+
}]
502+
}],
503+
toolchains: new Map<string, codeModel.CodeModelToolchain>()
504+
};
505+
506+
const codeModelB: codeModel.CodeModelContent = {
507+
configurations: [{
508+
name: 'Release',
509+
projects: [{
510+
name: 'projectB',
511+
sourceDirectory: folderB,
512+
targets: [{
513+
name: 'targetB',
514+
type: 'EXECUTABLE',
515+
fileGroups: [{
516+
sources: [fileInFolderB],
517+
isGenerated: false,
518+
defines: ['PROJECT_B'],
519+
compileCommandFragments: ['-DPROJECT_B'],
520+
language: 'CXX'
521+
}]
522+
}]
523+
}]
524+
}],
525+
toolchains: new Map<string, codeModel.CodeModelToolchain>()
526+
};
527+
528+
// Index both folders
529+
provider.updateConfigurationData({ cache, codeModelContent: codeModelA, activeTarget: 'targetA', activeBuildTypeVariant: 'Release', folder: folderA });
530+
provider.updateConfigurationData({ cache, codeModelContent: codeModelB, activeTarget: 'targetB', activeBuildTypeVariant: 'Release', folder: folderB });
531+
532+
// Without active folder set, both files should be available
533+
let configsA = await provider.provideConfigurations([vscode.Uri.file(fileInFolderA)]);
534+
let configsB = await provider.provideConfigurations([vscode.Uri.file(fileInFolderB)]);
535+
expect(configsA.length).to.eq(1);
536+
expect(configsB.length).to.eq(1);
537+
expect(configsA[0].configuration.defines).to.contain('PROJECT_A');
538+
expect(configsB[0].configuration.defines).to.contain('PROJECT_B');
539+
540+
// Set folderA as active - file in folderB should no longer provide configuration
541+
provider.setActiveFolder(folderA);
542+
configsA = await provider.provideConfigurations([vscode.Uri.file(fileInFolderA)]);
543+
configsB = await provider.provideConfigurations([vscode.Uri.file(fileInFolderB)]);
544+
expect(configsA.length).to.eq(1);
545+
expect(configsA[0].configuration.defines).to.contain('PROJECT_A');
546+
expect(configsB.length).to.eq(0); // Active folder doesn't have this file
547+
548+
// Switch active folder to B - file in folderA should no longer provide configuration
549+
provider.setActiveFolder(folderB);
550+
configsA = await provider.provideConfigurations([vscode.Uri.file(fileInFolderA)]);
551+
configsB = await provider.provideConfigurations([vscode.Uri.file(fileInFolderB)]);
552+
expect(configsA.length).to.eq(0); // Active folder doesn't have this file
553+
expect(configsB.length).to.eq(1);
554+
expect(configsB[0].configuration.defines).to.contain('PROJECT_B');
555+
556+
// Clear active folder - both should work again
557+
provider.setActiveFolder(null);
558+
configsA = await provider.provideConfigurations([vscode.Uri.file(fileInFolderA)]);
559+
configsB = await provider.provideConfigurations([vscode.Uri.file(fileInFolderB)]);
560+
expect(configsA.length).to.eq(1);
561+
expect(configsB.length).to.eq(1);
562+
});
472563
});

0 commit comments

Comments
 (0)