Skip to content

Commit

Permalink
Support multiple root cpp build configurations
Browse files Browse the repository at this point in the history
Added support for multiple root cpp build configurations.
Instead of maintaining a single active configuration, a map is
used to maintain the relationship between the workspace root and its
given `CppBuildConfiguration`. This feature is an experimental
PR based on new developments in clangd to support multiple
compilation databases.

By default, when selecting multiple build configurations, the extension
will create a merged compilation database. Added a preference to support
an experimental clangd feature (that might either change or be removed)
which would not generate this aggregated database, but rather just
notify clangd of all the databases the user wants to use.

Until this is officially supported you shouldn't set this experimental
preference, unless for testing purposes.

- Update the `cpp` manager to handle the new data structure.
- CppBuildConfigurationManager creates a merged compilation database
  when multiple configs are used.
- Update the test cases (fix the task test case with incorrect imports
  and false positive results).

Signed-off-by: Vincent Fugnitto <[email protected]>
Signed-off-by: Paul Maréchal <[email protected]>
  • Loading branch information
vince-fugnitto committed Jul 30, 2019
1 parent 3a56c70 commit 41fc2c4
Show file tree
Hide file tree
Showing 13 changed files with 600 additions and 183 deletions.
2 changes: 2 additions & 0 deletions packages/cpp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"@theia/preferences": "^0.9.0",
"@theia/process": "^0.9.0",
"@theia/task": "^0.9.0",
"@theia/workspace": "^0.9.0",
"@theia/variable-resolver": "0.9.0",
"string-argv": "^0.1.1"
},
"publishConfig": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { injectable, inject } from 'inversify';
import { StatusBar, StatusBarAlignment } from '@theia/core/lib/browser';
import { CppBuildConfigurationManager, CppBuildConfiguration } from './cpp-build-configurations';
import { CPP_CHANGE_BUILD_CONFIGURATION } from './cpp-build-configurations-ui';
import { WorkspaceService } from '@theia/workspace/lib/browser';

@injectable()
export class CppBuildConfigurationsStatusBarElement {
Expand All @@ -28,15 +29,18 @@ export class CppBuildConfigurationsStatusBarElement {
@inject(StatusBar)
protected readonly statusBar: StatusBar;

@inject(WorkspaceService)
protected readonly workspaceService: WorkspaceService;

protected readonly cppIdentifier = 'cpp-configurator';

/**
* Display the `CppBuildConfiguration` status bar element,
* and listen to changes to the active build configuration.
*/
show(): void {
this.setCppBuildConfigElement(this.cppManager.getActiveConfig());
this.cppManager.onActiveConfigChange(config => this.setCppBuildConfigElement(config));
this.setCppBuildConfigElement(this.getValidActiveCount());
this.cppManager.onActiveConfigChange2(configs => this.setCppBuildConfigElement(configs.size));
}

/**
Expand All @@ -45,14 +49,25 @@ export class CppBuildConfigurationsStatusBarElement {
*
* @param config the active `CppBuildConfiguration`.
*/
protected setCppBuildConfigElement(config: CppBuildConfiguration | undefined): void {
protected setCppBuildConfigElement(count: number): void {
this.statusBar.setElement(this.cppIdentifier, {
text: `$(wrench) C/C++ ${config ? '(' + config.name + ')' : 'Build Config'}`,
text: `$(wrench) C/C++ Build Config (${count} of ${this.workspaceService.tryGetRoots().length})`,
tooltip: 'C/C++ Build Config',
alignment: StatusBarAlignment.RIGHT,
command: CPP_CHANGE_BUILD_CONFIGURATION.id,
priority: 0.5,
});
}

/**
* Get the valid active configuration count.
*/
protected getValidActiveCount(): number {
let items: (CppBuildConfiguration | undefined)[] = [];
if (this.cppManager.getAllActiveConfigs) {
items = [...this.cppManager.getAllActiveConfigs().values()].filter(config => !!config);
}
return items.length;
}

}
165 changes: 98 additions & 67 deletions packages/cpp/src/browser/cpp-build-configurations-ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,18 @@
import { Command, CommandContribution, CommandRegistry, CommandService } from '@theia/core';
import { injectable, inject } from 'inversify';
import { QuickOpenService } from '@theia/core/lib/browser/quick-open/quick-open-service';
import { QuickOpenModel, QuickOpenItem, QuickOpenMode, } from '@theia/core/lib/browser/quick-open/quick-open-model';
import { FileSystem, FileSystemUtils } from '@theia/filesystem/lib/common';
import { FileSystem } from '@theia/filesystem/lib/common';
import URI from '@theia/core/lib/common/uri';
import { PreferenceScope, PreferenceService } from '@theia/preferences/lib/browser';
import { CppBuildConfigurationManager, CppBuildConfiguration, CPP_BUILD_CONFIGURATIONS_PREFERENCE_KEY } from './cpp-build-configurations';
import { CppBuildConfigurationManager, CPP_BUILD_CONFIGURATIONS_PREFERENCE_KEY, isCppBuildConfiguration, equals } from './cpp-build-configurations';
import { EditorManager } from '@theia/editor/lib/browser';
import { CommonCommands } from '@theia/core/lib/browser';
import { CommonCommands, LabelProvider } from '@theia/core/lib/browser';
import { QuickPickService, QuickPickItem } from '@theia/core/lib/common/quick-pick-service';
import { WorkspaceService } from '@theia/workspace/lib/browser';
import { CppBuildConfiguration } from '../common/cpp-build-configuration-protocol';

@injectable()
export class CppBuildConfigurationChanger implements QuickOpenModel {
export class CppBuildConfigurationChanger {

@inject(CommandService)
protected readonly commandService: CommandService;
Expand All @@ -40,89 +42,119 @@ export class CppBuildConfigurationChanger implements QuickOpenModel {
@inject(FileSystem)
protected readonly fileSystem: FileSystem;

@inject(LabelProvider)
protected readonly labelProvider: LabelProvider;

@inject(QuickPickService)
protected readonly quickPick: QuickPickService;

@inject(QuickOpenService)
protected readonly quickOpenService: QuickOpenService;

@inject(PreferenceService)
protected readonly preferenceService: PreferenceService;

readonly createItem: QuickOpenItem = new QuickOpenItem({
@inject(WorkspaceService)
protected readonly workspaceService: WorkspaceService;

/**
* Item used to trigger creation of a new build configuration.
*/
protected readonly createItem: QuickPickItem<'createNew'> = ({
label: 'Create New',
iconClass: 'fa fa-plus',
value: 'createNew',
description: 'Create a new build configuration',
run: (mode: QuickOpenMode): boolean => {
if (mode !== QuickOpenMode.OPEN) {
return false;
}
this.commandService.executeCommand(CPP_CREATE_NEW_BUILD_CONFIGURATION.id);
return true;
},
iconClass: 'fa fa-plus'
});

readonly resetItem: QuickOpenItem = new QuickOpenItem({
/**
* Item used to trigger reset of the active build configuration.
*/
protected readonly resetItem: QuickPickItem<'reset'> = ({
label: 'None',
iconClass: 'fa fa-times',
description: 'Reset active build configuration',
run: (mode: QuickOpenMode): boolean => {
if (mode !== QuickOpenMode.OPEN) {
return false;
}
this.commandService.executeCommand(CPP_RESET_BUILD_CONFIGURATION.id);
return true;
},
value: 'reset',
description: 'Reset the active build configuration',
iconClass: 'fa fa-times'
});

async onType(lookFor: string, acceptor: (items: QuickOpenItem[]) => void): Promise<void> {
const items: QuickOpenItem[] = [];
const active: CppBuildConfiguration | undefined = this.cppBuildConfigurations.getActiveConfig();
const configurations = this.cppBuildConfigurations.getValidConfigs();

const homeStat = await this.fileSystem.getCurrentUserHome();
const home = (homeStat) ? new URI(homeStat.uri).path.toString() : undefined;

// Item to create a new build configuration
items.push(this.createItem);
/**
* Change the build configuration for a given root.
* If multiple roots are available, prompt users a first time to select their desired root.
* Once a root is determined, prompt users to select an active build configuration if applicable.
*/
async change(): Promise<void> {

// Only return 'Create New' when no build configurations present
if (!configurations.length) {
acceptor(items);
// Prompt users to determine working root.
const root = await this.selectWorkspaceRoot();
if (!root) {
return;
}

// Item to de-select any active build config
if (active) {
items.push(this.resetItem);
// Prompt users to determine action (set active config, reset active config, create new config).
const action = await this.selectCppAction(root);
if (!action) {
return;
}

// Add one item per build config
configurations.forEach(config => {
const uri = new URI(config.directory);
items.push(new QuickOpenItem({
label: config.name,
// add an icon for active build config, and an empty placeholder for all others
iconClass: (config === active) ? 'fa fa-check' : 'fa fa-empty-item',
description: (home) ? FileSystemUtils.tildifyPath(uri.path.toString(), home) : uri.path.toString(),
run: (mode: QuickOpenMode): boolean => {
if (mode !== QuickOpenMode.OPEN) {
return false;
}

this.cppBuildConfigurations.setActiveConfig(config);
return true;
},
}));
});
// Perform desired action.
if (action === 'createNew') {
this.commandService.executeCommand(CPP_CREATE_NEW_BUILD_CONFIGURATION.id);
}
if (action === 'reset') {
this.cppBuildConfigurations.setActiveConfig(undefined, root);
}
if (action && isCppBuildConfiguration(action)) {
this.cppBuildConfigurations.setActiveConfig(action, root);
}
}

acceptor(items);
/**
* Pick a workspace root using the quick open menu.
*/
protected async selectWorkspaceRoot(): Promise<string | undefined> {
const roots = this.workspaceService.tryGetRoots();
return this.quickPick.show(roots.map(({ uri: root }) => {
const active = this.cppBuildConfigurations.getActiveConfig(root);
return {
// See: WorkspaceUriLabelProviderContribution
// It will transform the path to a prettier display (adding a ~, etc).
label: this.labelProvider.getName(new URI(root).withScheme('file')),
description: active ? active.name : 'undefined',
value: root,
};
}), { placeholder: 'Select workspace root' });
}

open() {
const configs = this.cppBuildConfigurations.getValidConfigs();
this.quickOpenService.open(this, {
placeholder: (configs.length) ? 'Choose a build configuration...' : 'No build configurations present',
fuzzyMatchLabel: true,
fuzzyMatchDescription: true,
/**
* Lists the different options for a given root if specified, first else.
* In this case, the options are to set/unset/create a build configuration.
*
* @param root
*/
protected async selectCppAction(root: string | undefined): Promise<string | CppBuildConfiguration | undefined> {
const items: QuickPickItem<'createNew' | 'reset' | CppBuildConfiguration>[] = [];
// Add the 'Create New' item at all times.
items.push(this.createItem);
// Add the 'Reset' item if there currently is an active config.
if (this.cppBuildConfigurations.getActiveConfig(root)) {
items.push(this.resetItem);
}
// Display all valid configurations for a given root.
const configs = this.cppBuildConfigurations.getValidConfigs(root);
const active = this.cppBuildConfigurations.getActiveConfig(root);
configs.map(config => {
items.push({
label: config.name,
description: config.directory,
iconClass: active && equals(config, active) ? 'fa fa-check' : 'fa fa-empty-item',
value: {
name: config.name,
directory: config.directory,
commands: config.commands
},
});
});
return this.quickPick.show(items, { placeholder: 'Select action' });
}

/** Create a new build configuration with placeholder values. */
Expand All @@ -132,7 +164,6 @@ export class CppBuildConfigurationChanger implements QuickOpenModel {
configs.push({ name: '', directory: '' });
await this.preferenceService.set(CPP_BUILD_CONFIGURATIONS_PREFERENCE_KEY, configs, PreferenceScope.Workspace);
}

}

export const CPP_CATEGORY = 'C/C++';
Expand Down Expand Up @@ -184,7 +215,7 @@ export class CppBuildConfigurationsContributions implements CommandContribution
execute: () => this.cppChangeBuildConfiguration.createConfig()
});
commands.registerCommand(CPP_CHANGE_BUILD_CONFIGURATION, {
execute: () => this.cppChangeBuildConfiguration.open()
execute: () => this.cppChangeBuildConfiguration.change()
});
}
}
Loading

0 comments on commit 41fc2c4

Please sign in to comment.